1158 Stimmen

Wie man über Argumente in einem Bash-Skript iteriert

Ich habe einen komplexen Befehl, aus dem ich ein Shell/Bash-Skript erstellen möchte. Ich kann es schreiben in Form von $1 leicht:

foo $1 args -o $1.ext

Ich möchte mehrere Eingaben an das Skript übergeben können. Was ist der richtige Weg, dies zu tun?

Und natürlich möchte ich Dateinamen mit Leerzeichen darin behandeln.

1919voto

Robert Gamble Punkte 101657

Utilice "$@" um alle Argumente darzustellen:

for var in "$@"
do
    echo "$var"
done

Damit wird jedes Argument durchlaufen und in einer eigenen Zeile ausgedruckt. $@ verhält sich wie $*, mit dem Unterschied, dass die Argumente bei Anführungszeichen richtig aufgeteilt werden, wenn sie Leerzeichen enthalten:

sh test.sh 1 2 '3 4'
1
2
3 4

276voto

Jonathan Leffler Punkte 694013

Neufassung eines jetzt gelöschten respuesta por VonC .

Robert Gamble Die lapidare Antwort des Autors geht direkt auf diese Frage ein. In dieser Antwort wird auf einige Probleme mit Dateinamen, die Leerzeichen enthalten, eingegangen.

Siehe auch: ${1:+"$@"} in /bin/sh

Grundlegende These: "$@" korrekt ist, und $* (nicht zitiert) ist fast immer falsch. Der Grund dafür ist "$@" funktioniert einwandfrei, wenn die Argumente Leerzeichen enthalten, und funktioniert genauso wie $* wenn sie es nicht tun. Unter bestimmten Umständen, "$*" ist auch in Ordnung, aber "$@" normalerweise (aber nicht immer) an denselben Orten. Nicht zitiert, $@ y $* sind gleichwertig (und fast immer falsch).

Was ist also der Unterschied zwischen $* , $@ , "$*" und "$@" ? Sie beziehen sich alle auf "alle Argumente für die Shell", aber sie tun unterschiedliche Dinge. Wenn unquotiert, $* y $@ das Gleiche tun. Sie behandeln jedes "Wort" (Folge von Nicht-Leerzeichen) als separates Argument. Die Anführungszeichen sind jedoch recht unterschiedlich: "$*" behandelt die Argumentliste als eine einzige durch Leerzeichen getrennte Zeichenkette, während "$@" behandelt die Argumente fast genau so, wie sie in der Befehlszeile angegeben wurden. "$@" expandiert zu nichts, wenn es keine Positionsargumente gibt; "$*" zu einer leeren Zeichenkette expandiert - und ja, es gibt einen Unterschied, auch wenn er manchmal schwer zu erkennen ist. Weitere Informationen finden Sie unten, nach der Einführung des (nicht standardisierten) Befehls al .

Sekundäre These: wenn Sie Argumente mit Leerzeichen verarbeiten müssen und dann an andere Befehle weitergeben müssen, dann brauchen Sie manchmal nicht standardisierte Werkzeuge zur Unterstützung. (Oder Sie sollten Arrays verwenden, und zwar vorsichtig: "${array[@]}" verhält sich analog zu "$@" .)

Beispiel:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

Warum klappt das nicht? Es funktioniert nicht, weil die Shell Anführungszeichen verarbeitet, bevor sie die Variablen expandiert. Um die Shell dazu zu bringen, die Anführungszeichen zu beachten, die in $var , müssen Sie eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

Richtig knifflig wird es, wenn Sie Dateinamen haben wie " He said, "Don't do this!" "(mit Anführungszeichen, doppelten Anführungszeichen und Leerzeichen).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

Die Shells (alle) machen es nicht besonders einfach, mit solchen nicht besonders einfach, so dass (lustigerweise) viele Unix-Programme keine gute Arbeit leisten, um sie zu behandeln. Unter Unix kann ein Dateiname (eine einzelne Komponente) alle Zeichen enthalten außer Schrägstrich und NUL '\0' . Die Shells empfehlen jedoch dringend, keine Leerzeichen, Zeilenumbrüche oder Tabulatoren in einem Pfadnamen zu vermeiden. Das ist auch der Grund, warum die Standard-Unix-Dateinamen keine Leerzeichen usw. enthalten.

Beim Umgang mit Dateinamen, die Leerzeichen und andere Zeichen enthalten können müssen Sie extrem vorsichtig sein, und ich habe vor langer Zeit herausgefunden Ich habe vor langer Zeit festgestellt, dass ich ein Programm brauche, das unter Unix nicht zum Standard gehört. Ich nenne es escape (Version 1.1 war datiert auf 1989-08-23T16:01:45Z).

Hier ist ein Beispiel für escape im Einsatz - mit dem SCCS-Kontrollsystem. Es handelt sich um ein Cover-Skript, das sowohl eine delta (denken Einchecken ) und eine get (denken Check-out ). Verschiedene Argumente, insbesondere -y (der Grund, warum Sie die Änderung vorgenommen haben) würde Leerzeichen und Zeilenumbrüche enthalten. Beachten Sie, dass das Skript aus dem Jahr 1992 stammt und daher Back-Ticks anstelle von $(cmd ...) Notation und verwendet nicht #!/bin/sh in der ersten Zeile.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "$@"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(Heutzutage würde ich escape wahrscheinlich nicht mehr ganz so gründlich verwenden - es ist nicht benötigt mit dem -e Argument, zum Beispiel - aber insgesamt ist das eines meiner einfacheren Skripte mit escape .)

Le site escape Programm gibt einfach seine Argumente aus, ähnlich wie echo tut, aber es stellt sicher, dass die Argumente für die Verwendung mit eval (eine Ebene von eval Ich habe ein Programm, das eine entfernte Shell ausführt, und das die Ausgabe von escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

Ich habe ein anderes Programm namens al die ihre Argumente einzeln pro Zeile auflistet (und es ist sogar noch älter: Version 1.1 vom 1987-01-27T14:35:49). Es ist sehr nützlich beim Debuggen von Skripten, da es in eine Befehlszeile eingefügt werden kann, um zu sehen, welche Argumente tatsächlich an den Befehl übergeben werden.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ Hinzugefügt: Um nun den Unterschied zwischen den verschiedenen "$@" Notationen, hier ist ein weiteres Beispiel:

$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

Beachten Sie, dass die ursprünglichen Leerzeichen zwischen den Zeilen nicht erhalten bleiben. * y */* in der Befehlszeile. Beachten Sie auch, dass Sie die "Befehlszeilenargumente" in der Shell ändern können, indem Sie:

set -- -new -opt and "arg with space"

Dies setzt 4 Optionen, ' -new ', ' -opt ', ' and ', und ' arg with space '.
]

Hmm, das ist eine ziemlich lange respuesta - vielleicht Exegese ist der bessere Begriff. Quellcode für escape auf Anfrage erhältlich (E-Mail an vorname punkt nachname at gmail dot com). Der Quellcode für al ist denkbar einfach:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

Das war's schon. Es ist gleichbedeutend mit dem test.sh Skript, das Robert Gamble gezeigt hat, und könnte als Shell-Funktion geschrieben werden (aber Shell-Funktionen gab es in der lokalen Version der Bourne-Shell noch nicht, als ich das erste Mal schrieb al ).

Beachten Sie auch, dass Sie schreiben können al als einfaches Shell-Skript:

[ $# != 0 ] && printf "%s\n" "$@"

Die Bedingung wird benötigt, damit es keine Ausgabe erzeugt, wenn keine Argumente übergeben werden. Die printf erzeugt eine leere Zeile nur mit dem Argument format string, aber das C-Programm erzeugt nichts.

157voto

Alok Singhal Punkte 87419

Beachten Sie, dass die Antwort von Robert richtig ist und in sh auch. Sie können es (portabel) noch weiter vereinfachen:

for i in "$@"

ist gleichbedeutend mit:

for i

Das heißt, Sie brauchen nichts!

Prüfung ( $ ist die Eingabeaufforderung):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d

Ich habe zum ersten Mal darüber gelesen in Unix-Programmierumgebung von Kernighan und Pike.

Unter bash , help for dokumentiert dies:

for NAME [in WORDS ... ;] do COMMANDS; done

Si 'in WORDS ...;' nicht vorhanden ist, dann 'in "$@"' angenommen wird.

88voto

nuoritoveri Punkte 2306

Für einfache Fälle können Sie auch shift . Es behandelt die Argumentliste wie eine Warteschlange. Jede shift wirft das erste Argument weg und die Index jedes der verbleibenden Argumente wird dekrementiert.

#this prints all arguments
while test $# -gt 0
do
    echo "$1"
    shift
done

30voto

baz Punkte 1049

Sie können auch auf sie als Array-Elemente zugreifen, wenn Sie zum Beispiel nicht alle Elemente durchgehen wollen

argc=$#
argv=("$@")

for (( j=0; j<argc; j++ )); do
    echo "${argv[j]}"
done

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X