Przepisz teraz usuniętą odpowiedź przez VonC .
Zwięzła odpowiedź Roberta Gamble'a dotyczy bezpośrednio pytania. Ten wzmacnia niektóre problemy z nazwami plików zawierającymi spacje.
Zobacz także: $ {1: + "$ @"} w / bin / sh
Podstawowa teza: "$@"
jest poprawna, a $*
(nie cytowana) prawie zawsze jest błędna. Jest tak, ponieważ "$@"
działa dobrze, gdy argumenty zawierają spacje, i działa tak samo, jak $*
gdy nie. W niektórych okolicznościach "$*"
jest również OK, ale "$@"
zwykle (ale nie zawsze) działa w tych samych miejscach. Nienotowane $@
i $*
są równoważne (i prawie zawsze błędne).
Więc jaka jest różnica między $*
, $@
, "$*"
, i "$@"
? Wszystkie są powiązane z „wszystkimi argumentami powłoki”, ale robią różne rzeczy. Gdy nie jest cytowany $*
i $@
rób to samo. Traktują każde „słowo” (sekwencję spacji) jako osobny argument. Cytowane formy są jednak zupełnie różne: "$*"
traktuje listę argumentów jako pojedynczy ciąg oddzielony spacjami, podczas "$@"
gdy argumenty traktuje prawie dokładnie tak, jak były podane w wierszu poleceń.
"$@"
rozwija się do zera, gdy nie ma argumentów pozycyjnych; "$*"
rozwija się do pustego ciągu - i tak, jest różnica, chociaż może być trudno ją dostrzec. Zobacz więcej informacji poniżej, po wprowadzeniu polecenia (niestandardowego) al
.
Teza dodatkowa: jeśli potrzebujesz przetworzyć argumenty ze spacjami, a następnie przekazać je innym poleceniom, czasem potrzebujesz niestandardowych narzędzi do pomocy. (Lub powinieneś używać tablic ostrożnie: "${array[@]}"
zachowuje się analogicznie do "$@"
.)
Przykład:
$ 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
$
Dlaczego to nie działa? Nie działa, ponieważ powłoka przetwarza cudzysłowy przed rozwinięciem zmiennych. Aby powłoka zwracała uwagę na osadzone w niej cytaty $var
, musisz użyć 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
$
To staje się bardzo trudne, gdy masz nazwy plików, takie jak „ He said,
"Don't do this!"
” (z cudzysłowami i podwójnymi cudzysłowami i spacjami).
$ 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
$
Powłoki (wszystkie) nie sprawiają, że szczególnie łatwo jest obsługiwać takie rzeczy, więc (co zabawne) wiele programów uniksowych nie radzi sobie z nimi dobrze. W systemie Unix nazwa pliku (pojedynczy składnik) może zawierać dowolne znaki oprócz ukośnika i wartości NUL '\0'
. Jednak powłoki zdecydowanie nie zachęcają do spacji, znaków nowej linii ani tabulatorów w nazwach ścieżek. Z tego powodu standardowe nazwy plików uniksowych nie zawierają spacji itp.
Kiedy masz do czynienia z nazwami plików, które mogą zawierać spacje i inne kłopotliwe znaki, musisz być bardzo ostrożny, a już dawno odkryłem, że potrzebuję programu, który nie jest standardem w Uniksie. Nazywam to escape
(wersja 1.1 została opatrzona datą 1989-08-23T16: 01: 45Z).
Oto przykład escape
zastosowania - z systemem sterowania SCCS. Jest to skrypt tytułowy, który wykonuje zarówno delta
(myśl check-in ), jak i
get
(myśl check-out ). Różne argumenty, zwłaszcza -y
(powód, dla którego dokonałeś zmiany) zawierałyby spacje i znaki nowej linii. Zauważ, że skrypt pochodzi z 1992 roku, więc używa $(cmd ...)
notacji zamiast
notacji i nie używa #!/bin/sh
w pierwszym wierszu.
: "@(#)$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"
(Prawdopodobnie w dzisiejszych czasach nie używałbym ucieczki aż tak dokładnie - nie jest to potrzebne -e
na przykład z argumentem - ale ogólnie jest to jeden z moich prostszych skryptów escape
.)
escape
Program po prostu wyprowadza swoje argumenty, a jak echo
nie, ale zapewnia, że argumenty są chronione do stosowania
eval
(jeden poziom eval
; mam program, który zrobił na zdalne wykonanie powłoki, i że potrzebne do ucieczki wyjście escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Mam inny program o nazwie, al
który wyświetla jego argumenty po jednym w wierszu (i jest jeszcze bardziej stary: wersja 1.1 z 1987-01-27T14: 35: 49). Jest to najbardziej przydatne podczas debugowania skryptów, ponieważ można je podłączyć do wiersza poleceń, aby zobaczyć, jakie argumenty są faktycznie przekazywane do polecenia.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Dodano:
A teraz, aby pokazać różnicę między różnymi "$@"
notacjami, oto jeszcze jeden przykład:
$ 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
$
Zauważ, że nic nie zachowuje oryginalnych spacji między *
i */*
w linii poleceń. Zauważ też, że możesz zmienić „argumenty wiersza poleceń” w powłoce, używając:
set -- -new -opt and "arg with space"
Ustawia 4 opcje „ -new
”, „ -opt
”, „ and
” i „ arg with space
”.
]
Hmm, to dość długa odpowiedź - być może egzegeza jest lepszym terminem. Kod źródłowy escape
dostępny na żądanie (e-mail do imienia i nazwiska kropka w gmail dot com). Kod źródłowy al
jest niezwykle prosty:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
To wszystko. Jest to odpowiednik test.sh
skryptu, który pokazał Robert Gamble, i może być napisane jako funkcja powłoki (ale funkcje powłoki nie istniały w lokalnej wersji powłoki Bourne'a, kiedy pisałem po raz pierwszy al
).
Pamiętaj też, że możesz napisać al
jako prosty skrypt powłoki:
[ $# != 0 ] && printf "%s\n" "$@"
Warunek jest potrzebny, aby nie generował danych wyjściowych po przekazaniu żadnych argumentów. printf
Komenda będzie produkować pusty wiersz tylko argumentu format string, ale program C produkuje niczego.