195 votes

Comment puis-je stocker une commande dans une variable dans un script shell?

Je voudrais stocker une commande à utiliser ultérieurement dans une variable (pas la sortie de la commande, mais la commande elle-même).

J'ai un script simple comme suit:

command="ls";
echo "Commande: $command"; #La sortie est: Commande: ls

b=`$command`;
echo $b; #La sortie est public_html REV test... (la commande a fonctionné avec succès)

Cependant, lorsque j'essaie quelque chose de plus compliqué, cela échoue. Par exemple, si je fais

command="ls | grep -c '^'";

La sortie est:

Commande: ls | grep -c '^'
ls: ne peut pas accéder à |: Aucun fichier ou dossier de ce type
ls: ne peut pas accéder à grep: Aucun fichier ou dossier de ce type
ls: ne peut pas accéder à '^': Aucun fichier ou dossier de ce type

Comment pourrais-je stocker une telle commande (avec des pipes/des commandes multiples) dans une variable pour une utilisation ultérieure?

15 votes

Utilisez une fonction!

230voto

Erik Points 38942

Utiliser eval :

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

37 votes

$(...) est maintenant recommandé au lieu de guillemets inversés. y=$(eval $x) mywiki.wooledge.org/BashFAQ/082

0 votes

Pendant que cela était confus au début, tout se déroule comme prévu dans les deux sens. eval est bon. Merci.

27 votes

eval est une pratique acceptable uniquement si vous faites confiance au contenu de vos variables. Si vous exécutez, disons, x="ls $name | wc" (ou même x="ls '$name' | wc"), alors ce code est un moyen rapide pour des vulnérabilités d'injection ou d'escalade de privilèges si cette variable peut être définie par quelqu'un avec moins de privilèges. (Parcourir tous les sous-répertoires dans /tmp, par exemple ? Vous feriez mieux de faire confiance à chaque utilisateur du système pour qu'aucun ne crée un nommé $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').

116voto

Inian Points 36223

Ne pas utiliser eval! Il comporte un risque majeur d'introduction d'une exécution de code arbitraire.

BashFAQ-50 - J'essaie de mettre une commande dans une variable, mais les cas complexes échouent toujours.

Mettez-le dans un tableau et développez tous les mots avec des guillemets doubles "${arr[@]}" pour ne pas laisser le IFS diviser les mots en raison de Word Splitting.

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

et voir le contenu du tableau à l'intérieur. Le declare -p vous permet de voir le contenu du tableau à l'intérieur avec chaque paramètre de commande dans des indices séparés. Si un tel argument contient des espaces, un guillemetage à l'intérieur lors de l'ajout au tableau empêchera qu'il soit divisé en raison de Word-Splitting.

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

et exécutez les commandes comme

"${cmdArgs[@]}"
23:15:18

(ou) utilisez tous ensemble une fonction bash pour exécuter la commande,

cmd() {
   date '+%H:%M:%S'
}

et appelez la fonction simplement

cmd

POSIX sh n'a pas de tableaux, donc au mieux, vous pouvez construire une liste d'éléments dans les paramètres positionnels. Voici une manière POSIX sh de lancer un programme de messagerie

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

Remarquez que cette approche ne peut traiter que des commandes simples sans redirections. Elle ne peut pas traiter les redirections, les pipelines, les boucles for/while, les instructions conditionnelles, etc.

Un autre cas d'utilisation courant est celui de l'exécution de curl avec plusieurs champs d'en-tête et un corps de requête. Vous pouvez toujours définir des arguments comme ci-dessous et invoquer curl sur le contenu du tableau étendu

curlArgs=('-H' "headerclé: valeur" '-H' "2èmeheaderclé: 2èmevaleur")
curl "${curlArgs[@]}"

Un autre exemple,

payload='{}'
hostURL='http://google.com'
authToken='quelqueToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

maintenant que les variables sont définies, utilisez un tableau pour stocker vos arguments de commande

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

et maintenant faites une expansion correctement citée

curl "${curlCMD[@]}"

0 votes

Cela ne fonctionne pas pour moi, j'ai essayé Command=('echo aaa | grep a') et "${Command[@]}", en espérant que cela exécute littéralement la commande echo aaa | grep a. Ce n'est pas le cas. Je me demande s'il y a un moyen sûr de remplacer eval, mais il semble que chaque solution ayant la même puissance que eval pourrait être dangereuse. N'est-ce pas?

0 votes

En bref, comment cela fonctionne-t-il si la chaîne originale contient un tuyau '|' ?

2 votes

@ Étudiant, si votre chaîne originale contient un pipeline, cette chaîne doit passer par les parties non sécurisées de l'analyseur bash pour être exécutée en tant que code. N'utilisez pas une chaîne dans ce cas; utilisez plutôt une fonction: Commande() { echo aaa | grep a; } - après quoi vous pouvez simplement exécuter Commande, ou résultat=$(Commande), ou similaire.

45voto

Nate Points 870
var=$(echo "asdf")
echo $var
# => asdf

En utilisant cette méthode, la commande est immédiatement évaluée et sa valeur de retour est stockée.

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (attendre quelques secondes)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

La même chose avec les backticks

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (attendre quelques secondes)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

En utilisant eval dans le $(...) ne le fera pas évaluer plus tard:

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (attendre quelques secondes)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

En utilisant eval, il est évalué lorsque eval est utilisé:

stored_date="date" # < stocker la commande elle-même
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (attendre quelques secondes)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ L'heure a changé

Dans l'exemple ci-dessus, si vous devez exécuter une commande avec des arguments, placez-les dans la chaîne que vous stockez:

stored_date="date -u"
# ...

Pour les scripts Bash, cela est rarement pertinent, mais une dernière note. Faites attention avec eval. N'évaluez que les chaînes que vous contrôlez, jamais les chaînes provenant d'un utilisateur non fiable ou construites à partir de données d'entrée d'utilisateur non fiables.

0 votes

Cela ne résout pas le problème original où la commande contient un pipe '|'.

0 votes

@Nate, notez que eval $stored_date peut être assez bon lorsque stored_date contient uniquement date, mais eval "$stored_date" est beaucoup plus fiable. Exécutez str=$'printf \' * %s\\n\' *'; eval "$str" avec et sans les guillemets autour du dernier "$str" pour un exemple. :)

0 votes

@CharlesDuffy Merci, j'avais oublié de citer. Je parie que mon linter aurait protesté si j'avais pris la peine de le lancer.

6voto

Derek Hazell Points 69

Pour bash, stockez votre commande de cette façon :

commande="ls | grep -c '^'"

Exécutez votre commande comme ceci :

echo $commande | bash

3 votes

Je ne suis pas sûr mais il se peut que cette manière d'exécuter la commande présente les mêmes risques que l'utilisation de 'eval'.

2 votes

En outre, vous ruinez le contenu de la variable en ne le citant pas lorsque vous utilisez la balise echo. Si command était la chaîne cd /tmp && echo *, il affichera les fichiers dans le répertoire actuel, pas dans /tmp. Voir aussi Quand mettre des guillemets autour d'une variable shell

0 votes

Pour être clair echo "$command" | bash

1voto

Cyberience Points 145

Pas sûr pourquoi tant de réponses compliquent les choses! utiliser alias [commande] 'string à exécuter' exemple:

alias dir='ls -l'

./dir
[jolie liste de fichiers]

0 votes

Il y a beaucoup de problèmes avec les alias. Pour une chose, ils ne se développent pas dans les scripts, ce qui est évidemment ennuyeux si vous écrivez un script (et donc votre question est pertinente sur Stack Overflow). D'autre part, vous ne pouvez pas transmettre des arguments positionnels aux alias, vous devez simplement les absorber le reste de votre ligne de commande sans y accéder. De plus, les règles d'analyse sont pointilleuses, vous ne pouvez donc pas activer l'expansion des alias et utiliser l'alias dans la même ligne de commande. En fin de compte, utilisez une fonction, comme tout le monde vous le disait tout le long.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X