Perché il ramo 'if [$ 1 = “1”]' è sempre selezionato anche se $ 1 non è 1?


10

Ho uno script di shell chiamato 'teleport.sh' come questo:

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

Quando eseguo:

sh teleport.sh 2 testfile

Questo testfileviene spostato nella ~/lab/Sundirectory, il che mi confonde molto perché non ho passato 1 o '1' a quello script.

Cosa c'è che non va qui?


1
+1 per laboratorio , sole , luna , terra e teletrasporto . Ma si dovrebbe sempre espansioni doppi apici ( $var, $(cmd), e anche `cmd`[a cui $(cmd)dovrebbe essere preferito]). Ci sono alcuni casi limite in cui non dovete citare, ma sempre facendo non farà male.
nyuszika7h,

@ nyuszika7h, la doppia virgoletta non dovrebbe significare "$ var" e "$ cmd"? qual è il vantaggio della parentesi tonda che hai menzionato sopra?
Zen,

$(cmd)è la sostituzione dei comandi , (principalmente) lo stesso di `cmd`. Vedi mywiki.wooledge.org/CommandSubstitution e mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Risposte:


19

L'uso degli spazi risolve il problema.

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

Anche se questo è più ordinato:

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

3
Sì, gli spazi sono necessari, ma mi chiedo perché la $1="1"sintassi originale "funzioni" (producendo un risultato vero). In che modo [/ testbuiltin interpreta effettivamente quell'espressione?
ecristopherson,

5
@echristopherson Senza spazi, testvede solo un argomento (un "valore l"). Senza gli altri argomenti (un "operatore" e un "valore r"), non c'è nulla da testare, quindi testdice solo "ok, beh sì, mi hai dato qualcosa e questo deve essere vagamente vero".
vescovo

2
$1="1"è $1concatenato da=1
jdh8

quando si utilizza case, come impostare un'azione da eseguire quando nessuna delle condizioni è soddisfatta?
Zen,

11

La prima cosa ovvia è che dovresti fornire spazi tra gli argomenti di [, testo [[:

if [ "$1" = 1 ];

Quando sei in Bash, [[ ]]si consiglia di utilizzare in quanto non fa cose inutili per l'espressione condizionale come la suddivisione delle parole e l'espansione del percorso. Non è inoltre necessario citare virgolette doppie. È ==inoltre possibile utilizzare un operatore più leggibile .

if [[ $1 == 1 ]];

Aggiunta di una nota: se secondo operando contiene anche variabili, citando è necessario in quanto può essere soggetto a pattern matching se contiene caratteri riconoscibili come *, ?, [], ecc .. Se il globbing esteso o pattern matching viene abilitata con shopt -s extglob, altre forme come @(), !()ecc saranno anche riconosciuti come modelli. Vedi Corrispondenza dei motivi .

Con operatori simili <e >potrebbe essere ancora necessario, poiché una volta avevo riscontrato un bug in cui non citare il secondo argomento causava risultati diversi.

Per quanto riguarda il primo operando, non si applica nulla.

Considera anche questa variazione più semplice:

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

O condensato:

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

"${@:2}"è una forma di espansione della sottostringa o espansione dei membri dell'array in cui si 2trova l'offset. Questo fa iniziare l'espansione dal secondo valore. Con questo potrebbe non essere necessario utilizzare shift.

L'aggiunta --impedisce mvdi riconoscere i nomi di file che iniziano con trattino ( -) come opzioni non valide.


non dovresti rompere in ogni caso?
Archemar,

2
@Archemar: no, non c'è fall-through (contrariamente a molte altre lingue).
Mat

2
@Mat, c'è fall-through se usi ;&invece di ;;(solo ksh, bash, zsh). Ma poi breaknon impedisce ancora il fall-through, breakè solo quello di uscire dagli anelli.
Stéphane Chazelas,

Non sono argomenti per ifma per il [comando.
Stéphane Chazelas,

1
Correzione: le virgolette doppie non sono necessarie solo sul lato sinistro! [[ $foo == $bar ]]eseguirà la corrispondenza del modello, ma [[ $foo == "$bar" ]]non lo farà.
nyuszika7h,

7

Per rispondere alla domanda sul perché questo accada, questo comportamento di [aka testè documentato in POSIX :

Nel seguente elenco, $ 1, $ 2, $ 3 e $ 4 rappresentano gli argomenti presentati per il test:

[...]

1 argomento:

Esci da true (0) se $ 1 non è null; altrimenti, esci da falso.

Lo stai passando 1 argomento, 2=1che non è nullo, e quindi testesce con successo.

Come altri posti (e shellcheck ) indicare, se si voleva confrontare per l'uguaglianza, si dovrebbe invece avere per passare gli argomenti 3 2, =e 1.


3
In un certo senso questa è l'unica risposta che ha risposto alla domanda posta (piuttosto che dare una o più ricette che fanno ciò che l'OP voleva realizzare), e shell può essere sufficientemente misteriosa da sapere perché fa le cose che fa è utile .
dmckee --- ex gattino moderatore

1

Voglio solo raccomandare un'alternativa portatile ma anche più ordinata. Bash non è universale (e se non hai bisogno di universal, perché stai scrivendo uno script di 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"

(Nota per i pedanti: sì, so che le virgolette $actionsulla case "$action" inriga non sono necessarie, ma penso che sia meglio metterle lì, in modo che i futuri lettori non debbano ricordarlo.)


1
"Bash non è universale (e se non hai bisogno di universal, perché stai scrivendo uno script di shell?)" - questo sembra implicare che lo script bash non ha casi d'uso.
Ruslan,

@Ruslan Sì, questa è la mia opinione considerata. Scrivi /bin/shscript portatili , se ne hai bisogno; altrimenti, scrivi in ​​un linguaggio di scripting meno terribile di shell. L'interprete Perl di base è infatti più probabile che sia presente in ambienti proprietari legacy e in ambienti embedded ridotti rispetto a Bash.
zwol,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.