Wie man über Argumente in einem Bash-Skript iteriert


Answers

VonC einer jetzt gelöschten answer von VonC .

Robert Gamble's knappe Antwort geht direkt auf die Frage ein. Dies verstärkt bei einigen Problemen mit Dateinamen, die Leerzeichen enthalten.

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

Grundthese: "$@" ist korrekt, und $* (nicht aufgelistet) ist fast immer falsch. Dies liegt daran, dass "$@" funktioniert, wenn Argumente Leerzeichen enthalten, und funktioniert genauso wie $* wenn dies nicht der Fall ist. Unter manchen Umständen ist "$*" in Ordnung, aber "$@" funktioniert normalerweise (aber nicht immer) an denselben Stellen. Unquoted, $@ und $* sind äquivalent (und fast immer falsch).

Also, was ist der Unterschied zwischen $* , $@ , "$*" und "$@" ? Sie sind alle verwandt mit "allen Argumenten für die Shell", aber sie machen verschiedene Dinge. Wenn nicht angegeben, machen $* und $@ dasselbe. Sie behandeln jedes "Wort" (Nicht-Whitespace) als separates Argument. Die in Anführungszeichen gesetzten Formen sind jedoch ziemlich unterschiedlich: "$*" behandelt die Argumentliste als eine durch Leerzeichen getrennte Zeichenfolge, während "$@" die Argumente fast genau so behandelt, wie sie in der Befehlszeile angegeben waren. "$@" expandiert zu nichts, wenn keine Positionsargumente vorhanden sind; "$*" zu einem leeren String erweitert - und ja, es gibt einen Unterschied, obwohl es schwer sein kann, es wahrzunehmen. Weitere Informationen finden Sie weiter unten, nach der Einführung des (nicht standardmäßigen) Befehls al .

Sekundäre These: Wenn Sie Argumente mit Leerzeichen verarbeiten und sie dann an andere Befehle weitergeben müssen, benötigen Sie manchmal nicht standardmäßige Tools. (Oder Sie sollten Arrays vorsichtig verwenden: "${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 funktioniert das nicht? Es funktioniert nicht, da die Shell Angebote verarbeitet, bevor sie Variablen erweitert. Um also die Shell auf die in $var eingebetteten Anführungszeichen aufmerksam zu machen, 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
    $ 

Dies wird sehr schwierig, wenn Sie Dateinamen wie " He said, "Don't do this!" " (Mit Anführungszeichen und 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 von ihnen) machen es nicht besonders einfach, solche Sachen zu handhaben, also (lustigerweise) machen viele Unix-Programme keine gute Arbeit, sie zu handhaben. Unter Unix kann ein Dateiname (einzelne Komponente) beliebige Zeichen außer Slash und NUL '\0' . Die Shells unterstützen jedoch keine Leerzeichen oder Zeilenumbrüche oder Tabulatoren irgendwo in einem Pfadnamen. Aus diesem Grund enthalten Standard-Unix-Dateinamen keine Leerzeichen usw.

Wenn Sie mit Dateinamen arbeiten, die Leerzeichen und andere problematische Zeichen enthalten können, müssen Sie sehr vorsichtig sein, und ich habe vor langer Zeit festgestellt, dass ich ein Programm brauche, das nicht standardmäßig auf Unix läuft. Ich nenne es escape (Version 1.1 wurde 1989-08-23T16: 01: 45Z datiert).

Hier ist ein Beispiel für eine escape in Verwendung - mit dem SCCS-Kontrollsystem. Es ist ein Cover-Skript, das sowohl ein delta (Think Check-In ) als auch ein get (Think Check-Out ) durchführt. Verschiedene Argumente, insbesondere -y (der Grund, warum Sie die Änderung vorgenommen haben) enthalten Leerzeichen und Zeilenumbrüche. Beachten Sie, dass das Skript aus dem Jahr 1992 stammt, also Back-Ticks anstelle von $(cmd ...) Notation verwendet und #!/bin/sh in der ersten Zeile verwendet.

:   "@(#)$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"

(Ich würde die Flucht in diesen Tagen wahrscheinlich nicht so gründlich benutzen - es wird zum Beispiel nicht mit dem Argument -e benötigt - aber insgesamt ist dies eines meiner einfacheren Skripte, die escape .)

Das escape Programm gibt einfach seine Argumente aus, ähnlich wie echo , aber es stellt sicher, dass die Argumente für die Verwendung mit eval geschützt sind (eine Stufe von eval ; ich habe ein Programm, das die Shell-Ausführung ausführte) 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 , das seine Argumente eins pro Zeile auflistet (und es ist noch älter: Version 1.1 vom 1987-01-27T14: 35: 49). Es ist am nützlichsten beim Debuggen von Skripten, da es in eine Befehlszeile eingesteckt 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: Und jetzt, um den Unterschied zwischen den verschiedenen "$@" Notationen zu zeigen, 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 nichts die ursprünglichen Leerzeichen zwischen dem * und */* in der Befehlszeile beibehält. Beachten Sie außerdem, dass Sie die 'Befehlszeilenargumente' in der Shell ändern können, indem Sie Folgendes verwenden:

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

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

Hmm, das ist eine ziemlich lange Antwort - vielleicht ist Exegese der bessere Ausdruck. Quellcode für escape ist auf Anfrage verfügbar (E-Mail an Vorname Punkt Nachname bei Gmail Dot com). Der Quellcode für al ist unglaublich einfach:

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

Das ist alles. Es entspricht dem Skript test.sh , das Robert Gamble gezeigt hat, und könnte als Shell-Funktion geschrieben werden (aber Shell-Funktionen existierten in der lokalen Version der Bourne-Shell nicht, als ich zuerst al ).

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

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

Die Bedingung wird benötigt, damit sie keine Ausgabe erzeugt, wenn keine Argumente übergeben werden. Der printf Befehl erzeugt eine leere Zeile mit nur dem Formatzeichenfolgenargument, aber das C-Programm erzeugt nichts.

Question

Ich habe einen komplexen Befehl, von dem ich gerne ein Shell / Bash-Skript machen würde. Ich kann es leicht in $1 schreiben:

foo $1 args -o $1.ext

Ich möchte in der Lage sein, mehrere Eingabenamen an das Skript zu übergeben. Was ist der richtige Weg?

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




aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "$@"



Für einfache Fälle können Sie auch shift . Es behandelt die Argumentliste wie eine Warteschlange, jede shift löst das erste Argument aus, die Anzahl der verbleibenden Argumente wird dekrementiert.

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