Zmienna jako polecenie; eval vs bash -c

41

Czytałem skrypt bash, który ktoś stworzył i zauważyłem, że autor nie używa eval do oceny zmiennej jako polecenia
Autor użył

bash -c "$1"

zamiast

eval "$1"

Zakładam, że używanie eval jest preferowaną metodą i prawdopodobnie i tak jest szybsze. Czy to prawda?
Czy jest jakaś praktyczna różnica między nimi? Jakie są zauważalne różnice między nimi?

kim jestem
źródło
W niektórych przypadkach możesz uciec bez jednego z nich. e='echo foo'; $edziała dobrze.
Dennis

Odpowiedzi:

40

eval "$1"wykonuje polecenie w bieżącym skrypcie. Może ustawiać i używać zmiennych powłoki z bieżącego skryptu, ustawiać zmienne środowiskowe dla bieżącego skryptu, ustawiać i używać funkcji z bieżącego skryptu, ustawiać bieżący katalog, umask, ograniczenia i inne atrybuty dla bieżącego skryptu i tak dalej. bash -c "$1"wykonuje polecenie w całkowicie osobnym skrypcie, który dziedziczy zmienne środowiskowe, deskryptory plików i inne środowisko procesowe (ale nie przesyła żadnych zmian z powrotem), ale nie dziedziczy wewnętrznych ustawień powłoki (zmienne powłoki, funkcje, opcje, pułapki itp.).

Istnieje inny sposób, (eval "$1")który wykonuje polecenie w podpowłoce: dziedziczy wszystko ze skryptu wywołującego, ale nie przesyła żadnej zmiany z powrotem.

Na przykład, zakładając, że zmienna dirnie jest eksportowana i $1jest cd "$foo"; ls, a następnie:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdwyświetla zawartość /somewhere/elsei drukuje /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdwyświetla zawartość /somewhere/elsei drukuje /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdwyświetla zawartość /starting/directory(ponieważ cd ""nie zmienia bieżącego katalogu) i drukuje /starting/directory.
Gilles „SO- przestań być zły”
źródło
Dzięki. Nie wiedziałem o (ewaluacja „1 $”), czy różni się to od źródła?
whoami,
1
@whoami (eval "$1")nie ma z tym nic wspólnego source. To tylko połączenie (…)i eval. source foojest mniej więcej równoważne z eval "$(cat foo)".
Gilles 'SO - przestań być zły'
Musieliśmy pisać nasze odpowiedzi w tym samym czasie ...
Mikeserv
@whoami Podstawowa różnica między evali .dotpolega na tym, że evaldziała z argumentami i .dotz plikami.
mikeserv
Dzięki Wam obojgu. Mój poprzedni komentarz wydaje się trochę głupi, kiedy go ponownie przeczytałem ...
whoami,
23

Najważniejsza różnica między

bash -c "$1" 

I

eval "$1"

Czy to pierwsze działa w podpowłoce, a drugie nie. Więc:

set -- 'var=something' 
bash -c "$1"
echo "$var"

WYDAJNOŚĆ:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

WYDAJNOŚĆ:

something

Nie mam jednak pojęcia, dlaczego ktokolwiek miałby używać tego pliku wykonywalnego bashw ten sposób. Jeśli musisz go wywołać, użyj wbudowanej gwarancji POSIX sh. Lub (subshell eval)jeśli chcesz chronić swoje środowisko.

Osobiście wolę .dotprzede wszystkim muszle .

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

WYNIK

something1
something2
something3
something4
something5

ALE POTRZEBUJESZ W ogóle?

Jedynym powodem użycia jednego z nich jest to, że twoja zmienna faktycznie przypisuje lub ocenia inną, lub dzielenie słów jest ważne dla wyniku.

Na przykład:

var='echo this is var' ; $var

WYDAJNOŚĆ:

this is var

To działa, ale tylko dlatego, echoże nie obchodzi go liczba argumentów.

var='echo "this is var"' ; $var

WYDAJNOŚĆ:

"this is var"

Widzieć? Pojawiają się podwójne cudzysłowy, ponieważ wynik rozszerzenia powłoki $varnie jest oceniany quote-removal.

var='printf %s\\n "this is var"' ; $var

WYDAJNOŚĆ:

"this
is
var"

Ale z evallub sh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

WYDAJNOŚĆ:

this is var
this is var

Kiedy używamy evallub shpowłoka bierze drugie przejście na wyniki rozszerzeń i ocenia je również jako potencjalne polecenie, więc cudzysłowy mają znaczenie. Możesz także:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

WYNIK

this is var
mikeserv
źródło
5

Zrobiłem szybki test:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(Tak, wiem, użyłem bash -c do wykonania pętli, ale to nie powinno mieć znaczenia).

Wyniki:

eval    : 1.17s
bash -c : 7.15s

Tak evaljest szybciej. Ze strony podręcznika użytkownika eval:

Narzędzie eval konstruuje polecenie, łącząc argumenty razem, oddzielając je znakami. Skonstruowane polecenie zostanie odczytane i wykonane przez powłokę.

bash -coczywiście wykonuje polecenie w powłoce bash. Jedna uwaga: użyłem, /bin/echoponieważ echojest to powłoka wbudowana w bash, co oznacza, że ​​nowy proces nie musi być uruchamiany. Wymiana /bin/echoz echodo bash -ctestu zajęło 1.28s. To mniej więcej to samo. Hovever, evaljest szybszy do uruchamiania plików wykonywalnych. Kluczową różnicą jest to, że evalnie uruchamia nowej powłoki (wykonuje polecenie w bieżącej), podczas gdy bash -curuchamia nową powłokę, a następnie wykonuje polecenie w nowej powłoce. Uruchomienie nowej powłoki wymaga czasu i dlatego bash -cjest wolniejsze niż eval.

PlasmaPower
źródło
Myślę, że OP chce bash -cz tym evalnie porównać exec.
Joseph R.
@JosephR. Ups! Zmienię to.
PlasmaPower
1
@JosephR. Należy to teraz naprawić. Też trochę bash -c
przerobiłem
3
Chociaż jest to prawda, brakuje podstawowej różnicy, że polecenie jest wykonywane w różnych środowiskach. Oczywiste jest, że rozpoczęcie nowej instancji bash będzie wolniejsze, nie jest to interesujące spostrzeżenie.
Gilles 'SO - przestań być zły'