Cum pentru a itera peste argumentele într-un script Bash

Am un complex de comandă pe care am'd dori să facă un shell/bash script de. Nu pot scrie în termeni de $1 cu ușurință:

foo $1 args -o $1.ext

Vreau să fie în măsură să treacă mai multe de intrare numele de script-ul. Ce's modul corect de a face asta?

Și, desigur, vreau să se ocupe de nume de fișiere cu spații în ele.

Comentarii la întrebare (1)
Soluția

Folosi"$@"` pentru a reprezenta toate argumentele:

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

Acest lucru se va repeta peste fiecare argument și de imprimare-l pe o linie separată. $@ se comportă ca $* cu excepția faptului că atunci când a citat argumentele sunt rupt în sus, în mod corespunzător, dacă există spații în ele:

sh test.sh 1 2 '3 4'
1
2
3 4
Comentarii (7)

Rewrite de-a șters răspunde de VonC. Robert Gamble's succint răspuns oferte direct cu întrebarea. Aceasta amplifică pe unele probleme cu nume de fișiere care conțin spații. Vezi de asemenea și: ${1:+"$@"} in /bin/sh De bază a tezei de doctorat: "$@" este corectă, și$(necotate) este aproape întotdeauna greșit. Acest lucru este pentru că"$@"funcționează bine atunci când argumente contine spatii, și funcționează la fel ca$atunci când acestea nu't. În unele circumstanțe,"$" este OK, dar "$@", de obicei (dar nu întotdeauna) funcționează în aceleași locuri. Necotate, $@ și `$sunt echivalente (și aproape întotdeauna greșit). Deci, ce este diferența dintre$,$@,"$" și"$@"? Toate acestea sunt legate de 'toate argumentele la shell', dar ei fac lucruri diferite. Când necotate,$și$@face același lucru. Tratează fiecare 'cuvantul' (secvență de non-spațiu) ca un alt argument. Citat forme sunt destul de diferite, deși:"$"tratează argumentul lista ca un singur spațiu șir separat, întrucât"$@"tratează argumentele aproape exact cum erau atunci când se specifică în linia de comandă. "$@"se extinde la nimic, la toate, atunci când nu există argumente poziționale;"$"se extinde la un șir gol — și da, acolo's o diferență, deși poate fi greu să-l perceapă. Vezi mai multe informații de mai jos, după introducerea (non-standard) de comandă "al". **Teză secundară:** dacă aveți nevoie pentru a procesa argumente cu spații și apoi trece-le pe la alte comenzi, atunci ai nevoie, uneori, non-standard instrumente pentru a ajuta. (Sau ar trebui să utilizați tablouri, cu atenție:"${array[@]}"se comportă în mod analog cu"$@"`.) Exemplu:*

    $ 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
    $

De ce nu - 't asta? Nu't lucra pentru shell procese citate înainte de a se extinde variabile. Deci, pentru a obține shell să acorde o atenție la citate încorporate în $var, trebuie să utilizați "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
    $ 

Acest lucru devine foarte dificil atunci când aveți nume de fișiere, cum ar fi " a spus El, "Nu't face acest lucru!"`" (cu citate și ghilimele duble și spații).

    $ 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
    $ 

Scoici (tot de ei) nu-l face deosebit de ușor să se ocupe de astfel de chestii, deci (destul de ciudat) multe programe Unix nu face o treabă bună de manipularea lor. Pe Unix, un nume de fisier (o singură componentă) poate conține orice caractere, cu excepția slash și NUL'\0'. Cu toate acestea, scoici încurajăm fără spații sau linii noi sau file oriunde într-o cale de nume. Este, de asemenea, de ce standard Unix nume de fișiere nu conțin spații, etc. Atunci când se ocupă cu nume de fișiere care pot conține spații și alte supărătoare caractere, trebuie să fie extrem de atent, și am găsit cu mult timp în urmă că am nevoie de un program care nu este standard pe Unix. Eu numesc aceasta "evadare" (versiunea 1.1 a fost datat 1989-08-23T16:01:45Z). Aici este un exemplu de "evadare" în uz - cu CSSC sistem de control. Este un capac script care nu atât o "delta" (cred check-in) și de o "a lua" (cred check-out). Diverse argumente, mai ales -y (motivul pentru care ai luat schimba) ar conține spații și linii noi. Rețineți că script-ul datează din 1992, asa ca foloseste back-căpușe în loc de $(cmd ...) notație și să nu utilizeze #!/bin/sh pe prima linie.

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

(Probabil că nu aș folosi de evacuare destul de atât de bine în aceste zile - este nu e nevoie de cu "- e " argument, de exemplu - dar, în general, acest lucru este unul dintre mai simplu script-uri folosind "evadare".) Escape program pur și simplu ieșiri argumentele sale, mai degrabă ca "echo" nu, dar se asigură că argumentele sunt protejate pentru utilizarea cu "eval" (un nivel de "eval"; eu nu am un program care a făcut remote shell de execuție, și care trebuia să scape de ieșire de "evadare").

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

Am un alt program numit "al", care enumeră argumentele sale câte unul pe linie (și este chiar mai vechi: versiunea 1.1 datat 1987-01-27T14:35:49). Este cel mai util atunci când depanare scripturi, ca acesta poate fi conectat la un linie de comandă pentru a vedea ce argumente sunt, de fapt, a trecut la comanda.

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

[Adăugat: Și acum, pentru a arăta diferența între diferitele "$@" notații, aici este un exemplu mai mult:

$ 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
$

Observați că nimic nu se păstrează în original spații între * " și " * / * pe linia de comandă. De asemenea, rețineți că puteți schimba 'argumente în linia de comandă' în coajă cu ajutorul:

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

Aceasta stabilește 4 opțiuni, '-nou', '-opta', 'și', iar 'arg, cu spațiu'.
] Hmm, asta's destul de o lungă perioadă de răspunde - poate exegeza este mai bine pe termen lung. Codul sursă pentru "evadare", disponibile la cerere (e-mail prenume dot lastname la gmail dot com). Codul sursă pentru " al " este incredibil de simplu:

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

Ca's toate. Este echivalent cu test.sh script care Robert Gamble a arătat, și ar putea fi scrise ca o coajă funcția (dar shell funcții n't există în versiunea locală a Bourne shell când am scris primul "al"). De asemenea, rețineți că puteți scrie al ca un simplu script de shell:

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

Condiția este necesară, astfel încât se produce nici o ieșire atunci când a trecut nici un argument. La "printf" comanda va produce o linie goală, doar cu șir format argument, dar program C nu produce nimic.

Comentarii (1)

Rețineți că Robert's răspunsul este corect, și funcționează în " sh " la fel de bine. Puteți (portably) simplifica și mai mult:

for i in "$@"

este echivalent cu:

for i

I. e., nu't nevoie de nimic!

Testarea ($ este prompt de comandă):

$ 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

Am citit prima dată despre acest lucru în Unix Mediu de Programare de Kernighan si Stiuca.

În bash, ajutor documente de acest lucru:

pentru NUMELE [în CUVINTE ... ;] face COMENZI; face`

Dacă 'in CUVINTE ...;' nu este prezent, atunci 'în "$@"' este presupus.

Comentarii (10)

Pentru cazuri simple, puteți utiliza, de asemenea, o "schimbare". Tratează argumentul lista ca o coadă. Fiecare "schimbare" aruncă primul argument și indicele de fiecare dintre celelalte argumente este decrementat.

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

Puteți accesa, de asemenea, le ca o matrice de elemente, de exemplu, dacă nu't vreau pentru a itera prin toate


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

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

aparse "$@"
Comentarii (1)