построить команду путем объединения строки в bash

13

У меня есть скрипт bash, который строит командную строку в строке на основе некоторых параметров, прежде чем выполнять ее за один раз. Предполагается, что части, которые соединяются с командной строкой, должны быть разделены конвейерами, чтобы облегчить «поток» данных через каждый компонент.

Очень упрощенный пример:

#!/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

По некоторым причинам трубы, кажется, не работают. Когда я запускаю этот скрипт, я получаю разные сообщения об ошибках, относящиеся обычно к первой части команды (перед первым каналом).

Итак, мой вопрос: возможно ли построить команду таким образом, и как лучше это сделать?

Леннарт Роллан
источник
Каковы сообщения об ошибках?
CameronNemo
В моем сценарии (который несколько сложнее, чем это упрощение) я получаю «файл не найден»
Леннарт Роллан,
Можно ли предположить, что infileсуществует в текущем каталоге?
saiarcot895
да. в моем коде это wget -O - вместо файла. На самом деле, если я просто скопирую сцепленную строку и вставлю ее в терминал, она будет работать нормально
Леннарт Роллан,

Ответы:

17

Все зависит от того, когда все оценивается. Когда вы печатаете $cmd, вся остальная часть строки передается в качестве аргументов первому слову в $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  

Это показывает, что аргументам, переданным echoкоманде, являются: " /etc/passwd", " |" (символ вертикальной черты), " wc" и " -l".

От 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.
waltinator
источник
8

Одним из решений этой проблемы, для дальнейшего использования, является использование «eval». Это гарантирует, что любой способ интерпретации строки bash будет забыт, и все это будет прочитано так, как если бы оно было набрано непосредственно в оболочке (что именно то, что нам нужно).

Таким образом, в приведенном выше примере, заменяя

$cmd

с

eval $cmd

решил это.

Леннарт Роллан
источник
Будьте осторожны с указанными параметрами. eval foo "a b"будет так же, как eval foo "a" "b".
Удондан
2

@waltinator уже объяснил, почему это не работает, как вы ожидали. Другой способ обойти это использовать bash -cдля выполнения вашей команды:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51
terdon
источник
1
Parsimony говорит мне не запускать другой процесс bash -c, а использовать его evalдля выполнения команды в текущем процессе.
Вальтинатор
@waltinator, конечно, я бы тоже использовал eval для этого (вот почему я проголосовал за тебя и Леннарта). Я просто предоставляю альтернативу.
тердон
0

Возможно, лучший способ сделать это состоит в том, чтобы избежать использования evalи просто использования массива Bash, и это встроенное расширение для построения всех аргументов и последующего их выполнения в команде.

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
dragon788
источник