buduj komendę, łącząc ciąg w bash

13

Mam skrypt bash, który buduje wiersz polecenia w ciągu opartym na niektórych parametrach przed wykonaniem go za jednym razem. Części, które są powiązane z ciągiem poleceń, powinny być oddzielone potokami, aby ułatwić „strumieniowanie” danych przez każdy komponent.

Bardzo uproszczony przykład:

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

Z jakiegoś powodu rury nie działają. Kiedy uruchamiam ten skrypt, otrzymuję różne komunikaty o błędach dotyczące zazwyczaj pierwszej części polecenia (przed pierwszym potokiem).

Moje pytanie brzmi więc, czy możliwe jest zbudowanie polecenia w ten sposób i jaki jest najlepszy sposób, aby to zrobić?

Lennart Rolland
źródło
Jakie są komunikaty o błędach?
CameronNemo
W moim skrypcie (który jest nieco bardziej złożony niż to uproszczenie) otrzymuję komunikat „nie znaleziono pliku”
Lennart Rolland
Czy można bezpiecznie założyć, że infileistnieje w bieżącym katalogu?
saiarcot895
tak. w moim kodzie jest to wget -O - zamiast pliku. Właściwie, jeśli po prostu skopiuję skonkatenowany ciąg i wstawię go do terminala, będzie działał dobrze
Lennart Rolland

Odpowiedzi:

17

Wszystko zależy od tego, kiedy wszystko zostanie ocenione. Podczas pisania $cmdcała reszta wiersza jest przekazywana jako argument do pierwszego słowa w $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  

To pokazuje, że argumentami przekazanymi do echopolecenia są: „ /etc/passwd”, „ |” (znak pionowego paska), „ wc” i „ -l”.

Od 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
źródło
8

Jednym z rozwiązań tego problemu na przyszłość jest użycie „eval”. Zapewnia to, że niezależnie od tego, w jaki sposób łańcuch interpretowany jest przez bash, zostaje zapomniany, a całość jest odczytywana tak, jakby była wpisana bezpośrednio w powłoce (dokładnie tego chcemy).

W powyższym przykładzie zastąpienie

$cmd

z

eval $cmd

rozwiązałem to.

Lennart Rolland
źródło
Uważaj jednak na podane parametry. eval foo "a b"byłby taki sam jak eval foo "a" "b".
udondan
2

@waltinator już wyjaśnił, dlaczego to nie działa zgodnie z oczekiwaniami. Innym sposobem jest bash -cwykonanie polecenia:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51
terdon
źródło
1
Parsimony mówi mi, żebym nie uruchamiał innego procesu bash -c, ale evalpowinienem wykonać polecenie w bieżącym procesie.
waltinator
@waltinator na pewno, prawdopodobnie użyłbym eval również do tego (dlatego głosowałem za tobą i Lennartem). Podaję tylko alternatywę.
terdon
0

Być może lepszym sposobem na to jest unikanie używania evali po prostu używania tablicy Bash oraz wbudowanego rozszerzenia do gromadzenia wszystkich argumentów, a następnie wykonywania ich przeciwko poleceniu.

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
źródło