Pourquoi la branche 'if [$ 1 = “1”]' est-elle toujours sélectionnée même si $ 1 n'est pas 1?

10

J'ai un script shell nommé 'teleport.sh' comme ceci:

if [ $1="1" ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ $1="2" ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ $1="3" ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Quand j'exécute:

sh teleport.sh 2 testfile

Ceci testfileest déplacé vers le ~/lab/Sunrépertoire, ce qui me déroute beaucoup car je n'ai pas passé 1 ou '1' à ce script.

Qu'est-ce qui ne va pas ici?

Zen
la source
1
+1 pour laboratoire , Soleil , Lune , Terre et téléportation . Mais vous devriez toujours doubles extensions de devis ( $var, $(cmd)et même `cmd`[auquel $(cmd)devrait être préféré]). Il y a des cas limites où vous ne devez citer, mais toujours le faire ne fera pas mal.
nyuszika7h
@ nyuszika7h, les guillemets ne doivent-ils pas signifier "$ var" et "$ cmd"? quel est l'avantage du support rond que vous avez mentionné ci-dessus?
Zen
$(cmd)est la substitution de commande , (la plupart du temps) identique à `cmd`. Voir mywiki.wooledge.org/CommandSubstitution et mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Réponses:

19

L'utilisation d'espaces résout votre problème.

if [ "$1" = 1 ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ "$1" = 2 ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ "$1" = 3 ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Bien que ce soit plus propre:

#!/bin/bash

action=$1
shift
files=("$@")
case $action in  
  1) mv -- "${files[@]}" ~/lab/Sun     ;;
  2) mv -- "${files[@]}" ~/lab/Moon    ;;
  3) mv -- "${files[@]}" ~/lab/Earth   ;;
esac
Karlo
la source
3
Oui, les espaces sont nécessaires, mais je me demande pourquoi la $1="1"syntaxe originale "fonctionne" du tout (produisant un vrai résultat). Comment le [/ testbuiltin interprète-t-il réellement cette expression?
echristopherson
5
@echristopherson Sans espaces, testne voit qu'un seul argument (une "valeur l"). Sans les autres arguments (un "opérateur" et une "valeur r"), il n'y a rien à tester, donc testdit simplement "ok, ben ouais, tu m'as donné quelque chose et cela doit être vide."
évêque
2
$1="1"est $1concaténé par=1
jdh8
lors de l'utilisation de case, comment définir une action à exécuter lorsqu'aucune des conditions n'est remplie?
Zen
11

La première chose évidente est que vous devez fournir des espaces entre les arguments de [, testou [[:

if [ "$1" = 1 ];

Dans Bash, l'utilisation [[ ]]est recommandée car elle ne fait pas de choses inutiles pour l'expression conditionnelle comme le fractionnement de mots et l'expansion de noms de chemin. Il n'est pas non plus nécessaire de citer des guillemets doubles. Un opérateur plus lisible ==peut également être utilisé.

if [[ $1 == 1 ]];

Ajout d'une note: Si un deuxième opérande contient également des variables, citant est nécessaire car il peut être soumis à la correspondance de motif si elle contient des caractères reconnaissables comme *, ?, [], etc .. si elle était étendue englobement ou correspondance de motif est activé avec shopt -s extglob, d' autres formes comme @(), !(), etc. seront également reconnus comme des modèles. Voir Correspondance de motifs .

Avec des opérateurs comme <et >cela peut encore être nécessaire car j'avais rencontré un bug où ne pas citer le deuxième argument provoquait des résultats différents.

Quant au premier opérande, rien ne s'applique.

Considérez également cette variante plus simple:

case "$1" in
1)
    mv -- "${@:2}" ~/lab/Sun
    ;;
2)
    mv -- "${@:2}" ~/lab/Moon
    ;;
3)
    mv -- "${@:2}" ~/lab/Earth
    ;;
esac

Ou condensé:

case "$1" in
1) mv -- "${@:2}" ~/lab/Sun ;;
2) mv -- "${@:2}" ~/lab/Moon ;;
3) mv -- "${@:2}" ~/lab/Earth ;;
esac

"${@:2}"est une forme d'extension de sous-chaîne ou d'extension de membre de tableau où 2est le décalage. Cela fait démarrer l'expansion à la deuxième valeur. Avec cela, nous n'aurons peut-être pas besoin d'utiliser shift.

L'ajout --empêche mvde reconnaître les noms de fichiers commençant par dash ( -) comme des options invalides.

konsolebox
la source
ne devriez-vous pas casser dans chaque cas?
Archemar
2
@Archemar: non, il n'y a pas de retombées (contrairement à beaucoup d'autres langues).
Mat
2
@Mat, il y a un échec si vous utilisez à la ;&place de ;;(ksh, bash, zsh uniquement). Mais alors breakn'empêche toujours pas la chute, breakc'est seulement pour sortir des boucles.
Stéphane Chazelas
Ce ne sont pas des arguments ifmais la [commande.
Stéphane Chazelas
1
Correction: les guillemets doubles ne sont pas nécessaires du côté gauche! [[ $foo == $bar ]]effectuera une correspondance de modèle, mais [[ $foo == "$bar" ]]ne le fera pas.
nyuszika7h
7

Pour répondre à la question de savoir pourquoi cela se produit, ce comportement de [aka testest documenté dans POSIX :

Dans la liste suivante, $ 1, $ 2, $ 3 et $ 4 représentent les arguments présentés pour tester:

[...]

1 argument:

Quittez true (0) si $ 1 n'est pas nul; sinon, quittez false.

Vous lui passez 1 argument, 2=1qui n'est pas nul, et se testtermine donc avec succès.

Comme le soulignent d'autres articles (et shellcheck ), si vous souhaitez comparer pour l'égalité, vous devrez plutôt passer les 3 arguments 2, =et 1.

cet autre gars
la source
3
Dans un sens, c'est la seule réponse qui a répondu à la question posée (plutôt que de donner une ou plusieurs recettes qui font ce que le PO voulait accomplir), et le shell peut être suffisamment mystérieux pour savoir pourquoi il fait les choses qu'il fait est utile .
dmckee --- chaton ex-modérateur
1

Je veux juste recommander une alternative portable mais aussi plus soignée. Bash n'est pas universel (et si vous n'avez pas besoin d'universel, pourquoi écrivez-vous un script shell?)

#! /bin/sh
action="$1"
shift
case "$action" in
    1) dest=Sun   ;;
    2) dest=Moon  ;;
    3) dest=Earth ;;
    *) echo "Unrecognized action code '$action' (must be 1, 2, or 3)" >&2; exit 1 ;;
esac
mv -- "$@" ~/lab/"$dest"

(Remarque pour les pédants: oui, je sais que les citations $actionsur la case "$action" inligne sont inutiles, mais je pense qu'il est préférable de les mettre de toute façon, afin que les futurs lecteurs n'aient pas à s'en souvenir.)

zwol
la source
1
"Bash n'est pas universel (et si vous n'avez pas besoin d'universel, pourquoi écrivez-vous un script shell?)" - cela semble impliquer que le script bash n'a pas de cas d'utilisation.
Ruslan
@Ruslan Oui, c'est mon opinion réfléchie. Écrivez des /bin/shscripts portables , si vous en avez besoin; sinon, écrivez dans un langage de script moins terrible que shell. L'interpréteur Perl de base est en fait plus susceptible d'être présent dans les environnements propriétaires hérités et les environnements intégrés réduits que Bash.
zwol