comando build concatenando a string no bash

13

Eu tenho um script bash que cria uma linha de comando em uma seqüência de caracteres com base em alguns parâmetros antes de executá-lo de uma só vez. As partes que são concatenadas para a cadeia de comandos devem ser separadas por pipes para facilitar um "fluxo" de dados através de cada componente.

Um exemplo muito simplificado:

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

Por alguma razão, os canos não parecem funcionar. Quando executo esse script, recebo diferentes mensagens de erro relacionadas geralmente à primeira parte do comando (antes do primeiro canal).

Portanto, minha pergunta é se é possível ou não criar um comando dessa maneira, e qual é a melhor maneira de fazê-lo?

Lennart Rolland
fonte
Quais são as mensagens de erro?
precisa saber é o seguinte
No meu script (que é um pouco mais complexo do que esta simplificação) eu recebo "arquivo não encontrado"
Lennart Rolland
É seguro supor que infileexista no diretório atual?
precisa saber é o seguinte
sim. no meu código é wget -O - em vez de um arquivo. Na verdade, se eu apenas copiar a cadeia concatenada e pasete-lo no terminal funciona muito bem
Lennart Rolland

Respostas:

17

Tudo depende de quando as coisas são avaliadas. Quando você digita $cmd, o restante da linha é passado como argumento para a primeira palavra em $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  

Isso mostra que os argumentos passados ​​para o echocomando são: " /etc/passwd", " |" (o caractere da barra vertical), " wc" e " -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.
waltinator
fonte
8

Uma solução para isso, para referência futura, é usar "eval". Isso garante que, de qualquer maneira que a string seja interpretada pelo bash, seja esquecida e a coisa toda seja lida como se tivesse sido digitada diretamente em um shell (que é exatamente o que queremos).

Portanto, no exemplo acima, substituindo

$cmd

com

eval $cmd

Resolvi-o.

Lennart Rolland
fonte
Tenha cuidado, porém, com os parâmetros citados. eval foo "a b"seria o mesmo que eval foo "a" "b".
Udondan
2

O @waltinator já explicou por que isso não funciona como o esperado. Outra maneira de contornar isso é usar bash -cpara executar seu comando:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51
Terdon
fonte
1
A parcimônia me diz para não iniciar outro processo bash -c, mas use evalpara executar o comando no processo atual.
waltinator
@waltinator claro, eu provavelmente usaria eval para isso também (e foi por isso que votei em você e em Lennart). Estou apenas fornecendo uma alternativa.
precisa
0

Possivelmente, a melhor maneira de fazer isso é evitar o uso evale apenas o uso de uma matriz Bash e sua expansão embutida para criar todos os argumentos e executá-los no comando.

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
fonte