eval polecenie w Bash i jego typowe zastosowania


165

Po przeczytaniu stron podręcznika bash iw odniesieniu do tego postu .

Wciąż nie rozumiem, co dokładnie evalrobi 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?


1
Dla przypomnienia, druga próba działa. $($n)działa $nw podpowłoce. Próbuje uruchomić polecenie, 1które nie istnieje.
Martin Wickman,

1
@MartinWickman Ale wymaganiem jest uruchomienie w echo $1końcu, nie 1. Nie sądzę, aby można to zrobić za pomocą podpowłok.
Hari Menon


1
@ Raze2dust: Nie wierzę, że sugerował, że można go uruchomić z podpowłokami, ale raczej wyjaśnił, dlaczego piąta komenda wymieniona w OP nie działa.
jedwards

Odpowiedzi:


196

evalprzyjmuje 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”:

echo ${!n}
one

$(…)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 $njako polecenie powłoki i wyświetla jego dane wyjściowe. Ponieważ $nwartościuje do 1, $($n)próbuje uruchomić polecenie 1, które nie istnieje.

eval echo \${$n}uruchamia parametry przekazane do eval. Po rozwinięciu parametrami są echoi ${1}. Więc eval echo \${$n}uruchamia polecenie echo ${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:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

evalnie 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. evaljest nadal przydatny, gdy trzeba skonstruować dłuższe polecenie zawierające operatory, słowa zastrzeżone itp.


W odniesieniu do mojego komentarza powyżej, ile „przejść” wykonuje eval?
kstratis

@ Konos5 Jaki komentarz? evalotrzymuje ciąg znaków (który sam może być wynikiem analizowania i oceny) i interpretuje go jako fragment kodu.
SO- Gilles 'SO- przestań być zły'

Pod odpowiedzią Raze2dust zostawiłem komentarz. Teraz uważam, że eval jest używany głównie do celów dereferencji. Jeśli wpiszę eval echo \ $ {$ n} otrzymam jeden. Jednak jeśli wpiszę echo \ $ {$ n} otrzymam \ $ {1}. Uważam, że dzieje się tak z powodu „dwuprzebiegowego” parsowania programu eval. Zastanawiam się teraz, co by się stało, gdybym potrzebował potrójnej dereferencji przy użyciu dodatkowej deklaracji i = n. W tym przypadku, zgodnie z Raze2dust, wystarczy umieścić dodatkowy eval. Uważam jednak, że powinien być lepszy sposób ... (może się łatwo
zagracać

@ Konos5 nie użyłbym eval eval. Nie pamiętam, żebym kiedykolwiek czuł taką potrzebę. Jeśli naprawdę potrzebujesz dwa evalkarnety, użyć zmiennej tymczasowej, będzie to łatwiejsze do debugowania: eval tmp="\${$i}"; eval x="\${$tmp}".
SO- Gilles 'SO- przestań być zły'

1
@ Konos5 „Sparowano dwa razy” jest nieco mylące. Niektórzy ludzie mogą uwierzyć w to ze względu na trudność określenia dosłownego argumentu łańcuchowego w Bash, który jest chroniony przed różnymi rozszerzeniami. evalpo 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ł.
ormaaj

39

Po prostu pomyśl o eval jako o „ocenie wyrażenia jeszcze jeden raz przed wykonaniem”

eval echo \${$n}staje się echo $1po 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 na 1
  • evalzniknął

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.

evaljest 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 sposoby evalużycia oraz związane z tym ryzyko.


3
Uwaga dotycząca pierwszego punktora ${VAR}składnia jest dozwolona i preferowana, gdy występuje jakakolwiek niejednoznaczność (czy $VAR == $V, po której następuje ARlub $VAR == $VApo niej R). ${VAR}jest równoważne $VAR. W rzeczywistości jego nazwa zmiennej $njest niedozwolona.
jedwards

2
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 (wydruki one) na moim komputerze ...
Hari Menon

2
@ Konos5 echo \\\${\${$i}} drukuje \${${n}}. eval echo \\\${\${$i}}jest równoważne echo \${${n}}`` and prints $ {1} . eval eval echo \\\ $ {\ $ {$ i}} `jest równoważne eval echo ${1}i drukuje one.
SO- Gilles 'SO- przestań być zły'

2
@ Konos5 Pomyśl w ten sam sposób - pierwszy ` escapes the second one, and the third `wymyka się $później. Tak jest \${${n}}po jednej rundzie ocen
Hari Menon

2
@ Konos5 Od lewej do prawej jest właściwy sposób myślenia o analizowaniu cytatów i ukośnika odwrotnego. Najpierw \\ pojawia się jeden lewy ukośnik. Następnie \$daje 1 dolara. I tak dalej.
Gilles 'SO- przestań być zły'

25

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:

eval "$(script-or-program)"

Zwróć uwagę, że cytaty są ważne. Weźmy ten (wymyślony) przykład:

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!

masz jakieś przykłady popularnych narzędzi, które to robią? Samo narzędzie ma środki do tworzenia zestawu poleceń powłoki, które można przekazać do eval?
Joakim Erdfelt,

@Joakim Nie znam żadnych narzędzi open source, które to robią, ale był używany w niektórych prywatnych skryptach w firmach, w których pracowałem. Właśnie zacząłem ponownie używać tej techniki z xampp. Pliki .conf Apache rozszerzają zapisane zmienne środowiskowe ${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świetla exportinstrukcje bash , aby zdefiniować zmienne dla plików .conf.
sootsnoot

@Joakim Alternatywą byłoby posiadanie skryptu do generowania każdego z plików .conf, których dotyczy problem, z szablonu, w oparciu o to samo przeszukiwanie. Jedną z rzeczy, które lubię bardziej w mojej drodze, jest to, że uruchamianie Apache bez przechodzenia przez / opt / lampp / xampp nie używa przestarzałych skryptów wyjściowych, ale raczej nie uruchamia się, ponieważ zmienne środowiskowe rozwijają się do zera i tworzą nieprawidłowe dyrektywy.
sootsnoot

@Anthony Sottile Widzę, że zredagowałeś odpowiedź, dodając cudzysłowy wokół $ (skrypt-lub-program), mówiąc, że są one ważne podczas uruchamiania wielu poleceń. Czy możesz podać przykład - poniższe działa dobrze z poleceniami oddzielonymi średnikami w stdout foo.sh: echo '#! / Bin / bash'> foo.sh; echo 'echo "echo -na; echo -nb; echo -n c"' >> foo.sh; chmod 755 foo.sh; eval $ (./ foo.sh). To daje abc na stdout. Uruchomienie ./foo.sh daje: echo -na; echo -nb; echo -nc
sootsnoot

1
Aby zapoznać się z przykładem typowego narzędzia wykorzystującego eval, zobacz pyenv . pyenv umożliwia łatwe przełączanie się między różnymi wersjami Pythona. Umieszczasz 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.
Jerry101,

10

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:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >

4

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 evalumoż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 evaldo 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.

evaljest koncepcyjnie prosta. Jednak ścisła składnia języka bash i kolejność analizowania interpretera bash mogą być zniuansowane i sprawiać, że evalwydają się tajemnicze i trudne w użyciu lub zrozumieniu. Oto najważniejsze informacje:

  1. Argument przekazany do evaljest wyrażeniem tekstowym, które jest obliczane w czasie wykonywania. evalwykona końcowy przeanalizowany wynik swojego argumentu jako rzeczywistą linię kodu w skrypcie.

  2. Składnia i kolejność analizowania są rygorystyczne. Jeśli wynik nie jest wykonywalną linią kodu bash, w zakresie twojego skryptu, program zawiesi się na evalinstrukcji, gdy spróbuje wykonać śmieci.

  3. Podczas testowania możesz zamienić evalinstrukcję na echoi przyjrzeć się temu, co jest wyświetlane. Jeśli jest to prawidłowy kod w bieżącym kontekście, przepuszczenie go evalbę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 evalstwierdzenia są bezcelowe i można je wyeliminować. evalnie 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ż evaljest bezcelowy, ponieważ chociaż istnieje etap przetwarzania konwertujący $ana 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 evalprefiksu.



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. evalLinia 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 evali evalwidzi 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.


3

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.


2

Niedawno musiałem użyć, evalaby wymusić wiele rozszerzeń nawiasów, aby były oceniane w wymaganej kolejności. Bash wykonuje wiele rozwinięć nawiasów od lewej do prawej, więc

xargs -I_ cat _/{11..15}/{8..5}.jpg

rozszerza się do

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

ale potrzebowałem drugiego rozszerzenia nawiasu wykonanego jako pierwsze, ustępującego

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Najlepsze, co mogłem wymyślić, to

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

Działa to, ponieważ pojedyncze cudzysłowy chronią pierwszy zestaw nawiasów klamrowych przed interpretacją podczas analizowania evalwiersza poleceń, pozostawiając je do rozwinięcia przez podpowłokę wywołaną przez eval.

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ć.


1

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:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

Wynik wygląda następująco:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

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.


Mówienie, że jest „więcej możliwości” jest co najmniej błahe, trywialne i zbędne.
dotbit

0

Podoba mi się odpowiedź „ocena wyrażenia jeszcze jeden raz przed wykonaniem” i chciałbym to wyjaśnić na innym przykładzie.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

Ciekawe wyniki w opcji 2 są takie, że przekazalibyśmy 2 parametry w następujący sposób:

  • Pierwszy parametr: "value
  • Drugi parametr: content"

Jak to jest w przypadku sprzeczności z intuicją? Dodatkowe evalto naprawią.

Na podstawie https://stackoverflow.com/a/40646371/744133


0

W pytaniu:

who | grep $(tty | sed s:/dev/::)

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:

grep $(tty | sed s:/dev/::) <(who)

działa dobrze.

who | grep $(echo pts/3)

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.