Aktualizacja: Niektórzy mówią, że nie należy używać eval. Nie zgadzam się. Myślę, że ryzyko pojawia się, gdy można przekazać uszkodzone dane wejściowe eval
. Jednak jest wiele typowych sytuacji, w których nie jest to ryzyko, dlatego warto wiedzieć, jak używać eval w każdym przypadku. Ta odpowiedź na temat przepełnienia stosów wyjaśnia ryzyko związane z eval i alternatywami dla eval. Ostatecznie to użytkownik decyduje, czy / kiedy eval jest bezpieczne i wydajne w użyciu.
Instrukcja bash eval
umożliwia wykonanie wierszy kodu obliczonych lub uzyskanych przez skrypt bash.
Być może najprostszym przykładem byłby program bash, który otwiera inny skrypt basha jako plik tekstowy, odczytuje każdy wiersz tekstu i używa go eval
do wykonania po kolei. To zasadniczo to samo zachowanie co bashsource
instrukcji , której można by użyć, chyba że byłoby konieczne wykonanie jakiejś transformacji (np. Filtrowania lub podstawiania) zawartości zaimportowanego skryptu.
Rzadko tego potrzebowałem eval
, ale uważałem za przydatne czytanie lub zapisywanie zmiennych, których nazwy były zawarte w łańcuchach przypisanych do innych zmiennych. Na przykład, aby wykonywać akcje na zestawach zmiennych, zachowując mały ślad kodu i unikając redundancji.
eval
jest koncepcyjnie prosta. Jednak ścisła składnia języka bash i kolejność analizowania interpretera bash mogą być zniuansowane i sprawiać, że eval
wydają się tajemnicze i trudne w użyciu lub zrozumieniu. Oto najważniejsze informacje:
Argument przekazany do eval
jest wyrażeniem tekstowym, które jest obliczane w czasie wykonywania. eval
wykona końcowy przeanalizowany wynik swojego argumentu jako rzeczywistą linię kodu w skrypcie.
Składnia i kolejność analizowania są rygorystyczne. Jeśli wynik nie jest wykonywalną linią kodu bash, w zakresie twojego skryptu, program zawiesi się na eval
instrukcji, gdy spróbuje wykonać śmieci.
Podczas testowania możesz zamienić eval
instrukcję na echo
i przyjrzeć się temu, co jest wyświetlane. Jeśli jest to prawidłowy kod w bieżącym kontekście, przepuszczenie go eval
będzie działać.
Poniższe przykłady mogą pomóc wyjaśnić, jak działa eval ...
Przykład 1:
eval
instrukcja przed „normalnym” kodem to NOP
$ eval a=b
$ eval echo $a
b
W powyższym przykładzie pierwsze eval
stwierdzenia są bezcelowe i można je wyeliminować. eval
nie ma sensu w pierwszej linii, ponieważ nie ma aspektu dynamicznego w kodzie, tj. został już przeanalizowany do końcowych wierszy kodu basha, więc byłby identyczny z normalną instrukcją kodu w skrypcie bash. Drugi również eval
jest bezcelowy, ponieważ chociaż istnieje etap przetwarzania konwertujący $a
na jego odpowiednik w postaci dosłownego ciągu, nie ma żadnego pośredniego kierunku (np. Brak odniesienia przez wartość ciągu rzeczywistego rzeczownika bash lub zmiennej skryptu trzymanej przez bash), więc zachowałby się identycznie jako wiersz kodu bez eval
prefiksu.
Przykład 2:
Wykonaj przypisanie zmiennej, używając nazw zmiennych przekazanych jako wartości ciągów.
$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval
Gdyby tak było echo $key=$val
, wynik wyglądałby tak:
mykey=myval
To , będąc końcowym wynikiem analizy ciągu znaków, zostanie wykonane przez eval, stąd wynik instrukcji echo na końcu ...
Przykład 3:
Dodanie więcej pośrednictwa do przykładu 2
$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing
Powyższe jest nieco bardziej skomplikowane niż w poprzednim przykładzie, w większym stopniu polegając na kolejności parsowania i osobliwościach basha. eval
Linia byłaby grubsza się wewnętrznie przetwarzane w następującej kolejności (zwrócić uwagę na następujące stwierdzenia są pseudokod, nie prawdziwy kod, tylko próba pokazania jak stwierdzenie byłoby uzyskać w podziale na etapy wewnętrznie dojść do wyniku końcowego) .
eval eval \$$keyA=\$$valA # substitution of $keyA and $valA by interpreter
eval eval \$keyB=\$valB # convert '$' + name-strings to real vars by eval
eval $keyB=$valB # substitution of $keyB and $valB by interpreter
eval that=amazing # execute string literal 'that=amazing' by eval
Jeśli zakładana kolejność analizowania nie wyjaśnia, jakie działania eval wystarczają, trzeci przykład może bardziej szczegółowo opisywać parsowanie, aby pomóc wyjaśnić, co się dzieje.
Przykład 4:
Odkryj, czy zmienne, których nazwy są zawarte w łańcuchach, same zawierają wartości łańcuchowe.
a="User-provided"
b="Another user-provided optional value"
c=""
myvarname_a="a"
myvarname_b="b"
myvarname_c="c"
for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
eval varval=\$$varname
if [ -z "$varval" ]; then
read -p "$varname? " $varname
fi
done
W pierwszej iteracji:
varname="myvarname_a"
Bash analizuje argument eval
i eval
widzi to dosłownie w czasie wykonywania:
eval varval=\$$myvarname_a
Poniższy pseudokod próbuje zilustrować, jak bash interpretuje powyższą linię rzeczywistego kodu, aby uzyskać końcową wartość wykonywaną przez eval
. (następujące wiersze opisowe, a nie dokładny kod bash):
1. eval varval="\$" + "$varname" # This substitution resolved in eval statement
2. .................. "$myvarname_a" # $myvarname_a previously resolved by for-loop
3. .................. "a" # ... to this value
4. eval "varval=$a" # This requires one more parsing step
5. eval varval="User-provided" # Final result of parsing (eval executes this)
Po zakończeniu całego parsowania wynik jest wykonywany, a jego efekt jest oczywisty, co pokazuje, że nie ma w sobie nic szczególnie tajemniczego eval
, a złożoność polega na analizie jego argumentu.
varval="User-provided"
Pozostały kod w powyższym przykładzie po prostu sprawdza, czy wartość przypisana do zmiennej $ varval jest null, a jeśli tak, monituje użytkownika o podanie wartości.
$($n)
działa$n
w podpowłoce. Próbuje uruchomić polecenie,1
które nie istnieje.