web-dev-qa-db-fra.com

commande de construction en concaténant string dans bash

J'ai un script bash qui construit une ligne de commande dans une chaîne basée sur certains paramètres avant de l'exécuter en une fois. Les parties concaténées à la chaîne de commande sont supposées être séparées par des canaux afin de faciliter la "transmission en continu" des données via 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, différents messages d'erreur se rapportant généralement à la première partie de la commande (avant le premier canal) sont générés.

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?

12
Lennart Rolland

Tout dépend du moment où les choses sont évaluées. Lorsque vous tapez $cmd, le reste de la ligne est transmis en tant qu'argument au premier mot de $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 commande echo sont: "/etc/passwd", "|" (caractère de 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.
16
waltinator

Une solution à cela, pour référence future, consiste à utiliser "eval". Cela garantit que la chaîne interprétée par bash, quelle que soit la manière dont elle est interprétée, soit oubliée et lue dans son intégralité comme si elle était tapée directement dans un shell (ce qui est exactement ce que nous voulons).

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

$cmd

avec

eval $cmd

résolu.

7
Lennart Rolland

@waltinator a déjà expliqué pourquoi cela ne fonctionnait pas comme prévu. Une autre solution consiste à 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
1
terdon

Une meilleure façon de le faire est peut-être d'éviter d'utiliser eval et d'utiliser simplement un tableau de Bash et son développement en ligne pour construire tous les arguments, puis les exécuter à l'aide de 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
0
dragon788