Po przeczytaniu stron podręcznika bash iw odniesieniu do tego postu .
Wciąż nie rozumiem, co dokładnie eval
robi polecenie i jakie byłyby jego typowe zastosowania. Na przykład, jeśli:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
Co dokładnie się tutaj dzieje i jak znak dolara i ukośnik odwrotny wiążą się z problemem?
$($n)
działa$n
w podpowłoce. Próbuje uruchomić polecenie,1
które nie istnieje.echo $1
końcu, nie1
. Nie sądzę, aby można to zrobić za pomocą podpowłok.eval
.Odpowiedzi:
eval
przyjmuje ciąg jako argument i ocenia go tak, jakbyś wpisał ten ciąg w wierszu poleceń. (Jeśli przekażesz kilka argumentów, najpierw zostaną one połączone spacjami między nimi).${$n}
jest błędem składni w bash. W nawiasach klamrowych możesz mieć tylko nazwę zmiennej, z możliwymi przedrostkami i sufiksami, ale nie możesz mieć arbitralnej składni basha, aw szczególności nie możesz używać rozwijania zmiennych. Można jednak powiedzieć „wartość zmiennej, której nazwa jest w tej zmiennej”:$(…)
uruchamia polecenie podane w nawiasach w podpowłoce (tj. w oddzielnym procesie, który dziedziczy wszystkie ustawienia, takie jak wartości zmiennych z bieżącej powłoki) i zbiera jego dane wyjściowe.echo $($n)
Działa więc$n
jako polecenie powłoki i wyświetla jego dane wyjściowe. Ponieważ$n
wartościuje do1
,$($n)
próbuje uruchomić polecenie1
, które nie istnieje.eval echo \${$n}
uruchamia parametry przekazane doeval
. Po rozwinięciu parametrami sąecho
i${1}
. Więceval echo \${$n}
uruchamia polecenieecho ${1}
.Należy zauważyć, że przez większość czasu, należy użyć w cudzysłowie zmienne podstawienia i podstawienia komend (tzn w każdej chwili istnieje
$
)"$foo", "$(foo)"
. Zawsze umieszczaj cudzysłowy wokół podstawień zmiennych i poleceń , chyba że wiesz, że musisz je pominąć. Bez podwójnych cudzysłowów powłoka dokonuje podziału na pola (tj. Dzieli wartość zmiennej lub wynik polecenia na osobne słowa), a następnie traktuje każde słowo jako wzorzec wieloznaczny. Na przykład:eval
nie jest używany zbyt często. W niektórych powłokach najczęstszym zastosowaniem jest uzyskanie wartości zmiennej, której nazwa nie jest znana do czasu uruchomienia. W bash nie jest to konieczne dzięki${!VAR}
składni.eval
jest nadal przydatny, gdy trzeba skonstruować dłuższe polecenie zawierające operatory, słowa zastrzeżone itp.źródło
eval
otrzymuje ciąg znaków (który sam może być wynikiem analizowania i oceny) i interpretuje go jako fragment kodu.eval eval
. Nie pamiętam, żebym kiedykolwiek czuł taką potrzebę. Jeśli naprawdę potrzebujesz dwaeval
karnety, użyć zmiennej tymczasowej, będzie to łatwiejsze do debugowania:eval tmp="\${$i}"; eval x="\${$tmp}"
.eval
po prostu pobiera kod w ciągu i ocenia go zgodnie ze zwykłymi regułami. Technicznie nie jest to nawet poprawne, ponieważ jest kilka narożnych przypadków, w których Bash modyfikuje parsowanie, aby nawet nie wykonywać rozszerzeń do argumentów eval - ale to bardzo niejasna ciekawostka, o której wątpię, aby ktokolwiek wiedział.Po prostu pomyśl o eval jako o „ocenie wyrażenia jeszcze jeden raz przed wykonaniem”
eval echo \${$n}
staje sięecho $1
po pierwszej rundzie oceny. Trzy zmiany, na które należy zwrócić uwagę:\$
Stała$
(Potrzebne jest odwrotny ukośnik, inaczej stara się oceniać${$n}
, co oznacza zmienną o nazwie{$n}
, która nie jest dozwolone)$n
został oceniony na1
eval
zniknąłW drugiej rundzie tak jest
echo $1
wykonać bezpośrednio.Więc
eval <some command>
najpierw oceni<some command>
(oceniając tutaj mam na myśli zmienne zastępcze, zamień znaki ucieczki na poprawne itp.), A następnie ponownie uruchomi wynikowe wyrażenie.eval
jest używany, gdy chcesz dynamicznie tworzyć zmienne lub odczytywać dane wyjściowe z programów specjalnie zaprojektowanych do odczytu w ten sposób. Przykłady można znaleźć pod adresem http://mywiki.wooledge.org/BashFAQ/048 . Odsyłacz zawiera również typowe sposobyeval
użycia oraz związane z tym ryzyko.źródło
${VAR}
składnia jest dozwolona i preferowana, gdy występuje jakakolwiek niejednoznaczność (czy$VAR == $V
, po której następujeAR
lub$VAR == $VA
po niejR
).${VAR}
jest równoważne$VAR
. W rzeczywistości jego nazwa zmiennej$n
jest niedozwolona.eval eval echo \\\${\${$i}}
wykona potrójną dereferencję. Nie jestem pewien, czy istnieje prostszy sposób na zrobienie tego. Również\${$n}
działa dobrze (wydrukione
) na moim komputerze ...echo \\\${\${$i}}
drukuje\${${n}}
.eval echo \\\${\${$i}}
jest równoważneecho \${${n}}`` and prints
$ {1}.
eval eval echo \\\ $ {\ $ {$ i}} `jest równoważneeval echo ${1}
i drukujeone
.` escapes the second one, and the third
`wymyka się$
później. Tak jest\${${n}}
po jednej rundzie ocen\\
pojawia się jeden lewy ukośnik. Następnie\$
daje 1 dolara. I tak dalej.Z mojego doświadczenia wynika, że „typowym” zastosowaniem eval jest uruchamianie poleceń generujących polecenia powłoki w celu ustawienia zmiennych środowiskowych.
Być może masz system, który używa kolekcji zmiennych środowiskowych i masz skrypt lub program, który określa, które z nich należy ustawić i ich wartości. Za każdym razem, gdy uruchamiasz skrypt lub program, działa on w procesie rozwidlonym, więc wszystko, co robi bezpośrednio ze zmiennymi środowiskowymi, jest tracone po zakończeniu. Ale ten skrypt lub program może wysyłać polecenia eksportu na standardowe wyjście.
Bez eval musiałbyś przekierować standardowe wyjście do pliku tymczasowego, pobrać źródło pliku tymczasowego, a następnie go usunąć. Dzięki eval możesz po prostu:
Zwróć uwagę, że cytaty są ważne. Weźmy ten (wymyślony) przykład:
źródło
${varname}
. Uważam, że wygodnie jest używać identycznych plików .conf na kilku różnych serwerach z kilkoma parametrami parametryzowanymi przez zmienne środowiskowe. Edytowałem / opt / lampp / xampp (który uruchamia apache), aby wykonać tego rodzaju ewaluację za pomocą skryptu, który przegląda system i wyświetlaexport
instrukcje bash , aby zdefiniować zmienne dla plików .conf.eval "$(pyenv init -)"
w swoim.bash_profile
(lub podobnym) pliku konfiguracyjnym powłoki. To tworzy mały skrypt powłoki, a następnie ocenia go w bieżącej powłoce.Instrukcja eval mówi powłoce, aby przyjęła argumenty eval jako polecenie i uruchomiła je przez wiersz poleceń. Przydaje się w takiej sytuacji jak poniżej:
W swoim skrypcie, jeśli definiujesz polecenie w zmiennej, a później chcesz użyć tego polecenia, powinieneś użyć eval:
źródło
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ć, żeeval
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ę naecho
i przyjrzeć się temu, co jest wyświetlane. Jeśli jest to prawidłowy kod w bieżącym kontekście, przepuszczenie goeval
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 NOPW 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 bezeval
prefiksu.Przykład 2:
Wykonaj przypisanie zmiennej, używając nazw zmiennych przekazanych jako wartości ciągów.
Gdyby tak było
echo $key=$val
, wynik wyglądałby tak: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
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) .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.
W pierwszej iteracji:
Bash analizuje argument
eval
ieval
widzi to dosłownie w czasie wykonywania: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):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.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.
źródło
Początkowo celowo nigdy nie nauczyłem się używać eval, ponieważ większość ludzi zaleca trzymanie się z dala od niego jak zarazy. Jednak niedawno odkryłem przypadek użycia, który sprawił, że zastanawiałem się nad tym, że nie rozpoznałem go wcześniej.
Jeśli masz zadania crona, które chcesz uruchomić interaktywnie w celu przetestowania, możesz wyświetlić zawartość pliku za pomocą cat, a następnie skopiować i wkleić zadanie cron, aby je uruchomić. Niestety wiąże się to z dotknięciem myszy, co w mojej książce jest grzechem.
Powiedzmy, że masz zadanie crona w /etc/cron.d/repeatme z zawartością:
*/10 * * * * root program arg1 arg2
Nie możesz tego wykonać jako skryptu ze wszystkimi śmieciami przed nim, ale możemy użyć cut, aby pozbyć się wszystkich śmieci, zawinąć je w podpowłokę i wykonać ciąg z eval
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
Polecenie cut drukuje tylko szóste pole pliku, rozdzielone spacjami. Następnie Eval wykonuje to polecenie.
Użyłem tutaj zadania cron jako przykładu, ale koncepcja polega na sformatowaniu tekstu ze standardowego wyjścia, a następnie ocenie tego tekstu.
Użycie eval w tym przypadku nie jest niebezpieczne, ponieważ wiemy dokładnie, co będziemy oceniać wcześniej.
źródło
Niedawno musiałem użyć,
eval
aby wymusić wiele rozszerzeń nawiasów, aby były oceniane w wymaganej kolejności. Bash wykonuje wiele rozwinięć nawiasów od lewej do prawej, więcrozszerza się do
ale potrzebowałem drugiego rozszerzenia nawiasu wykonanego jako pierwsze, ustępującego
Najlepsze, co mogłem wymyślić, to
Działa to, ponieważ pojedyncze cudzysłowy chronią pierwszy zestaw nawiasów klamrowych przed interpretacją podczas analizowania
eval
wiersza poleceń, pozostawiając je do rozwinięcia przez podpowłokę wywołaną przezeval
.Może istnieć jakiś przebiegły schemat obejmujący zagnieżdżone rozszerzenia nawiasów klamrowych, które pozwalają na to w jednym kroku, ale jeśli tak jest, jestem za stary i głupi, żeby to zobaczyć.
źródło
Zapytałeś o typowe zastosowania.
Jedną z częstych skarg dotyczących skryptów powłoki jest to, że (rzekomo) nie można przekazywać przez odwołanie, aby odzyskać wartości z funkcji.
Ale faktycznie, poprzez „eval”, to może przechodzić przez odniesienie. Odbierający może przekazać z powrotem listę przypisań zmiennych do oceny przez dzwoniącego. Jest przekazywana przez referencję, ponieważ wywołujący może określić nazwę (nazwy) zmiennej wynikowej (zmiennych wynikowych) - zobacz przykład poniżej. Wyniki błędów można przekazać z powrotem do standardowych nazw, takich jak errno i errstr.
Oto przykład przekazywania przez referencję w bash:
Wynik wygląda następująco:
Ten tekst ma prawie nieograniczoną szerokość pasma! I jest więcej możliwości, jeśli używa się wielu wierszy wyjściowych: np. Pierwsza linia może być użyta do przypisania zmiennych, druga do ciągłego „strumienia myśli”, ale to wykracza poza zakres tego postu.
źródło
Podoba mi się odpowiedź „ocena wyrażenia jeszcze jeden raz przed wykonaniem” i chciałbym to wyjaśnić na innym przykładzie.
Ciekawe wyniki w opcji 2 są takie, że przekazalibyśmy 2 parametry w następujący sposób:
"value
content"
Jak to jest w przypadku sprzeczności z intuicją? Dodatkowe
eval
to naprawią.Na podstawie https://stackoverflow.com/a/40646371/744133
źródło
W pytaniu:
wyświetla błędy twierdzące, że pliki a i tty nie istnieją. Zrozumiałem, że oznacza to, że tty nie jest interpretowane przed wykonaniem grep, ale zamiast tego bash przekazał tty jako parametr do grep, który zinterpretował go jako nazwę pliku.
Istnieje również sytuacja przekierowań zagnieżdżonych, które powinny być obsługiwane przez dopasowane nawiasy, które powinny określać proces potomny, ale bash jest pierwotnie separatorem słów, tworzącym parametry do wysłania do programu, dlatego nawiasy nie są najpierw dopasowywane, ale interpretowane jako widziany.
Dostałem specyficzny z grep i określiłem plik jako parametr zamiast używać potoku. Uprościłem również polecenie podstawowe, przekazując dane wyjściowe z polecenia jako plik, aby potoki we / wy nie były zagnieżdżane:
działa dobrze.
nie jest tak naprawdę pożądane, ale eliminuje zagnieżdżoną rurę, a także działa dobrze.
Podsumowując, bash nie lubi zagnieżdżania się. Ważne jest, aby zrozumieć, że bash nie jest programem nowofalowym napisanym w sposób rekurencyjny. Zamiast tego bash jest starym programem 1,2,3, do którego dodano funkcje. W celu zapewnienia kompatybilności wstecznej pierwotny sposób interpretacji nigdy nie został zmieniony. Gdyby bash został przepisany tak, aby najpierw pasował do nawiasów, ile błędów zostałoby wprowadzonych do ilu programów bash? Wielu programistów uwielbia być tajemniczy.
źródło