web-dev-qa-db-fra.com

Variable en tant que commande; eval vs bash -c

Je lisais un script bash créé par quelqu'un et j'ai remarqué que l'auteur n'utilise pas eval pour évaluer une variable comme une commande
L'auteur a utilisé

bash -c "$1"

au lieu de

eval "$1"

Je suppose que l'utilisation d'eval est la méthode préférée et 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?

42
whoami

eval "$1" Exécute la commande dans le script actuel. Il peut définir et utiliser des variables Shell à partir du script actuel, définir des variables d'environnement pour le script actuel, définir et utiliser des fonctions à partir du script actuel, définir le répertoire actuel, umask, des limites et d'autres attributs pour le script actuel, 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 fichiers et d'autres environnements de processus (mais ne retransmet aucun changement) mais n'hérite pas des paramètres internes du shell (variables, fonctions, options, pièges, etc.).

Il existe une autre façon, (eval "$1"), Qui exécute la commande dans un sous-shell: elle hérite de tout du script appelant mais ne retransmet aucune modification.

Par exemple, en supposant que la variable dir n'est pas exportée et que $1 Est cd "$foo"; ls, Alors:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd Répertorie le contenu de /somewhere/else Et imprime /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd répertorie le contenu de /somewhere/else et imprime /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd Répertorie le contenu de /starting/directory (Car cd "" Ne modifie pas le répertoire actuel) et imprime /starting/directory.

La différence la plus importante entre

bash -c "$1" 

Et

eval "$1"

Est-ce que le premier s'exécute en sous-couche et le second ne fonctionne pas. Donc:

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

PRODUCTION:

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

PRODUCTION:

something

Je n'ai aucune idée pourquoi quelqu'un utiliserait jamais l'exécutable bash de cette façon, cependant. Si vous devez l'invoquer, utilisez le sh intégré garanti POSIX. Ou (subshell eval) si vous souhaitez protéger votre environnement.

Personnellement, je préfère le .dot par dessus tout.

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

PRODUCTION

something1
something2
something3
something4
something5

MAIS EN AVEZ-VOUS BESOIN AT TOUS?

La seule cause à utiliser est vraiment dans le cas où votre variable en attribue ou en évalue une autre, ou si le fractionnement de mots est important pour la sortie.

Par exemple:

var='echo this is var' ; $var

PRODUCTION:

this is var

Cela fonctionne, mais uniquement parce que echo ne se soucie pas du nombre d'arguments.

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

PRODUCTION:

"this is var"

Voir? Les guillemets doubles viennent parce que le résultat de l'expansion de Shell de $var n'est pas évalué pour quote-removal.

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

PRODUCTION:

"this
is
var"

Mais avec eval ou sh:

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

PRODUCTION:

this is var
this is var

Lorsque nous utilisons eval ou sh, le shell effectue une deuxième passe aux résultats des extensions et les évalue également en tant que commande potentielle, de sorte que les guillemets font une différence. Vous pouvez également faire:

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

PRODUCTION

this is var
23
mikeserv

J'ai fait un petit 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'

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

Les resultats:

eval    : 1.17s
bash -c : 7.15s

Donc eval est plus rapide. Depuis la page de manuel de eval:

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

bash -c bien sûr, exécute la commande dans un shell bash. Une remarque: j'ai utilisé /bin/echo car echo est un shell intégré avec bash, ce qui signifie qu'un nouveau processus n'a pas besoin d'être démarré. Remplacement de /bin/echo avec echo pour le bash -c test, il a fallu 1.28s. C'est à peu près la même chose. Cependant, eval est plus rapide pour exécuter les exécutables. La principale différence ici est que eval ne démarre pas un nouveau Shell (il exécute la commande dans le courant) alors que bash -c démarre un nouveau Shell, puis exécute la commande dans le nouveau Shell. Le démarrage d'un nouveau Shell prend du temps, c'est pourquoi bash -c est plus lent que eval.

5
PlasmaPower