Warum wird der Zweig "if [$ 1 =" 1 "]" immer ausgewählt, auch wenn $ 1 nicht 1 ist?


10

Ich habe ein Shell-Skript namens 'teleport.sh' wie folgt:

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

Wenn ich ausführe:

sh teleport.sh 2 testfile

Dies testfilewird in das ~/lab/SunVerzeichnis verschoben , was mich sehr verwirrt, da ich nicht 1 oder '1' an dieses Skript übergeben habe.

Was ist hier los?


1
+1 für Labor , Sonne , Mond , Erde und Teleport . Aber Sie sollten immer doppelte Anführungszeichen Erweiterungen ( $var, $(cmd)und sogar `cmd`[zu denen $(cmd)bevorzugt werden sollte]). Es gibt einige Grenzfälle , wo man nicht haben , zu zitieren, aber immer tut es nicht schaden.
Nyuszika7h

@ nyuszika7h, sollte ein doppeltes Anführungszeichen nicht "$ var" und "$ cmd" bedeuten? Was ist der Vorteil der oben erwähnten runden Klammer?
Zen

$(cmd)ist Befehlsersetzung , (meistens) das gleiche wie `cmd`. Siehe mywiki.wooledge.org/CommandSubstitution und mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Antworten:


19

Die Verwendung von Leerzeichen behebt Ihr Problem.

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

Obwohl dies ordentlicher ist:

#!/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
Ja, die Leerzeichen sind notwendig, aber ich frage mich, warum die ursprüngliche $1="1"Syntax überhaupt "funktioniert" (was zu einem echten Ergebnis führt). Wie interpretiert das [/ testbuiltin diesen Ausdruck tatsächlich?
Echristopherson

5
@echristopherson Ohne Leerzeichen wird testnur ein Argument angezeigt (ein "l-Wert"). Ohne die anderen Argumente (ein "Operator" und ein "r-Wert") gibt es nichts zu testen, also testsagt man einfach "ok, na ja, du hast mir etwas gegeben und das muss vakuum wahr sein".
Bischof

2
$1="1"wird $1verkettet von=1
jdh8

Wie kann bei Verwendung von case eine Aktion festgelegt werden, die ausgeführt werden soll, wenn keine der Bedingungen erfüllt ist?
Zen

11

Die erste offensichtliche Sache ist , Sie Räume zwischen den Argumenten liefern sollten [, testoder [[:

if [ "$1" = 1 ];

In Bash wird die Verwendung [[ ]]empfohlen, da hierdurch keine für bedingte Ausdrücke unnötigen Funktionen wie Wortaufteilung und Pfadnamenerweiterung erforderlich sind. Das Zitieren um doppelte Anführungszeichen ist ebenfalls nicht erforderlich. Ein besser lesbarer Operator ==kann ebenfalls verwendet werden.

if [[ $1 == 1 ]];

Hinzugefügt Anmerkung: Wenn zweiter Operand auch Variablen enthält, unter Angabe ist notwendig , da es je nach Musteranpassung sein kann , wenn es erkennbar Zeichen enthält wie *, ?, [], etc .. Wenn die erweiterte Globbing oder Pattern - Matching aktiviert mit shopt -s extglob, anderen Formen wie @(), !()usw. wird auch als Muster erkannt. Siehe Mustervergleich .

Bei Operatoren wie <und >kann es dennoch notwendig sein, da ich einmal auf einen Fehler gestoßen war, bei dem das Nichtzitieren des zweiten Arguments zu unterschiedlichen Ergebnissen führte.

Für den ersten Operanden gilt nichts.

Betrachten Sie auch diese einfachere Variante:

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

Oder verdichtet:

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

"${@:2}"ist eine Form der Teilstringerweiterung oder Arrayelementerweiterung, wobei 2der Versatz ist. Dadurch beginnt die Erweiterung beim zweiten Wert. Damit müssen wir möglicherweise nicht verwenden shift.

Das hinzugefügte --verhindert, mvdass Dateinamen, die mit dash ( -) beginnen, als ungültige Optionen erkannt werden .


solltest du nicht in jedem fall brechen?
Archemar

2
@Archemar: Nein, es gibt keinen Durchfall (im Gegensatz zu vielen anderen Sprachen).
Mat

2
@Mat, es gibt einen Durchfall, wenn Sie ;&anstelle von ;;(nur ksh, bash, zsh) verwenden. Aber dann breakverhindert das Durchfallen immer noch nicht, sondern breaknur aus Schleifen auszubrechen.
Stéphane Chazelas

Das sind keine Argumente, ifsondern der [Befehl.
Stéphane Chazelas

1
Korrektur: Doppelte Anführungszeichen werden nur auf der linken Seite nicht benötigt! [[ $foo == $bar ]]führt einen Mustervergleich durch, jedoch [[ $foo == "$bar" ]]nicht.
Nyuszika7h

7

Zur Beantwortung der Frage, warum dies geschieht, dieses Verhalten von [aka testwird in POSIX dokumentiert :

In der folgenden Liste stehen $ 1, $ 2, $ 3 und $ 4 für die zu testenden Argumente:

[...]

1 Argument:

Beenden Sie true (0), wenn $ 1 nicht null ist. Andernfalls beenden Sie false.

Sie übergeben 1 Argument, 2=1das nicht null ist und daher testmit Erfolg beendet wird.

Wie andere Stellen (und shellcheck ) weisen darauf hin, wenn Sie auf Gleichheit vergleichen wollten, würden Sie stattdessen die drei Argumente übergeben müssen 2, =und 1.


3
In gewissem Sinne ist dies die einzige Antwort, die die gestellte Frage beantwortet hat (anstatt ein oder mehrere Rezepte zu geben, die das tun, was das OP erreichen wollte), und Shell kann hinreichend mysteriös sein, um zu wissen, warum es die Dinge tut, die es tut, ist nützlich .
dmckee --- Ex-Moderator Kätzchen

1

Ich möchte nur eine tragbare, aber auch ordentlichere Alternative empfehlen. Bash ist nicht universell (und wenn Sie kein universelles benötigen, warum schreiben Sie ein Shell-Skript?)

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

(Hinweis für Pedanten: Ja, ich weiß, dass die Anführungszeichen $actionin der case "$action" inZeile unnötig sind, aber ich denke, es ist am besten, sie trotzdem dort zu platzieren, damit zukünftige Leser sich nicht daran erinnern müssen.)


1
"Bash ist nicht universell (und wenn Sie kein universelles benötigen, warum schreiben Sie ein Shell-Skript?)" - dies scheint zu implizieren, dass Bash-Skripte keine Anwendungsfälle haben.
Ruslan

@ Ruslan Ja, das ist meine überlegte Meinung. Schreiben /bin/shSie bei Bedarf tragbare Skripte. Andernfalls schreiben Sie in einer Skriptsprache, die weniger schrecklich als Shell ist. Der grundlegende Perl-Interpreter ist in älteren proprietären Umgebungen und reduzierten eingebetteten Umgebungen mit größerer Wahrscheinlichkeit vorhanden als Bash.
zwol
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.