Variabile come comando; eval vs bash -c


41

Stavo leggendo uno script bash creato da qualcuno e ho notato che l'autore non usa eval per valutare una variabile come comando
L'autore ha usato

bash -c "$1"

invece di

eval "$1"

Presumo che usare eval sia il metodo preferito ed è probabilmente più veloce comunque. È vero?
C'è qualche differenza pratica tra i due? Quali sono le differenze notevoli tra i due?


In alcune occasioni, puoi scappare senza neanche. e='echo foo'; $efunziona benissimo.
Dennis,

Risposte:


40

eval "$1"esegue il comando nello script corrente. Può impostare e utilizzare le variabili di shell dello script corrente, impostare le variabili di ambiente per lo script corrente, impostare e utilizzare le funzioni dello script corrente, impostare la directory corrente, umask, i limiti e altri attributi per lo script corrente e così via. bash -c "$1"esegue il comando in uno script completamente separato, che eredita le variabili di ambiente, i descrittori di file e altri ambienti di processo (ma non restituisce alcuna modifica) ma non eredita le impostazioni della shell interna (variabili della shell, funzioni, opzioni, trap, ecc.).

Esiste un altro modo, (eval "$1")che esegue il comando in una subshell: eredita tutto dallo script chiamante ma non trasmette alcuna modifica indietro.

Ad esempio, supponendo che la variabile dirnon sia esportata e $1sia cd "$foo"; ls, quindi:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdelenca il contenuto /somewhere/elsee stampa /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdelenca il contenuto /somewhere/elsee stampa /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdelenca il contenuto di /starting/directory(perché cd ""non cambia la directory corrente) e stampa /starting/directory.

Grazie. Non sapevo di (eval "$ 1"), è diverso dalla fonte?
whoami,

1
@whoami (eval "$1")non ha nulla a che fare con source. È solo una combinazione di (…)e eval. source fooè approssimativamente equivalente a eval "$(cat foo)".
Gilles 'SO- smetti di essere malvagio'

Dobbiamo aver scritto le nostre risposte allo stesso tempo ...
mikeserv,

@whoami La differenza principale tra evale .dotè che evalfunziona con argomenti e .dotfunziona con i file.
mikeserv,

Grazie ad entrambi. Il mio commento precedente sembra essere un po 'stupido ora che l'ho letto di nuovo ...
Whoami

23

La differenza più importante tra

bash -c "$1" 

E

eval "$1"

È che il primo viene eseguito in una subshell e il secondo no. Così:

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

PRODUZIONE:

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

PRODUZIONE:

something

Non ho idea del perché qualcuno avrebbe mai usato l'eseguibile bashin quel modo, però. Se è necessario invocarlo, utilizzare il POSIX integrato garantito sh. O (subshell eval)se desideri proteggere il tuo ambiente.

Personalmente, preferisco soprattutto le shell .dot.

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

PRODUZIONE

something1
something2
something3
something4
something5

MA LO AVETE BISOGNO DI TUTTO?

L'unica causa da utilizzare, in realtà, è nel caso in cui la variabile ne assegni o valuti un'altra, oppure la suddivisione delle parole è importante per l'output.

Per esempio:

var='echo this is var' ; $var

PRODUZIONE:

this is var

Funziona, ma solo perché echonon importa del suo conteggio degli argomenti.

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

PRODUZIONE:

"this is var"

Vedere? Le virgolette vengono fornite perché il risultato dell'espansione della shell $varnon viene valutato quote-removal.

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

PRODUZIONE:

"this
is
var"

Ma con evalo sh:

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

PRODUZIONE:

this is var
this is var

Quando usiamo evalo shla shell fa un secondo passaggio ai risultati delle espansioni e li valuta anche come un potenziale comando, e quindi le virgolette fanno la differenza. Puoi anche fare:

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

PRODUZIONE

this is var

5

Ho fatto un test veloce:

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'

(Sì, lo so, ho usato bash -c per eseguire il loop ma questo non dovrebbe fare la differenza).

I risultati:

eval    : 1.17s
bash -c : 7.15s

Quindi evalè più veloce. Dalla pagina man di eval:

L'utilità di valutazione deve costruire un comando concatenando argomenti insieme, separando ciascuno con un carattere. Il comando costruito deve essere letto ed eseguito dalla shell.

bash -covviamente, esegue il comando in una shell bash. Una nota: l'ho usata /bin/echoperché echoè integrata una shell bash, il che significa che non è necessario avviare un nuovo processo. Sostituito /bin/echocon echoper il bash -ctest, ci è voluto 1.28s. Questo è più o meno lo stesso. Al passaggio del mouse, evalè più veloce per l'esecuzione di eseguibili. La differenza chiave qui è che evalnon avvia una nuova shell (esegue il comando in quella corrente) mentre bash -cavvia una nuova shell, quindi esegue il comando nella nuova shell. L'avvio di una nuova shell richiede tempo ed è per questo che bash -cè più lento di eval.


Penso che l'OP voglia confrontare bash -ccon evalno exec.
Joseph R.,

@JosephR. Oops! Lo cambierò.
PlasmaPower

1
@JosephR. Ora dovrebbe essere risolto. Inoltre ho ripetuto un po 'di più i test e bash -cnon è poi così male ...
PlasmaPower

3
Sebbene ciò sia vero, manca la differenza fondamentale che il comando viene eseguito in ambienti diversi. È ovvio che l'avvio di una nuova istanza di bash sarà più lento, questa non è un'osservazione interessante.
Gilles 'SO- smetti di essere malvagio' il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.