webentwicklung-frage-antwort-db.com.de

"Numerisches Argument erforderlich" beim Ausführen eines Skripts mit arithmetischen Operationen

Ich versuche, eine Funktion zu schreiben, die einen Zeitstempel der Form hr: min: sec, ms (d. H. 15: 41: 47,757) in Millisekunden konvertieren soll. Die Funktion ist folgende:

#!/bin/sh
mili () {

    hr=$(echo "$1" | cut -c1-2)
    echo "hr is: " $hr
    min=$(echo "$1" | cut -c4-5)
    echo "min is: " $min
    sec=$(echo "$1" | cut -c7-8)
    echo "sec is: " $sec
    ms=$(echo "$1" | cut -c10-12)
    echo "ms is: " $ms
    total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)

    return "$total"
    #echo "Result is: "$total" "
}

mili $1

Wenn ich es jedoch ausführe:

./mili.sh "15: 41: 47,757"

Ich erhalte folgende Ausgabenachricht:

./mili.sh: command substitution: line 15: syntax error near unexpected token 
`\*'
./mili.sh: command substitution: line 15: `($hr \* 3600 + $min \* 60 + $sec) 
\* 1000 + $ms'
./mili.sh: line 17: return: : numeric argument required

Ich habe Variationen von expr mit und ohne einfache Anführungszeichen, doppelte Anführungszeichen und Backticks ausprobiert, aber es scheint nie zu scheinen, dass sie die Arithmetik berechnen. Ich kann bestätigen, dass ein einfacher Befehl wie folgt funktioniert: Ausdruck 2 * 3, aber wenn ich versuche, etwas Ähnliches in meinem Skript zu verwenden, schlägt dies fehl.

Wie bekomme ich es, meinen Ausdruck einfach zu berechnen?

4
Tikiyetti

Innerhalb der Arithmetik muss * nicht mit Escapezeichen versehen werden. Außerdem fehlten einige Klammern. So ersetzen Sie:

total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)

Mit:

total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))

Alternative

Der Code kann vereinfacht werden, sodass cut nicht mehrfach aufgerufen werden muss:

mili() {
    IFS=':,' read hr min sec ms <<<"$1"
    echo "hr is: " $hr
    echo "min is: " $min
    echo "sec is: " $sec
    echo "ms is: " $ms
    total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))
    echo "Total=$total"
    return "$total"
}

Nebenbei: Bash-Arithmetik und Dollarzeichen

In der Bash-Arithmetik ist das Dollarzeichen vor einer Variablen optional. Zum Beispiel:

$ a=1; echo "$((1 + a)) and $((1+ $a))"
2 and 2

Während in einigen Style-Guides empfohlen wird, $ im arithmetischen Kontext wegzulassen, gibt es einen wesentlichen Unterschied. Wie Chepner in den Kommentaren darauf hinweist, ist die Behandlung von undefinierten Variablen sehr unterschiedlich:

$ unset a
$ echo $((1 + $a))
bash: 1 + : syntax error: operand expected (error token is "+ ")
$ echo $((1 + a))
1

In Summe:

  1. Wenn Sie möchten, dass eine undefinierte Variable den Standardwert Null hat, lassen Sie $ weg.

  2. Wenn Sie möchten, dass eine undefinierte Variable durch nichts ersetzt wird, was möglicherweise zu einem ungültigen Ausdruck führt, schließen Sie $ ein.

In der Shell-Funktion mili würde eine undefinierte Variable hr, min usw. auf einen Codefehler hinweisen und wir möchten möglicherweise eine Fehlernachricht, die uns darüber warnt, und wir möchten $ einfügen. In anderen Fällen, in denen ein Standardwert von Null angemessen ist, würden wir das Fehlen von $ nicht korrekt angeben.

5
John1024

Noch ein paar Punkte: 

  1. nicht return "$total": Ein Rückgabewert ist ein int zwischen 0 und 255. Sie müssen echo "$total"

  2. es wird zu Fehlern kommen, wenn die Stunde/Minute/Sekunde 08 oder 09 - ist. bash behandelt Zahlen mit führender Null als Oktal und 8 und 9 sind ungültige Oktalstellen.

    $ mili 11:22:09,456
    hr is:  11
    min is:  22
    sec is:  09
    ms is:  456
    bash: (11 * 3600 + 22 * 60 + 09: value too great for base (error token is "09")
    

Ich würde schreiben:

mili () {     
    IFS=":,." read -r hr min sec ms <<<"$1"
    echo "hr is:   $hr" >&2
    echo "min is:  $min" >&2
    echo "sec is:  $sec" >&2
    echo "ms is:  $ms" >&2
    echo "$(( ((10#$hr * 60 + 10#$min) * 60 + 10#$sec) * 1000 + 10#$ms ))"
}

wo der 10# Basis-10-Nummern erzwingt

dann

$ ms=$(mili 11:22:09.456)
hr is:   11
min is:  22
sec is:  09
ms is:  456

$ echo $ms
40929456
3
glenn jackman

Hier ist eine verrückte Alternative:

$ mili () {
    IFS=., read -r time ms <<<"$1"
    ms3=$(cut -c 1-3 <<<"${ms}000")
    echo "$(date -u -d "1970-01-01 $time" +%s)$ms3"
}

$ mili 15:41:47,757
56507757

$ mili 15:41:47,75
56507750

$ mili 15:41:47
56507000
1
glenn jackman