Bash -c z parametrami pozycyjnymi

15

Zwykle $0w skrypcie ustawiana jest nazwa skryptu lub cokolwiek, co zostało wywołane jako (w tym ścieżka). Jeśli jednak używam bashz tą -copcją, $0jest ustawiony na pierwszy z argumentów przekazanych po ciągu polecenia:

bash -c 'echo $0 ' foo bar 
# foo 

W efekcie wydaje się, że parametry pozycyjne zostały przesunięte, ale obejmują $0. Jednak shiftw ciągu polecenia nie ma wpływu $0(jak zwykle):

bash -c 'echo $0; shift; echo $0 ' foo bar
# foo
# foo

Dlaczego to pozornie dziwne zachowanie ciągów poleceń? Zauważ, że szukam powodu, uzasadnienia, za wdrożeniem tak dziwnego zachowania.


Można spekulować, że taki ciąg komend nie wymagałby $0parametru jak zwykle zdefiniowano, więc dla oszczędności jest on również używany do zwykłych argumentów. Jednak w takim przypadku zachowanie shiftjest dziwne. Inną możliwością jest $0zdefiniowanie zachowania programów (la bashnazywane jako shlub vimnazywane as vi), ale nie może tak być, ponieważ $0tutaj jest widoczne tylko w ciągu poleceń, a nie przez programy w nim wywołane. Nie mogę wymyślić żadnego innego zastosowania $0, więc nie mogę tego wyjaśnić.

muru
źródło
Na marginesie, widziałem użycie -dla $0argumentu jako idiomu, jak w sh -c 'foo $1 $2' - a b. W ten sposób wygląda to całkiem normalnie (gdy się dowiesz, co to -znaczy)
Volker Siegel
Możesz również echo 'echo the other side of this pipe globs "$@"' | sh -s -- *, niestety, $0generalnie nie być ustawialnym parametrem z -sopcją tream ... Można go jednak używać na wiele takich samych sposobów xargs. I inne poza tym.
mikeserv
@VolkerSiegel Byłoby bardziej normalne --, to mogłaby mieć zwykłą interpretację „stąd zaczynają się argumenty”, widzianą w niektórych innych programach. Z drugiej strony może to być mylące dla osób, które nie są zaznajomione z -cmyśleniem, że --faktycznie mają taką interpretację.
muru

Odpowiedzi:

10

Daje to możliwość ustawienia / wyboru $0podczas korzystania ze skryptu wbudowanego. W przeciwnym razie $0byłoby po prostu bash.

Następnie możesz na przykład:

$ bash -c 'wc -c < "${1?}"' getlength foo
4
$ bash -c 'wc -c < "${1?}"' getlength bar
getlength: bar: No such file or directory
$ bash -c 'wc -c < "${1?}"' getlength
getlength: 1: parameter null or not set

Nie wszystkie muszle to robiły. Powłoka Bourne'a tak zrobiła. Powłoka Korna (i Almquista) zdecydowała się na zamianę pierwszego parametru $1. POSIX ostatecznie poszedł na drodze Bourne, więc kshi ashpochodne powrócił do tego później (więcej o tym w http://www.in-ulm.de/~mascheck/various/find/#shell ). Oznaczało to, że przez długi czas sh(w zależności od systemu opartego na powłoce Bourne'a, Almquista lub Korna) nie wiedziałeś, czy poszedł pierwszy argument, $0czy też $1, dla przenośności, musiałeś robić takie rzeczy jak:

sh -c 'echo foo in "$1"' foo foo

Lub:

sh -c 'shift "$2"; echo txt files are "$@"' tentative-arg0 3 2 *.txt

Na szczęście POSIX określił nowe zachowanie tam, gdzie pojawia się pierwszy argument $0, dzięki czemu możemy teraz przenośnie wykonać:

sh -c 'echo txt files are "$@"' meaningful-arg0-for-error *.txt
Stéphane Chazelas
źródło
Ja po prostu zabrakło: bash -c 'echo txt files are "$@"' meaningful-arg0-for-error *.txtna Ubuntu 14.04, bash version 4.3.11(1)-releasei mam: txt files are *.txt. Oo
muru
2
@muru, co jest poprawne (i prawdopodobnie nie masz txtplików w bieżącym katalogu). Zobacz takżebash -c 'echo "${1?}"' foo
Stéphane Chazelas
O tak. To praktycznie przydatny przykład.
muru
1
sh -c 'shift "$2"; echo txt files are "$@"' tentative-arg0 3 2 *.txtjest fantastycznie pomysłowy!
iruvar
Lub używasz całkowicie niezawodnego obejścia (sugerowanego przez Stéphane Chazelas w comp.unix.shell) SHELL -c 'shift $1; command' 2 1 arg1 arg2 ... haha ...
mikeserv
3

To zachowanie jest zdefiniowane przez POSIX :

sh -c nazwa_komendy [argument ...]

Odczytaj polecenia z argumentu string_pole. Ustaw wartość parametru specjalnego 0 (patrz Parametry specjalne ) na podstawie wartości argumentu nazwa_komendy i parametrów pozycyjnych ($ 1, $ 2 itd.) W kolejności od pozostałych argumentów argumentu.

Co do tego, dlaczego chcesz tego zachowania: to wygładza lukę między skryptem a -cłańcuchem. Możesz bezpośrednio konwertować między nimi bez zmiany zachowania. Inne obszary opierają się na tym, że są identyczne.

Jest to również zgodne z ogólnym działaniem argumentów programu: ostatecznie sprowadza się to do wywołania jednego z exec funkcji, w której znajduje się również pierwszy podany argument $0, i równie często ten argument jest taki sam, jak uruchamiany plik wykonywalny. Czasami jednak potrzebujesz specjalnej wartości i nie byłoby innego sposobu, aby ją zdobyć. Biorąc pod uwagę, że argument istnieje, musi zostać odwzorowany na coś, a użytkownik musi mieć możliwość ustawienia tego, co to jest.

Ta konsekwencja (i prawdopodobnie historyczny wypadek) prowadzi do sytuacji, którą znajdziesz.

Michael Homer
źródło
Czy możesz podać (mam nadzieję, że w prawdziwym świecie) przykład drugiego para?
muru
Ostrożnie z analogią z argv[0]. $0jest ustawiony na argv[0]przekazywany tylko execve()wtedy, gdy jest podobny shlub bash. W przypadku skryptów $0jest ustawiony na ścieżkę podaną jako argument 1, 2 ... do interpretera (w przypadku skryptów wykonywanych bezpośrednio, która pochodzi z pierwszego argumentu ścieżki do execve ()).
Stéphane Chazelas