Variable en tant que commande; eval vs bash -c


41

Je lisais un script bash que quelqu'un a fait et j'ai remarqué que l'auteur n'utilisait pas eval pour évaluer une variable en tant que commande
L'auteur a utilisé

bash -c "$1"

au lieu de

eval "$1"

Je suppose que l’utilisation de eval est la méthode préférée et que c’est probablement plus rapide de toute façon. Est-ce vrai?
Y a-t-il une différence pratique entre les deux? Quelles sont les différences notables entre les deux?


Dans certaines occasions, vous pouvez vous en passer sans cela. e='echo foo'; $efonctionne très bien.
Dennis

Réponses:


40

eval "$1"exécute la commande dans le script en cours. Il peut définir et utiliser des variables shell à partir du script en cours, des variables d'environnement pour le script en cours, définir et utiliser des fonctions à partir du script en cours, définir le répertoire en cours, umask, les limites et autres attributs du script en cours, etc. bash -c "$1"exécute la commande dans un script complètement séparé, qui hérite des variables d'environnement, des descripteurs de fichier et de tout autre environnement de processus (mais ne renvoie aucune modification) mais n'hérite pas des paramètres de shell internes (variables d'environnement, fonctions, options, interruptions, etc.).

Il existe un autre moyen, (eval "$1")qui exécute la commande dans un sous-shell: il hérite de tout du script appelant mais ne renvoie aucune modification.

Par exemple, en supposant que la variable dirn'est pas exportée et $1est cd "$foo"; ls, alors:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdliste le contenu /somewhere/elseet les impressions /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdliste le contenu /somewhere/elseet les impressions /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdrépertorie le contenu de /starting/directory(car cd ""ne change pas le répertoire en cours) et imprime /starting/directory.

Merci. Je ne connaissais pas (eval "$ 1"), est-ce différent de la source?
whoami

1
@ Whoami (eval "$1")n'a rien à voir avec source. C'est juste une combinaison de (…)et eval. source fooest à peu près équivalent à eval "$(cat foo)".
Gilles 'SO- arrête d'être méchant'

Nous
devions

@whoami La principale différence entre evalet .dotest que evalfonctionne avec des arguments et .dotavec des fichiers.
mikeserv

Merci à vous deux. Mon commentaire précédent semble être un peu stupide maintenant que je le relis ...
whoami

23

La différence la plus importante entre

bash -c "$1" 

Et

eval "$1"

Est-ce que le premier fonctionne dans un sous-shell et le second ne le fait pas. Alors:

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

SORTIE:

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

SORTIE:

something

Je ne sais pas pourquoi quelqu'un utiliserait jamais l'exécutable bashde cette manière, cependant. Si vous devez l’appeler, utilisez l’intégration garantie POSIX sh. Ou (subshell eval)si vous souhaitez protéger votre environnement.

Personnellement, je préfère la coquille .dotavant tout.

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

SORTIE

something1
something2
something3
something4
something5

Mais en avez-vous besoin?

En réalité, la seule cause à utiliser est le cas où votre variable en attribue ou en évalue une autre, ou le fractionnement des mots est important pour la sortie.

Par exemple:

var='echo this is var' ; $var

SORTIE:

this is var

Cela fonctionne, mais seulement parce echoque peu importe le nombre d'arguments.

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

SORTIE:

"this is var"

Voir? Les doubles guillemets arrivent car le résultat de l'expansion du shell de $varn'est pas évalué quote-removal.

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

SORTIE:

"this
is
var"

Mais avec evalou sh:

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

SORTIE:

this is var
this is var

Lorsque nous utilisons evalou que shle shell passe en revue les résultats des extensions et les évalue également comme une commande potentielle, les guillemets font une différence. Vous pouvez aussi faire:

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

SORTIE

this is var

5

J'ai fait un test rapide:

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'

(Oui, je sais, j'ai utilisé bash -c pour exécuter la boucle mais cela ne devrait pas faire la différence).

Les resultats:

eval    : 1.17s
bash -c : 7.15s

Alors evalc'est plus rapide. De la page de manuel de eval:

L'utilitaire eval doit construire une commande en concaténant des arguments, en les séparant par un caractère. La commande construite doit être lue et exécutée par le shell.

bash -cbien sûr, exécute la commande dans un shell bash. Une note: j'ai utilisé /bin/echoparce que echoc'est un shell intégré bash, ce qui signifie qu'un nouveau processus n'a pas besoin d'être démarré. En remplaçant /bin/echopar echopour le bash -ctest, cela a pris 1.28s. C'est à peu près la même chose. Cependant, evalest plus rapide pour exécuter des exécutables. La différence clé ici est que evalne démarre pas un nouveau shell (il exécute la commande dans celui en cours), tandis que bash -cdémarre un nouveau shell, puis exécute la commande dans le nouveau shell. Démarrer un nouveau shell prend du temps, c'est pourquoi il bash -cest plus lent que eval.


Je pense que le PO veut comparer bash -cavec evalpas exec.
Joseph R.

@JosephR. Oops! Je vais changer ça.
PlasmaPower

1
@JosephR. Il devrait être corrigé maintenant. J'ai aussi refait les tests un peu plus et ce bash -cn'est pas si mal ...
PlasmaPower

3
Bien que cela soit vrai, il manque la différence fondamentale selon laquelle la commande est exécutée dans différents environnements. Il est évident que le démarrage d'une nouvelle instance de bash sera plus lent, ce n'est pas une observation intéressante.
Gilles 'SO- arrête d'être méchant'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.