Was bedeutet "test $ 2 &&" in diesem Bash-Skript?

8

Ich sehe mir ein Bash-Skript mit folgendem Code an:

#!/bin/sh

set -e # Exit if any command fails

# If multiple args given run this script once for each arg
test $2 && {
  for arg in $@
    do $0 $arg
  done
  exit
}
.
.
.

Wie im Kommentar erwähnt, besteht der Zweck darin, "das Skript für jedes Argument auszuführen, wenn es mehr als ein Argument gibt". Ich habe einen Runner-Code, um dies zu testen:

# install-unit
# If multiple args given run this script once for each arg
test $2 && {
  echo "\$0: $0"
  echo "\$1: $1"
  echo "\$2: $2"
  echo "test \$2 && is true"
}

test $2 && {
# note this is just a regular bash for loop
# http://goo.gl/ZHpBvT
  for arg in $@
    do $0 $arg
  done
  exit
}

echo "running: $1"

was Folgendes ergibt:

sh  ./multiple-run.sh cats and dogs and stuff
$0: ./multiple-run.sh
$1: cats
$2: and
test $2 && is true
running: cats
running: and
running: dogs
running: and
running: stuff
sh  ./multiple-run.sh cats
running: cats

Ich möchte, dass jemand das test $2 && {}Teil und die exiteingebaute Shell auspackt. Am Anfang dachte ich, dass das test $2prüft, ob es mehr Argumente gibt, und vielleicht ist es das, aber es sieht seltsam aus, weil es so aussieht, als gäbe es zwei Ausdrücke getrennt durch das "und" &&, und ich bin auch nur verwirrt, was zum Teufel das exiteingebaute tut.

Einfache englische Erklärungen mit Beispielen und Dokumentationsreferenzen sind willkommen :)

Vielen Dank!

Edit: Danke Stéphane Chazelas dafür. Für alle, die durch die Änderung der for-Schleifensyntax verwirrt sind, lesen Sie Nummer 3 in diesem Geekstuff- Artikel. Zusammenfassen:

echo "positional parameters"
for arg do
  echo "$arg"
done

echo "\nin keyword"
for arg in "$@"
  do echo "$arg"
done

gibt uns:

 ./for-loop-positional.sh cats and dogs          
positional parameters
cats
and
dogs

in keyword
cats
and
dogs
mbigras
quelle
1
Siehe Was ist der Zweck des Schlüsselworts "do" in Bash for-Schleifen? über die for arg doSyntax.
Stéphane Chazelas

Antworten:

14

Das ist eine falsche Schreibweise:

#!/bin/sh -

set -e # Exit if any command fails

# If multiple args given run this script once for each arg
if [ "$#" -gt 1 ]; then
  for arg do
    "$0" "$arg"
  done
  exit
fi

Ich denke, der Autor wollte überprüfen, ob das zweite Argument eine nicht leere Zeichenfolge war (was nicht mit der Überprüfung identisch ist, ob mehr als ein Argument vorhanden ist, da das zweite Argument übergeben werden könnte, sondern die leere Zeichenfolge ist). .

test somestring

Eine Kurzform von

test -n somestring

Gibt true zurück, wenn somestringes sich nicht um die leere Zeichenfolge handelt. ( test ''gibt false zurück, test anythingelsegibt true zurück (aber Vorsicht test -tbei einigen Shells, die prüfen, ob stdout stattdessen ein Terminal ist)).

Der Autor hat jedoch die Anführungszeichen um die Variable vergessen (tatsächlich für alle Variablen). Dies bedeutet, dass der Inhalt von $2dem Operator split + glob unterliegt. Also , wenn $2enthält Zeichen $IFS(Leerzeichen, Tabulator und Newline durch Standard) oder glob Zeichen ( *, ?, [...]), das wird nicht funktionieren.

Und wenn $2leer ist (wie wenn weniger als 2 Argumente übergeben werden oder das zweite Argument leer ist), test $2wird testnicht test ''. testerhält überhaupt kein Argument (leer oder anderweitig).

Zum Glück wird in diesem Fall testohne Argumente false zurückgegeben. Es ist etwas besser als test -n $2das, was stattdessen true zurückgegeben hätte (da dies test -ngenauso wäre wie test -n -n), so dass Code in einigen Fällen zu funktionieren scheint.

Um zusammenzufassen:

  • um zu testen, ob 2 oder mehr Argumente übergeben werden:

    [ "$#" -gt 1 ]

    oder

    [ "$#" -ge 2 ]
  • um zu testen, ob eine Variable nicht leer ist:

    [ -n "$var" ]
    [ "$var" != '' ]
    [ "$var" ]

    All dies ist zuverlässig in POSIX-Implementierungen von [, aber wenn Sie mit sehr alten Systemen arbeiten müssen, müssen Sie möglicherweise verwenden

    [ '' != "$var" ]

    stattdessen für Implementierungen von [dieser Drossel auf Werte $varwie =, -t, (...

  • um zu testen, ob eine Variable definiert ist (dies könnte verwendet werden, um zu testen, ob dem Skript ein zweites Argument übergeben wird, aber die Verwendung $#ist viel einfacher zu lesen und idiomatischer):

    [ "${var+defined}" = defined ]

(oder das Äquivalent zum testFormular. Die Verwendung des [Alias ​​für den testBefehl ist üblicher).


Nun zum Unterschied zwischen cmd1 && cmd2und if cmd1; then cmd2; fi.

Beide werden cmd2nur ausgeführt, wenn sie cmd1erfolgreich sind. Der Unterschied in diesem Fall besteht darin, dass der Exit-Status der gesamten Befehlsliste der des letzten Befehls ist, der in dem &&Fall ausgeführt wird (also ein Fehlercode , wenn cmd1nicht true zurückgegeben wird (obwohl dies hier nicht ausgelöst wird set -e)), während er sich in befindet In diesem ifFall ist dies der Wert von cmd2oder 0 (Erfolg), wenn er cmd2nicht ausgeführt wird.

In Fällen, in denen cmd1eine Bedingung verwendet werden soll (wenn der Fehler nicht als Problem anzusehen ist ), ist die Verwendung im Allgemeinen besser, ifinsbesondere wenn dies das letzte ist, was Sie in einem Skript tun, da dies den Beendigungsstatus Ihres Skripts definiert. Es sorgt auch für besser lesbaren Code.

Die cmd1 && cmd2Form wird häufiger als Bedingungen selbst verwendet, wie in:

if cmd1 && cmd2; then...
while cmd1 && cmd2; do...

Dies ist in Kontexten der Fall, in denen wir uns um den Exit-Status dieser beiden Befehle kümmern.

Stéphane Chazelas
quelle
5

test $2 && some_command besteht aus zwei Befehlen:

  • test $2prüft, ob der String nach der Erweiterung des zweiten Arguments ( $2) eine Länge ungleich Null hat, dh er prüft notwendigerweise test -n $2( [ -n $2 ]). Beachten Sie, dass, da Sie keine Anführungszeichen verwendet haben $2, testWerte mit Leerzeichen erstickt werden. Sie sollten test "$2"hier verwenden.

  • &&ist ein Kurzschlussauswertungsoperator, der angibt, dass der Befehl after &&nur ausgeführt wird, wenn der Befehl vor seinem Erfolg erfolgreich ist, dh den Exit-Code 0 hat. Im obigen Beispiel some_commandwird er also nur ausgeführt, wenn er test $2erfolgreich ist, dh wenn die Zeichenfolge nicht ist -zero Länge, some_commandwird dann ausgeführt.

heemayl
quelle
1
test "$2"ist immer noch nicht sicher, ob $ 2 ist -noder so. Auch Stéphanes Antwort ist richtig, dass dies in erster Linie nur eine schlechte Methode ist. (zB foo arg1 '' arg3wird es täuschen zu denken, dass es nur ein Argument gibt.)
Peter Cordes
2
@PeterCordes, beachten Sie, dass beide test "$2"und test -n "$2"in POSIX-kompatiblen Implementierungen von zuverlässig sind test. Bei nicht konformen tests treten bei beiden Probleme auf (wie einige mit test -nund einige mit test -n =, die einen Fehler zurückgeben würden). In der Vergangenheit test -tsollte (und dokumentiert) getestet werden, ob stdout ein Terminal ist, daher test "$2"konnte es nicht zuverlässig sein. POSIX hat das geändert. ksh93 test -ttestet immer noch, ob stdout ein Terminal ist (für Rückwärtsportabilität), aber nur, wenn die testund -tlitteral sind (nicht in test "$2"oder cmd=test; "$cmd" -toder test $empty -t).
Stéphane Chazelas