construire une commande en concaténant une chaîne dans bash


13

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 seule fois. Les parties concaténées à la chaîne de commande sont censées être séparées par des canaux 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, j'obtiens différents messages d'erreur se rapportant généralement à la première partie de la commande (avant le premier canal).

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


Quels sont les messages d'erreur?
CameronNemo

Dans mon script (qui est un peu plus complexe que cette simplification) j'obtiens "fichier non trouvé"
Lennart Rolland

Est-il sûr de supposer qu'il infileexiste dans le répertoire actuel?
saiarcot895

Oui. dans mon code, c'est wget -O - au lieu d'un fichier. En fait, si je copie simplement la chaîne concaténée et que je la colle dans le terminal, cela fonctionne bien
Lennart Rolland

Réponses:


17

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'arguments 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 echocommande sont: " /etc/passwd", " |" (la 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.

8

Une solution à cela, pour référence future, consiste à utiliser "eval". Cela garantit que quelle que soit la façon dont la chaîne est interprétée par bash est oubliée et le tout est lu comme si elle avait été tapée directement dans un shell (ce qui est exactement ce que nous voulons).

Donc, dans l'exemple ci-dessus, remplacer

$cmd

avec

eval $cmd

résolu.


Soyez prudent cependant avec les paramètres cités. eval foo "a b"serait le même que eval foo "a" "b".
udondan

2

@waltinator a déjà expliqué pourquoi cela ne fonctionne pas comme prévu. Une autre solution consiste à utiliser bash -cpour 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
La parcimonie me dit de ne pas démarrer un autre processus avec bash -c, mais d'utiliser evalpour faire la commande dans le processus actuel.
waltinator

@waltinator bien sûr, j'utiliserais probablement eval pour cela aussi (c'est pourquoi je vous ai voté et Lennart). Je fournis juste une alternative.
terdon

0

Peut-être une meilleure façon de le faire est d'éviter d'utiliser evalet d' utiliser simplement un tableau Bash et son expansion en ligne pour construire tous les arguments, puis 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
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.