Exécuter une commande stockée dans une variable


11

J'ai une commande stockée dans une variable. Imaginons que la variable $iait la valeur:

cat -nT index.php |grep 'someregex'

Lorsque j'essaie d'exécuter la variable ci-dessus en la tapant, $ielle échoue car le shell tente d'exécuter la variable entière en une seule commande. J'ai également essayé d'utiliser eval($i)et de mettre des $ibackticks.

Comment puis-je faire exécuter le shell $icomme s'il s'agissait d'une commande? Et pourquoi ne fonctionne-t-il pas de la même manière que.

$i='echo hi'; $i

Est-ce parce que je devais en quelque sorte pirater les guillemets simples? (Parce que vous ne pouvez pas les imbriquer.) Actuellement, ma solution est

echo $i > /foo;  . /foo

Mais je ne veux pas avoir créé un fichier juste pour cela, seulement pour le supprimer plus tard.

Ce que je veux dire par «j'ai piraté les guillemets simples, c'est que j'ai fait:

$i='cat index.php | grep -P '"'"'MYREGEXHERE'"'"

4
La question est, pourquoi la commande est-elle stockée dans une variable en premier lieu? Vous feriez mieux d'assembler la commande lorsque vous l'exécutez, en stockant les arguments d'option dans des variables ou dans un tableau. Pouvez-vous montrer l'intégralité du script que vous avez?
slhck

Réponses:


18

Réponse courte: voir BashAQ # 50: J'essaie de mettre une commande dans une variable, mais les cas complexes échouent toujours! .

Réponse longue: c'est à cause de l'ordre dans lequel bash analyse la ligne de commande. Plus précisément, il recherche des éléments tels que les tuyaux et les redirections avant d'étendre les valeurs variables, et ne revient pas en arrière et ne recherche pas les tuyaux, etc. dans les valeurs développées. Essentiellement, les variables sont substituées à mi-chemin du processus d'analyse, de sorte que la valeur n'est analysée qu'à mi-chemin avant d'être exécutée.

Pour résoudre ce problème, vous devez répondre à la question de @ slhck: pourquoi la commande est-elle stockée dans une variable en premier lieu? Quel est le problème réel que vous essayez de résoudre? Selon l'objectif réel, plusieurs solutions sont possibles:

  • Ne le stockez pas dans une variable, exécutez-le simplement directement. Stocker des commandes pour une utilisation ultérieure est délicat, et si vous n'en avez pas vraiment besoin, tout simplement pas.

  • Utilisez une fonction au lieu d'une variable. C'est à cela qu'ils servent, après tout:

    i() { cat -nT index.php |grep 'someregex'; }
    i

    Le principal inconvénient de cela est que vous ne pouvez pas créer la fonction dynamiquement - vous ne pouvez pas inclure ou exclure conditionnellement du code de la fonction (bien que la fonction elle-même puisse avoir des éléments conditionnels qui sont sélectionnés lors de son exécution).

  • Utilisez eval. Cela devrait être considéré comme un dernier recours, car il est facile d'obtenir un comportement inattendu. Il exécute essentiellement la commande via une autre passe d'analyse complète, de sorte que tous les canaux, etc. obtiennent leur pleine signification - mais cela signifie également que des parties de la commande que vous pensiez être uniquement des données seront également analysées et peut-être exécutées. Les noms de fichiers qui contiennent des métacaractères shell (pipe, point-virgule, citation / apostrophe, etc.) peuvent avoir des effets étranges et parfois dangereux. Si vous utilisez eval, au moins entre guillemets, la chaîne, sinon son contenu est essentiellement analysé une fois et demie, avec des résultats encore plus étranges.

    i="cat -nT index.php |grep 'someregex'"
    eval "$i"

EDIT: Une autre approche standard de stockage d'une commande dans une variable consiste à utiliser un tableau au lieu d'une simple variable. Cela vous permet de stocker une commande avec des arguments complexes (par exemple contenant des espaces) sans problème, mais ne stockera pas des choses comme les tuyaux et les redirections. Ainsi, l'approche tableau ne sera pas utile dans ce cas particulier.


+1 pour la evalméthode. Cela m'a empêché de poser la même question :). J'ai quelque chose comme sudo rsync -aAHXi -n --delete-excluded --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/lost+found","/mnt/DATA/*","/var/log/*","/var/swap","/var/cache/apt/archives/*.deb"} -e ssh root@piac_wireless:/ /home/mrx/Docs/RPi/backup/piac/piac_usb-rootça qui n'exécutait pas correctement les exclusions.
dentex

@dentex, je vous déconseille fortement d'utiliser evalpour cela - il y a trop de façons pour que ça se passe mal. Un tableau serait une meilleure façon de le faire. Voir ici , ici et ici pour des exemples.
Gordon Davisson

Merci pour la suggestion. J'essaierai d'implémenter la solution avec le tableau, afin de passer la liste d'exclusion à rsync.
dentex

4

Cela devrait simplement fonctionner:

a="cat -nT index.php |grep 'someregex'"
eval "$a"

Attention, qui evalintroduit des vulnérabilités potentielles et des situations de comportement inattendues doit donc être utilisé, si jamais, avec plus de prudence.


Si vous utilisez eval, vous devez vraiment utiliser des guillemets doubles ( eval "$i") pour éviter les effets extra-extra-bizarres. Par exemple, essayez ceci avec a="cat -nT index.php |grep ' .* '"(c'est-à-dire que l'expression régulière doit correspondre à deux espaces avec n'importe quelle séquence de caractères entre eux) et voyez ce qui se passe réellement. Pour un crédit supplémentaire, expliquez pourquoi cela se produit.
Gordon Davisson

Ajout de guillemets @GordonDavisson.
jlliagre

2

Lors de la déclaration de la variable, vous ne devez pas utiliser le signe dollar comme préfixe.

Essaye ça:

i='echo hi'; $i

1
C'est exact, mais ce n'est pas vraiment une réponse à la question du PO.
slhck

c'est pour moi VRAIMENT: P
nwgat
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.