Jak wygenerować argumenty dla innego polecenia poprzez podstawienie polecenia

11

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, othercommandponieważ niektóre argumenty wymagają cytowania i nie można tego zmienić?

użytkownik1207217
źródło
Zależy od tego, co rozumiesz przez „niezawodnie”. Jeśli chcesz, aby następna komenda wzięła wynik dokładnie tak, jak jest wyświetlany na ekranie i zastosowała do niego reguły powłoki, być może evalmoże być użyty, ale generalnie nie jest to zalecane. xargsjest również coś do rozważenia
Sergiy Kolodyazhnyy
Chciałbym (spodziewam się), że dane wyjściowe somecommandbędą poddawane regularnemu analizowaniu powłoki
1207217
Jak powiedziałem w mojej odpowiedzi, należy użyć innego znaku do podziału na pola (jak :) ... zakładając, że postać będzie niezawodnie nie mieć wyjścia.
Olorin
Ale to nie jest w porządku, ponieważ nie przestrzega reguł cytowania, o to właśnie chodzi
1207217
2
Czy możesz zamieścić przykład z prawdziwego świata? Mam na myśli faktyczny wynik pierwszego polecenia i sposób jego interakcji z drugim poleceniem.
nxnev

Odpowiedzi:

10

Napisałem skrypt, który może wygenerować dla mnie te argumenty, z cytatami

Jeśli dane wyjściowe są poprawnie cytowane dla powłoki i ufasz wynikowi , możesz evalna 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.shprodukuje dane wyjściowe podobne 'foo bar' '*' asdf, możemy uruchomić, eval "args=( $(./gen_args.sh) )"aby wypełnić tablicę wywoływaną argswynikami. To byłoby trzy elementy foo bar, *, asdf.

Możemy "${args[@]}"jak zwykle używać do indywidualnego rozszerzania elementów tablicy:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(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 , evalchętnie uruchamiać żadnych zastępstw powłoki, więc $HOMEna wyjściu rozwinie się do swojego katalogu domowego, a na zmianę poleceń faktycznie uruchomić polecenie w prowadzeniu płaszcza eval. 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śli gen_args.shpobiera 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. (Gdyby get_args.shsam 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 skrypt foo bar#*#asdf. Teraz możemy użyć niekwotowanego rozszerzenia polecenia, aby podzielić wynik polecenia na argumenty.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Musisz IFScofnąć się później, jeśli zależy Ci na dzieleniu wyrazów w innym miejscu skryptu ( unset IFSpowinno działać, aby stało się domyślnym), a także użyć, set +fjeś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).

ilkkachu
źródło
Najlepsze z obu światów!
Olorin
Czy mógłbyś dodać uwagę wskazującą na znaczenie cytowania ${args[@]}- w innym przypadku to nie działało
1207217
@ user1207217, tak, masz rację. To samo dotyczy tablic i "${array[@]}"jak "$@". Oba muszą być cytowane, w przeciwnym razie dzielenie słów rozbija elementy tablicy na części.
ilkkachu
6

Problem polega na tym, że gdy somecommandskrypt wyświetli opcje othercommand, opcje są tak naprawdę tylko tekstem i na łasce standardowego parsowania powłoki (na co wpływ ma to, co się $IFSdzieje i jakie opcje powłoki działają, czego w ogólnym przypadku nie zrobiłbyś kontrolować).

Zamiast używać somecommanddo wypisywania opcji, byłoby łatwiej, bezpieczniej i bardziej niezawodnie używać go do wywoływania othercommand . somecommandSkrypt by następnie być skrypt otoki wokół othercommandzamiast 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ć, filektóre polecenia w /usr/binrzeczywistości są opakowaniami skryptów powłoki).

W bash, kshlub zsh, możesz łatwo otoczyć skrypt, który używa tablicy do przechowywania poszczególnych opcji dla othercommandtakich jak:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Następnie wywołaj othercommand(nadal w skrypcie opakowania):

othercommand "${options[@]}"

Rozwinięcie "${options[@]}"spowoduje, że każdy element optionstablicy zostanie indywidualnie cytowany i zaprezentowany othercommandjako 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ń othercommandjako dane wyjściowe.

W /bin/shużyj, $@aby zatrzymać opcje:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setTo polecenie służy do ustawiania parametrów pozycyjnych $1, $2, $3itd. 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).

Kusalananda
źródło
czy mógłbyś wyjaśnić set --?
user1207217
@ user1207217 Dodano wyjaśnienie do odpowiedzi.
Kusalananda
4

Jeśli dane somecommandwyjściowe są w niezawodnie dobrej składni powłoki, możesz użyć eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

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:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Zauważ, że echo rm bar baz test.shnie został przekazany do skryptu (z powodu ;) i został uruchomiony jako osobne polecenie. Dodałem |wokół, $varaby 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ń.

Olorin
źródło