Jak iterować argumenty w skrypcie Bash
Mam złożone polecenie, z którego chciałbym zrobić skrypt powłoki / Basha. Mogę to napisać w kategoriach $1
łatwo:
foo $1 args -o $1.ext
Chcę móc przekazać skryptowi wiele nazw wejściowych. Jak to zrobić?
I, oczywiście, chcę obsługiwać nazwy plików ze spacjami w nich.
9 answers
Użyj "$@"
, aby przedstawić wszystkie argumenty:
for var in "$@"
do
echo "$var"
done
Spowoduje iterację każdego argumentu i wydrukowanie go w osobnej linii. $@ zachowuje się jak $ * z tą różnicą, że podczas cytowania argumenty są odpowiednio dzielone, jeśli są w nich spacje:
sh test.sh 1 2 '3 4'
1
2
3 4
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-01 23:52:48
przepisanie usuniętej odpowiedzi przez VonC .
Zobacz: ${1:+"$@"} in / bin / sh
Podstawowe prace magisterskie: "$@"
jest poprawny, a $*
(nienotowany) prawie zawsze się myli.
Dzieje się tak dlatego, że "$@"
działa dobrze, gdy argumenty zawierają spacje i
działa na tak samo jak $*
Kiedy nie.
W pewnych okolicznościach "$*"
też jest OK, ale "$@"
zazwyczaj (ale nie
zawsze) działa w tych samych miejscach.
Nie cytowane, $@
i {[11] } są równoważne (i prawie zawsze błędne).
Więc jaka jest różnica między $*
, $@
, "$*"
, i "$@"
? Wszystkie są związane z "wszystkimi argumentami do powłoki", ale robią różne rzeczy. Kiedy nie cytowane, $*
i $@
robią to samo. Traktują każde "słowo" (sekwencję nie-białych znaków) jako odrębną kłótnia. Formularze cytowane są jednak zupełnie inne: "$*"
traktuje listę argumentów jako pojedynczy łańcuch oddzielony spacjami, podczas gdy "$@"
traktuje argumenty prawie dokładnie tak, jak były podane w wierszu poleceń.
"$@"
rozszerza się do niczego, gdy nie ma argumentów pozycyjnych; "$*"
rozszerza się do pustego ciągu-i tak, jest różnica, choć może być trudno ją dostrzec.
Zobacz więcej informacji poniżej, po wprowadzeniu (niestandardowego) polecenia al
.
Teza Drugorzędna: jeśli trzeba przetworzyć argumenty ze spacjami i wtedy
przekaż je innym poleceniom, wtedy czasami potrzebujesz niestandardowych
Narzędzia wspomagające. (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 cytaty przed rozszerzeniem
zmienne.
Tak więc, aby powłoka zwracała uwagę na cytaty osadzone w $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
$
Robi się to naprawdę trudne, gdy masz nazwy plików, takie jak " He said,
"Don't do this!"
"(z cudzysłowami, 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
$
[63]}muszle (Wszystkie) nie sprawiają, że jest to szczególnie łatwe w obsłudze takie
rzeczy, więc (co zabawne) wiele programów uniksowych nie wykonuje dobrej pracy
radzenie sobie z nimi.
W systemie Unix nazwa pliku (jednoskładnikowego) może zawierać dowolne znaki z wyjątkiem
slash i NUL '\0'
.
Jednak powłoki zdecydowanie nie zachęcają do spacji lub nowych linii lub tabs
gdziekolwiek w nazwie ścieżki.
Dlatego też standardowe Unixowe nazwy plików nie zawierają spacji, itd.
Gdy mamy do czynienia z nazwami plików, które mogą zawierać spacje i inne
kłopotliwe postacie, trzeba być niezwykle ostrożnym, a ja znalazłem
dawno temu potrzebowałem programu, który nie jest standardem w Unixie.
Nazywam to escape
(Wersja 1.1 była datowana na 1989-08-23T16:01:45Z).
Oto przykład escape
w użyciu-z systemem sterowania SCCS.
Jest to skrypt okładki, który wykonuje zarówno delta
(think check-in ) i
get
(think check-out ).
Różne argumenty, zwłaszcza -y
(powód, dla którego dokonałeś zmiany)
zawiera spacje i znaki nowego wiersza.
Zauważ, że skrypt pochodzi z 1992 roku, więc używa back-ticków zamiast
$(cmd ...)
notacji i nie używa #!/bin/sh
w pierwszej linii.
: "@(#)$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"
(w dzisiejszych czasach raczej nie używałbym escape ' a tak dokładnie - jest
nie jest potrzebny np. argument -e
- ale ogólnie jest to
jeden z moich prostszych skryptów przy użyciu escape
.)
escape
program po prostu wypisuje swoje argumenty, raczej jak echo
robi, ale zapewnia, że argumenty są chronione do użycia z
eval
(jeden poziom eval
; mam program, który zrobił zdalną powłokę
execution, i że potrzebne do ucieczki wyjścia escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Mam inny program o nazwie {[28] } który wyświetla swoje argumenty po jednej linii (a jest jeszcze bardziej starożytna: Wersja 1.1 z dnia 1987-01-27T14: 35: 49). Jest to najbardziej przydatne, gdy debugowania skryptów, ponieważ można go podłączyć do wiersz 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 pomiędzy *
i */*
w wierszu poleceń. Zauważ również, że możesz zmienić "argumenty wiersza poleceń" w powłoce używając:
set -- -new -opt and "arg with space"
To ustawia 4 opcje, '-new
', '-opt
', 'and
', i "arg with space
".
]
Hmm, to dość długa odpowiedź - być możeegzegeza jest lepszym określeniem.
Kod źródłowy dla escape
dostępny na życzenie (email do firstname dot
lastname at gmail dot com).
Kod źródłowy dla al
jest niesamowicie prosty:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
To wszystko. Jest to odpowiednik skryptu test.sh
, który pokazał Robert Gamble, i może być napisany jako funkcja powłoki (ale funkcje powłoki nie istniały w lokalnym wersja powłoki Bourne ' a kiedy po raz pierwszy napisałem al
).
Zauważ również, że możesz napisać al
jako prosty skrypt powłoki:
[ $# != 0 ] && printf "%s\n" "$@"
Warunek jest potrzebny, aby nie generował żadnych wyników, gdy nie podano żadnych argumentów. Polecenie printf
wyświetli pustą linię tylko z argumentem format string, ale program C nic nie produkuje.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-13 07:39:36
Zauważ, że odpowiedź Roberta jest poprawna i działa również w sh
. Można (przenośnie) uprościć to jeszcze bardziej:
for i in "$@"
Jest równoważne:
for i
Czyli niczego nie potrzebujesz!
Testowanie ($
jest wierszem polecenia):
$ 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
Po raz pierwszy przeczytałem o tym w środowisku programowania Unix Kernighan i Pike.
W bash
, help for
dokumentuje to:
for NAME [in WORDS ... ;] do COMMANDS; done
Jeśli
'in WORDS ...;'
nie występuje, to'in "$@"'
zakłada się.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-16 09:28:14
W prostych przypadkach można również użyć shift
.
Traktuje listę argumentów jak kolejkę. Każdy shift
wyrzuca pierwszy argument i
indeks każdego z pozostałych argumentów jest zmniejszany.
#this prints all arguments
while test $# -gt 0
do
echo "$1"
shift
done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-20 14:24:33
Możesz również uzyskać do nich dostęp jako do elementów tablicy, na przykład, jeśli nie chcesz iterować przez wszystkie
argc=$#
argv=("$@")
for (( j=0; j<argc; j++ )); do
echo "${argv[j]}"
done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-20 14:20:40
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-27 18:00:20
Jeśli chcesz wyliczyć listę argumentów za pomocą indeksu (np. wyszukać konkretne słowo), możesz to zrobić bez kopiowania listy lub mutowania jej.
Powiedz, że chcesz podzielić listę argumentów za podwójnym myślnikiem ( " -- " ) i przekazać argumenty przed myślnikami do jednego polecenia, a argumenty za myślnikami do innego:
toolwrapper() {
for i in $(seq 1 $#); do
[[ "${!i}" == "--" ]] && break
done || return $? # returns error status if we don't "break"
echo "dashes at $i"
echo "Before dashes: ${@:1:i-1}"
echo "After dashes: ${@:i+1:$#}"
}
Wyniki powinny wyglądać tak:
$ toolwrapper args for first tool -- and these are for the second
dashes at 5
Before dashes: args for first tool
After dashes: and these are for the second
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-02 03:48:46
Getopt Użyj polecenia w skryptach, aby sformatować dowolne opcje wiersza poleceń lub parametry.
#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-03 15:31:25
Loop against $#, the number of arguments variable, również działa.
#! /bin/bash
for ((i=1; i<=$#; i++))
do
printf "${!i}\n"
done
test.sh 1 2 '3 4'
Ouput:
1
2
3 4
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-31 17:53:52