Turiu sudėtingą komandą, iš kurios norėčiau sukurti shell/bash scenarijų. Ją galiu lengvai užrašyti kaip $1
:
foo $1 args -o $1.ext
Noriu, kad scenarijui būtų galima perduoti kelis įvesties vardus. Kaip teisingai tai padaryti?
Ir, žinoma, noriu tvarkyti failų pavadinimus su tarpais.
Naudokite "$@"
visiems argumentams reikšti:
for var in "$@"
do
echo "$var"
done
Taip bus iteruojamas kiekvienas argumentas ir spausdinamas atskiroje eilutėje. $@ elgiasi kaip $*, išskyrus tai, kad cituojant argumentai tinkamai suskaidomi, jei juose yra tarpų:
sh test.sh 1 2 '3 4'
1
2
3 4
Perrašytas dabar jau ištrintas atsakymas pagal VonC.
Robert Gamble's glaustame atsakyme tiesiogiai nagrinėjamas klausimas.
Šiame atsakyme plačiau aptariami kai kurie klausimai, susiję su failų pavadinimais, kuriuose yra tarpų.
Taip pat žr: ${1:+"$@"} in /bin/sh
Pagrindinė tezė: "$@"
yra teisinga, o $*
(be kabučių) beveik visada klaidinga.
Taip yra todėl, kad "$@"
veikia gerai, kai argumentuose yra tarpai, o
veikia taip pat, kaip $*
, kai jų nėra.
Tam tikromis aplinkybėmis "$*"
taip pat tinka, bet "$@"
paprastai (bet ne
visada) veikia tose pačiose vietose.
Neįrašyti $@
ir $*
yra lygiaverčiai (ir beveik visada neteisingi).
Taigi, kuo skiriasi $*
, $@
, "$*"
ir "$@"
? Visi jie susiję su 'visais apvalkalo argumentais', tačiau atlieka skirtingus veiksmus. Kai $*
ir $@
nėra cituojami, jie atlieka tą patį. Jie kiekvieną 'žodį' (ne baltųjų ženklų seką) traktuoja kaip atskirą argumentą. Tačiau cituojamos formos yra visiškai skirtingos: "$*"
traktuoja argumentų sąrašą kaip vieną tarpeliais atskirtą eilutę, o "$@"
traktuoja argumentus beveik taip pat, kaip jie buvo nurodyti komandinėje eilutėje.
"$@"
išsiplečia į nieką, kai nėra pozicionuojamųjų argumentų; "$*"
išsiplečia į tuščią eilutę — ir taip, yra skirtumas, nors jį gali būti sunku pastebėti.
Daugiau informacijos rasite toliau, po (nestandartinės) komandos al
įvedimo.
Antroji tezė: jei reikia apdoroti argumentus su tarpais ir tada
juos perduoti kitoms komandoms, tuomet kartais reikia nestandartinių
pagalbos priemonių. (Arba atsargiai naudokite masyvus: "${Masyvas[@]}"
elgiasi analogiškai kaip "$@"
.)
Pavyzdys:
$ 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
$
Kodėl tai neveikia?
Neveikia, nes apvalkalas apdoroja kabutes prieš jas išplėsdamas
kintamuosius.
Taigi, kad apvalkalas atkreiptų dėmesį į kabutes, įterptas į $var
,
turite naudoti 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
$
Tai tampa labai sudėtinga, kai turite tokius failų pavadinimus kaip "Jis sakė, "Nedarykite to!"
" (su kabutėmis, dvigubomis kabutėmis ir tarpais).
$ 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
$
Kriauklės (visos) ne itin palengvina tokių
todėl (kaip bebūtų keista) daugelis "Unix" programų nelabai gerai atlieka šį darbą.
juos apdoroti.
"Unix" sistemoje failo pavadinime (viename komponente) gali būti bet kokie simboliai, išskyrus
pasvirąjį brūkšnį ir NUL '\0'
.
Tačiau apvalkalai primygtinai rekomenduoja nenaudoti tarpų, naujų eilučių ar skirtukų
niekur kelio pavadinime.
Dėl to ir standartiniuose Unix failų varduose nėra tarpų ir pan.
Kai susiduriama su failų vardais, kuriuose gali būti tarpų ir kt.
simbolių, turite būti labai atsargūs, ir aš pastebėjau, kad
seniai, kad man prireikė programos, kuri nėra standartinė "Unix" sistemoje.
Pavadinau ją escape
(1.1 versijos data - 1989-08-23T16:01:45Z).
Štai escape
naudojimo pavyzdys - su SCCS valdymo sistema.
Tai yra viršelio scenarijus, kuris atlieka ir delta
(pagalvokite check-in), ir
Get
(pagalvokite check-out).
Įvairūs argumentai, ypač -y
(priežastis, dėl kurios atlikote pakeitimą)
yra tuščių ir naujų eilučių.
Atkreipkite dėmesį, kad scenarijus sukurtas 1992 m., todėl jame naudojamos atgalinės žymės, o ne
$(cmd ...)
ir pirmoje eilutėje nenaudoja #!/bin/sh
.
: "@(#)$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"
(Tikriausiai šiais laikais taip kruopščiai nenaudočiau escape - tai yra
pavyzdžiui, nereikia su -e
argumentu, bet apskritai tai yra
vienas iš mano paprastesnių skriptų, naudojančių escape
.)
Programa escape
tiesiog išveda savo argumentus, panašiai kaip echo
tačiau ji užtikrina, kad argumentai būtų apsaugoti, kad juos būtų galima naudoti su
eval
(vienas iš eval
lygių; turiu programą, kuri atliko nuotolinį shell
vykdymą, ir jai reikėjo apsaugoti escape
išvestį).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Turiu kitą programą, pavadintą al
, kurios argumentai išvardijami po vieną eilutėje
(ir ji dar senesnė: 1987-01-27T14:35:49 versija 1.1).
Ji naudingiausia derinant scenarijus, nes ją galima prijungti prie
į komandų eilutę, kad pamatytumėte, kokie argumentai iš tikrųjų perduodami komandai.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[Pridėta:
O dabar, norėdami parodyti skirtumą tarp įvairių "$@"
užrašų, pateikiame dar vieną pavyzdį:
$ 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
$
Atkreipkite dėmesį, kad niekas neišsaugo originalių tuščių vietų tarp *
ir */*
komandinėje eilutėje. Taip pat atkreipkite dėmesį, kad galite pakeisti 'komandinės eilutės argumentus' apvalkale naudodami:
set -- -new -opt and "arg with space"
Taip nustatomos 4 parinktys: 'naujas
', 'opt
', 'ir
' ir 'arg su tarpais
'.
]
Hmm, tai'gana ilgas atsakymas - gal geriau tinka terminas egzegezė.
Pirminį escape
kodą galima gauti paprašius (el. paštu firstname dot
lastname at gmail dot com).
al
šaltinio kodas yra neįtikėtinai paprastas:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Štai ir viskas. Tai yra lygiavertis test.sh
scenarijus, kurį parodė Robertas Gamble'as, ir galėtų būti parašytas kaip apvalkalo funkcija (tačiau apvalkalo funkcijos neegzistavo vietinėje Bourne'o apvalkalo versijoje, kai pirmą kartą parašiau al
).
Taip pat atkreipkite dėmesį, kad al
galite parašyti kaip paprastą apvalkalo scenarijų:
[ $# != 0 ] && printf "%s\n" "$@"
Sąlyga reikalinga tam, kad jis nesukurtų išvesties, kai jam neperduodami jokie argumentai. Komanda printf
išves tuščią eilutę tik su formato eilutės argumentu, tačiau C programa nieko neišves.
Atkreipkite dėmesį, kad Roberto atsakymas yra teisingas, jis veikia ir sh
. Galite jį dar labiau supaprastinti:
for i in "$@"
yra lygiavertis:
for i
T.y., jums nieko nereikia!
Testavimas ($
yra komandų eilutė):
$ 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
Pirmą kartą apie tai perskaičiau Kernighan ir Pike knygoje Unix programavimo aplinka.
bash
, help for
tai dokumentuoja:
... ;] do COMMANDS; done`
Jei
'in WORDS ...;'
nėra, tuomet manoma, kad'in "$@"'
.