Befehl erstellen durch Verketten eines Strings in der Bash

13

Ich habe ein Bash-Skript, das eine Befehlszeile in einer Zeichenfolge basierend auf einigen Parametern erstellt, bevor es auf einmal ausgeführt wird. Die Teile, die mit der Befehlszeichenfolge verknüpft sind, sollen durch Pipes getrennt werden, um ein "Streaming" von Daten durch jede Komponente zu ermöglichen.

Ein sehr vereinfachtes Beispiel:

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

Aus irgendeinem Grund scheinen die Pfeifen nicht zu funktionieren. Wenn ich dieses Skript ausführe, erhalte ich verschiedene Fehlermeldungen, die sich normalerweise auf den ersten Teil des Befehls beziehen (vor der ersten Pipe).

Meine Frage ist also, ob es möglich ist, einen Befehl auf diese Weise zu erstellen, und wie lässt sich dies am besten erreichen?

Lennart Rolland
quelle
Was sind die Fehlermeldungen?
CameronNemo
In meinem Skript (das etwas komplexer ist als diese Vereinfachung) erhalte ich "Datei nicht gefunden"
Lennart Rolland
Ist es sicher anzunehmen, dass infilees im aktuellen Verzeichnis existiert?
Saiarcot895
Ja. In meinem Code ist es wget -O - anstelle einer Datei. Eigentlich, wenn ich nur die verkettete Zeichenfolge kopiere und sie in das Terminal einfüge, läuft sie einwandfrei
Lennart Rolland,

Antworten:

17

Es hängt alles davon ab, wann die Dinge bewertet werden. Bei der Eingabe $cmdwird der gesamte Rest der Zeile als Argument an das erste Wort in übergeben $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  

Dies zeigt, dass die an den echoBefehl übergebenen Argumente " /etc/passwd", " |" (das vertikale Strichzeichen), " wc" und " -l" sind.

Von 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
quelle
8

Eine Lösung für dieses Problem besteht in der Verwendung von "eval". Dies stellt sicher, dass die Art und Weise, in der der String von der Bash interpretiert wird, vergessen wird und das Ganze so gelesen wird, als ob es direkt in eine Shell geschrieben worden wäre (was genau das ist, was wir wollen).

Also im obigen Beispiel ersetzen

$cmd

mit

eval $cmd

Ich habe es gelöst.

Lennart Rolland
quelle
Seien Sie vorsichtig mit den angegebenen Parametern. eval foo "a b"wäre das gleiche wie eval foo "a" "b".
Udondan
2

@waltinator hat bereits erklärt, warum dies nicht wie erwartet funktioniert. Eine andere Möglichkeit besteht bash -cdarin, Ihren Befehl auszuführen:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51
terdon
quelle
1
Parsimony weist mich an, keinen anderen Prozess zu starten bash -c, sondern evalden Befehl im aktuellen Prozess auszuführen.
Waltinator
@waltinator sicher, ich würde eval wahrscheinlich auch dafür verwenden (weshalb ich dich und Lennart empört habe). Ich biete nur eine Alternative.
Terdon
0

Möglicherweise ist es besser, evalein Bash-Array und dessen Inline-Erweiterung zu vermeiden und nur zu verwenden, um alle Argumente aufzubauen und sie dann gegen den Befehl auszuführen.

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
quelle