web-dev-qa-db-fra.com

Obtenir "l'argument numérique requis" lors de l'exécution d'un script avec des opérations arithmétiques

J'essaie d'écrire une fonction censée convertir un horodatage de la forme hr: min: sec, ms (15: 41: 47,757) en millisecondes. La fonction est la suivante:

#!/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

Cependant, quand je le lance:

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

Je reçois le message de sortie suivant:

./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

J'ai essayé des variantes de expr avec et sans guillemets simples, guillemets doubles et croisés, mais je n'arrive jamais à le faire calculer l'arithmétique. Je peux confirmer une commande simple comme celle-ci: expr 2 * 3 mais lorsque j'essaie d'utiliser quelque chose de similaire dans mon script, cela échoue.

Comment puis-je l'obtenir simplement pour calculer mon expression?

4
Tikiyetti

Dans l'arithmétique, * n'a pas besoin d'être échappé. De plus, il manquait quelques parenthèses. Ainsi, remplacez:

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

Avec:

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

Alternative

Le code peut être simplifié en évitant le recours à plusieurs appels à cut:

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"
}

De côté: arithmétique Bash et signes dollar

Dans le contexte arithmétique bash, le signe dollar avant une variable est facultatif. Par exemple:

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

Bien que certains guides de style recommandent d'omettre $ dans un contexte arithmétique, il existe une différence essentielle. Comme le souligne Chepner dans les commentaires, le traitement des variables non définies est très différent:

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

En somme:

  1. Si vous souhaitez qu'une valeur non définie par défaut soit égale à zéro, omettez le $.

  2. Si vous souhaitez qu'une variable indéfinie ne soit remplacée par rien, ce qui peut entraîner une expression non valide, incluez le $.

Dans la fonction Shell mili, une variable non définie hr, min, etc., indiquerait une erreur de code et nous pourrions souhaiter un message d'erreur pour nous en avertir et inclure le $. Dans d'autres circonstances où une valeur par défaut de zéro est raisonnable, nous ne le ferions pas et omettre le $ serait correct.

5
John1024

Un autre couple de points: 

  1. non return "$total": une valeur de retour est un entier compris entre 0 et 255. Vous devez echo "$total"

  2. vous allez avoir des erreurs lorsque l'heure/minute/seconde est 08 ou 09 - bash traite les nombres avec le zéro non significatif comme octal, et 8 et 9 sont des chiffres octaux invalides.

    $ 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")
    

J'écrirais:

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 ))"
}

où le 10# force les nombres de la base 10

puis

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

$ echo $ms
40929456
3
glenn jackman

Voici une alternative folle:

$ 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