Riscrivi una risposta ora cancellata da VonC .
La risposta sintetica di Robert Gamble si occupa direttamente della domanda. Questo amplifica alcuni problemi con nomi di file contenenti spazi.
Vedi anche: $ {1: + "$ @"} in / bin / sh
Tesi di base: "$@"
è corretta e $*
(non quotata) è quasi sempre sbagliata. Questo perché "$@"
funziona bene quando gli argomenti contengono spazi e funziona come $*
quando non lo fanno. In alcune circostanze, "$*"
va bene lo stesso, ma di "$@"
solito (ma non sempre) funziona negli stessi posti. Non quotati $@
e $*
equivalenti (e quasi sempre sbagliati).
Quindi, qual è la differenza tra $*
, $@
, "$*"
, e "$@"
? Sono tutti collegati a "tutti gli argomenti della shell", ma fanno cose diverse. Quando non quotate, $*
e $@
fare la stessa cosa. Trattano ogni "parola" (sequenza di non spazi bianchi) come un argomento separato. Le forme tra virgolette sono piuttosto diverse, però: "$*"
tratta l'elenco degli argomenti come una singola stringa separata da spazi, mentre "$@"
tratta gli argomenti quasi esattamente come quando erano specificati sulla riga di comando.
"$@"
non si espande per niente quando non ci sono argomenti posizionali; "$*"
si espande in una stringa vuota - e sì, c'è una differenza, anche se può essere difficile percepirla. Vedi ulteriori informazioni di seguito, dopo l'introduzione del comando (non standard) al
.
Tesi secondaria: se hai bisogno di elaborare argomenti con spazi e poi passarli ad altri comandi, a volte hai bisogno di strumenti non standard per aiutarti. (O dovresti usare le matrici, con attenzione: "${array[@]}"
si comporta in modo analogo a "$@"
.)
Esempio:
$ 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
$
Perché non funziona? Non funziona perché la shell elabora le virgolette prima di espandere le variabili. Quindi, per fare in modo che la shell presti attenzione alle virgolette incorporate $var
, devi usare 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
$
Questo diventa davvero complicato quando hai nomi di file come " He said,
"Don't do this!"
" (con virgolette, virgolette doppie e spazi).
$ 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
$
Le shell (tutte) non rendono particolarmente facile gestire tali cose, quindi (abbastanza stranamente) molti programmi Unix non fanno un buon lavoro nel gestirli. Su Unix, un nome file (singolo componente) può contenere qualsiasi carattere tranne barra e NUL '\0'
. Tuttavia, le shell incoraggiano fortemente nessuno spazio o newline o tab ovunque nei nomi di un percorso. È anche il motivo per cui i nomi di file Unix standard non contengono spazi, ecc.
Quando si ha a che fare con nomi di file che possono contenere spazi e altri caratteri problematici, è necessario fare molta attenzione e molto tempo fa ho scoperto che avevo bisogno di un programma non standard su Unix. Lo chiamo escape
(la versione 1.1 era datata 1989-08-23T16: 01: 45Z).
Ecco un esempio di escape
in uso - con il sistema di controllo SCCS. È uno script di copertina che esegue sia un delta
(pensa al check-in ) sia un
get
(pensa al check-out ). Vari argomenti, in particolare -y
(il motivo per cui hai apportato la modifica) conterrebbero spazi vuoti e righe. Si noti che lo script risale al 1992, quindi utilizza back-tick anziché
$(cmd ...)
notazione e non utilizza #!/bin/sh
sulla prima riga.
: "@(#)$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"
(Probabilmente non userei escape così accuratamente in questi giorni - non è necessario con l' -e
argomento, ad esempio - ma nel complesso, questo è uno dei miei script più semplici usando escape
.)
Il escape
programma emette semplicemente i suoi argomenti, un po 'come echo
fa, ma assicura che gli argomenti siano protetti per l'uso con
eval
(un livello di eval
; ho un programma che ha eseguito l'esecuzione remota della shell e che doveva sfuggire all'output di escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Ho un altro programma chiamato al
che elenca i suoi argomenti uno per riga (ed è ancora più antico: versione 1.1 del 1987-01-27T14: 35: 49). È molto utile durante il debug degli script, in quanto può essere collegato a una riga di comando per vedere quali argomenti vengono effettivamente passati al comando.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Aggiunto:
e ora per mostrare la differenza tra le varie "$@"
notazioni, ecco un altro esempio:
$ 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
$
Notare che nulla conserva gli spazi vuoti originali tra *
e */*
sulla riga di comando. Inoltre, tieni presente che puoi modificare gli 'argomenti della riga di comando' nella shell usando:
set -- -new -opt and "arg with space"
Questo imposta 4 opzioni, ' -new
', ' -opt
', ' and
' e ' arg with space
'.
]
Hmm, questa è una risposta piuttosto lunga - forse l' esegesi è il termine migliore. Codice sorgente escape
disponibile su richiesta (e-mail al nome punto punto cognome su gmail punto com). Il codice sorgente per al
è incredibilmente semplice:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
È tutto. È equivalente allo test.sh
script mostrato da Robert Gamble e potrebbe essere scritto come una funzione di shell (ma le funzioni di shell non esistevano nella versione locale della shell Bourne quando ho scritto per la prima volta al
).
Si noti inoltre che è possibile scrivere al
come un semplice script shell:
[ $# != 0 ] && printf "%s\n" "$@"
Il condizionale è necessario in modo che non produca output se non viene passato alcun argomento. Il printf
comando produrrà una riga vuota con solo l'argomento stringa di formato, ma il programma C non produce nulla.