W następstwie: nieoczekiwane zachowanie podczas podstawiania poleceń powłoki
Mam polecenie, które może przyjąć ogromną listę argumentów, z których niektóre mogą zawierać spacje (i prawdopodobnie inne rzeczy)
Napisałem skrypt, który może wygenerować dla mnie te argumenty z cudzysłowami, ale muszę skopiować i wkleić dane wyjściowe np
./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
Próbowałem to usprawnić, po prostu robiąc
./othercommand $(./somecommand)
i spotkałem się z nieoczekiwanym zachowaniem wspomnianym powyżej. Pytanie brzmi - czy można rzetelnie zastosować podstawienie polecenia do wygenerowania argumentów, othercommand
ponieważ niektóre argumenty wymagają cytowania i nie można tego zmienić?
shell
arguments
command-substitution
użytkownik1207217
źródło
źródło
eval
może być użyty, ale generalnie nie jest to zalecane.xargs
jest również coś do rozważeniasomecommand
będą poddawane regularnemu analizowaniu powłoki:
) ... zakładając, że postać będzie niezawodnie nie mieć wyjścia.Odpowiedzi:
Jeśli dane wyjściowe są poprawnie cytowane dla powłoki i ufasz wynikowi , możesz
eval
na nim uruchomić .Zakładając, że masz powłokę obsługującą tablice, najlepiej byłoby użyć jej do przechowywania otrzymanych argumentów.
Jeśli
./gen_args.sh
produkuje dane wyjściowe podobne'foo bar' '*' asdf
, możemy uruchomić,eval "args=( $(./gen_args.sh) )"
aby wypełnić tablicę wywoływanąargs
wynikami. To byłoby trzy elementyfoo bar
,*
,asdf
.Możemy
"${args[@]}"
jak zwykle używać do indywidualnego rozszerzania elementów tablicy:(Uwaga: cudzysłów
"${array[@]}"
rozwija się do wszystkich elementów jako niezmodyfikowane odrębne argumenty. Bez cudzysłowu elementy tablicy podlegają podziałowi słów. Patrz np. Strona Tablice na BashGuide .)Jednakże ,
eval
chętnie uruchamiać żadnych zastępstw powłoki, więc$HOME
na wyjściu rozwinie się do swojego katalogu domowego, a na zmianę poleceń faktycznie uruchomić polecenie w prowadzeniu płaszczaeval
. Dane wyjściowe"$(date >&2)"
utworzyłyby pojedynczy pusty element tablicy i wydrukowałyby bieżącą datę na standardowym wyjściu. Jest to problem, jeśligen_args.sh
pobiera dane z jakiegoś niezaufanego źródła, takiego jak inny host w sieci, nazwy plików utworzone przez innych użytkowników. Dane wyjściowe mogą zawierać dowolne polecenia. (Gdybyget_args.sh
sam był złośliwy, nie musiałby niczego wysyłać, mógłby po prostu uruchomić złośliwe polecenia bezpośrednio).Alternatywą dla cytowania powłoki, która jest trudna do przeanalizowania bez eval, byłoby użycie innego znaku jako separatora w danych wyjściowych skryptu. Musisz wybrać taki, który nie jest potrzebny w rzeczywistych argumentach.
Wybierzmy
#
i wypiszmy skryptfoo bar#*#asdf
. Teraz możemy użyć niekwotowanego rozszerzenia polecenia, aby podzielić wynik polecenia na argumenty.Musisz
IFS
cofnąć się później, jeśli zależy Ci na dzieleniu wyrazów w innym miejscu skryptu (unset IFS
powinno działać, aby stało się domyślnym), a także użyć,set +f
jeśli chcesz użyć globowania później.Jeśli nie używasz Bash lub innej powłoki, która ma tablice, możesz użyć do tego parametrów pozycyjnych. Wymień
args=( $(...) )
sięset -- $(./gen_args.sh)
i używać"$@"
zamiast"${args[@]}"
potem. (Tutaj również potrzebujesz cudzysłowów"$@"
, w przeciwnym razie parametry pozycyjne podlegają podziałowi słów).źródło
${args[@]}
- w innym przypadku to nie działało"${array[@]}"
jak"$@"
. Oba muszą być cytowane, w przeciwnym razie dzielenie słów rozbija elementy tablicy na części.Problem polega na tym, że gdy
somecommand
skrypt wyświetli opcjeothercommand
, opcje są tak naprawdę tylko tekstem i na łasce standardowego parsowania powłoki (na co wpływ ma to, co się$IFS
dzieje i jakie opcje powłoki działają, czego w ogólnym przypadku nie zrobiłbyś kontrolować).Zamiast używać
somecommand
do wypisywania opcji, byłoby łatwiej, bezpieczniej i bardziej niezawodnie używać go do wywoływaniaothercommand
.somecommand
Skrypt by następnie być skrypt otoki wokółothercommand
zamiast jakiś skrypt pomocniczy, że trzeba pamiętać, aby połączyć w jakiś szczególny sposób w ramach linii poleceńotherscript
. Skrypty owijania są bardzo powszechnym sposobem dostarczania narzędzia, które wywołuje inne podobne narzędzie z innym zestawem opcji (wystarczy sprawdzić,file
które polecenia w/usr/bin
rzeczywistości są opakowaniami skryptów powłoki).W
bash
,ksh
lubzsh
, możesz łatwo otoczyć skrypt, który używa tablicy do przechowywania poszczególnych opcji dlaothercommand
takich jak:Następnie wywołaj
othercommand
(nadal w skrypcie opakowania):Rozwinięcie
"${options[@]}"
spowoduje, że każdy elementoptions
tablicy zostanie indywidualnie cytowany i zaprezentowanyothercommand
jako osobny argument.Użytkownik otoki byłby nieświadomy faktu, że faktycznie dzwoni
othercommand
, co nie byłoby prawdą, gdyby skrypt wygenerował zamiast tego opcje wiersza poleceńothercommand
jako dane wyjściowe.W
/bin/sh
użyj,$@
aby zatrzymać opcje:(
set
To polecenie służy do ustawiania parametrów pozycyjnych$1
,$2
,$3
itd. Są to, co składa się na tablicy$@
w standardzie POSIX powłoki. Początkowy--
jest aby zasygnalizowaćset
, że nie ma możliwości, tylko podane argumenty.--
Jest naprawdę potrzebne tylko wtedy, gdy pierwsza wartość to coś, co zaczyna się od-
).Zauważ, że są to podwójne cudzysłowy
$@
i${options[@]}
to zapewnia, że elementy nie są indywidualnie dzielone na słowa (i globbed nazwy pliku).źródło
set --
?Jeśli dane
somecommand
wyjściowe są w niezawodnie dobrej składni powłoki, możesz użyćeval
:Ale musisz mieć pewność, że dane wyjściowe mają prawidłowe cytowanie i tak dalej, w przeciwnym razie możesz skończyć z uruchamianiem poleceń również poza skryptem:
Zauważ, że
echo rm bar baz test.sh
nie został przekazany do skryptu (z powodu;
) i został uruchomiony jako osobne polecenie. Dodałem|
wokół,$var
aby to podkreślić.Zasadniczo, o ile nie można całkowicie zaufać wynikowi
somecommand
, nie można wiarygodnie wykorzystać jego wyniku do zbudowania ciągu poleceń.źródło