17 votes

construire une commande en concaténant une chaîne de caractères en bash

J'ai un bash script qui construit une ligne de commande dans une chaîne basée sur certains paramètres avant de l'exécuter en une seule fois. Les parties qui sont concaténées à la chaîne de commande sont censées être séparées par des tuyaux pour faciliter un "streaming" de données à travers chaque composant.

Un exemple très simplifié :

#!/bin/bash
part1=gzip -c
part2=some_other_command
cmd="cat infile"

if [ ! "$part1" = "" ]
then
    cmd+=" | $part1"
fi

if [ ! "$part2" = "" ]
then
    cmd+=" | $part2"
fi

cmd+="> outfile"
#show command. It looks ok
echo $cmd
#run the command. fails with pipes
$cmd

Pour une raison quelconque, les tuyaux ne semblent pas fonctionner. Lorsque j'exécute ce script, je reçois différents messages d'erreur concernant généralement la première partie de la commande (avant le premier tuyau).

Ma question est donc de savoir s'il est possible ou non de construire une commande de cette manière, et quelle est la meilleure façon de le faire ?

18voto

waltinator Points 32821

Tout dépend du moment où les choses sont évaluées. Lorsque vous tapez $cmd tout le reste de la ligne est passé comme argument au premier mot de la ligne $cmd .

walt@spong:~(0)$ a="cat /etc/passwd"
walt@spong:~(0)$ b="| wc -l"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
cat /etc/passwd | wc -l
walt@spong:~(0)$ $c
cat: invalid option -- 'l'
Try 'cat --help' for more information.
walt@spong:~(1)$ eval $c
62
walt@spong:~(0)$ a="echo /etc/passwd"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
echo /etc/passwd | wc -l
walt@spong:~(0)$ $c
/etc/passwd | wc -l
walt@spong:~(0)$ $c |od -bc
0000000 057 145 164 143 057 160 141 163 163 167 144 040 174 040 167 143  
          /   e   t   c   /   p   a   s   s   w   d       |       w   c  
0000020 040 055 154 012  
              -   l  \n  
0000024
walt@spong:~(0)$ eval $c
1  

Cela montre que les arguments passés à la echo commande sont : " /etc/passwd ", " | "(le caractère barre verticale), " wc " et " -l ".

De man bash :

eval [arg ...]  
    The  args  are read and concatenated together into   
    a single command.  This command is then read and  
    executed by the shell, and its exit status is returned  
    as the value of eval.  If there are no args, or only null  
    arguments, eval returns 0.

8voto

Lennart Rolland Points 711

Une solution à ce problème, pour référence future, est d'utiliser "eval". Cela garantit que quelle que soit la manière dont la chaîne est interprétée par bash est oubliée et que l'ensemble est lu comme s'il était tapé directement dans un Shell (ce qui est exactement ce que nous voulons).

Ainsi, dans l'exemple ci-dessus, en remplaçant

$cmd

avec

eval $cmd

l'a résolu.

2voto

Stewart Points 1385

@waltinator a déjà expliqué pourquoi cela ne fonctionne pas comme prévu. Une autre façon de contourner le problème est d'utiliser bash -c pour exécuter votre commande :

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51

0voto

dragon788 Points 1216

Une meilleure façon de procéder est peut-être d'éviter d'utiliser la fonction eval et d'utiliser un tableau Bash et son expansion en ligne pour construire tous les arguments et ensuite les exécuter contre la commande.

runcmd=() # This is slightly messier than declare -a but works
for cmd in $part1 $part2 $part3; do runcmd+="| $cmd "; done
cat infile ${runcmd[@]} # You might be able to do $basecmd ${runcmd[@]}
# but that sometimes requires an `eval` which isn't great

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X