Bedingte Pipeline

39

Angenommen, ich habe die folgende Pipeline:

cmd1 < input.txt |\
cmd2 |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt

Unter bestimmten Voraussetzungen möchte ich ein cmd3zwischen cmd2und hinzufügen cmd4. Gibt es eine Möglichkeit, eine bedingte Pipeline zu erstellen, ohne das Ergebnis von cmd2 in einer temporären Datei zu speichern? Ich würde an etwas denken wie:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Pierre
quelle

Antworten:

36

Nur das Übliche &&und ||Operatoren:

cmd1 < input.txt |
cmd2 |
( [[ "${DEFINED}" ]] && cmd3 || cat ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

(Beachten Sie, dass kein abschließender Backslash erforderlich ist, wenn die Linie mit einer Pipe endet.)

Update nach Jonas 'Beobachtung .
Wenn cmd3 möglicherweise mit einem Exit-Code ungleich Null endet und Sie catdie verbleibende Eingabe nicht verarbeiten möchten , kehren Sie die Logik um:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Mann bei der Arbeit
quelle
Handlicher Tipp
Invertzucker
6
Seien Sie sich der Fallstricke bewusst: unix.stackexchange.com/a/39043/19055
Jonas Kölker
2
Das Umkehren der Logik hilft nicht. Wenn catmit einem Exit-Status ungleich Null zurückgegeben wird (häufig beim Abrufen eines SIGPIPE), cmd3wird ausgeführt. Verwenden Sie besser if / then / else wie in Thors Antwort oder andere Lösungen, die nicht ausgeführt werden können cat.
Stéphane Chazelas
so kann cat verwendet werden, um dinge zu verbinden, eine art "durch" -stream
alexander
19

if/ else/ fifunktioniert. Angenommen, eine Bourne-ähnliche Shell:

cmd1 < input.txt |
cmd2 |
if [ -n "$DEFINED" ]; then cmd3; else cat; fi |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
Thor
quelle
10

Alle bisher gegebenen Antworten ersetzen cmd3mit cat. Sie können auch die Ausführung von Befehlen vermeiden, wenn Sie:

if [ -n "$DEFINE" ]; then
  alias maybe_cmd3='cmd3 |'
else
  alias maybe_cmd3=''
fi
cmd1 |
cmd2 |
maybe_cmd3
cmd4 |
... |
cmdN > result.txt

Das ist POSIX, aber beachten Sie, dass Sie in einem bashSkript, in dem bashsich kein sh-mode befindet (wie bei einem Skript, das mit beginnt #! /path/to/bash), die Alias-Erweiterung mit shopt -s expand_aliases(oder set -o posix) aktivieren müssen .

Ein anderer Ansatz, bei dem immer noch keine unnötigen Befehle ausgeführt werden, ist die Verwendung von eval:

if [ -n "$DEFINE" ]; then
  maybe_cmd3='cmd3 |'
else
  maybe_cmd3=''
fi
eval "
  cmd1 |
  cmd2 |
  $maybe_cmd3
  cmd4 |
  ... |
  cmdN > result.txt"

Oder:

eval "
  cmd1 |
  cmd2 |
  ${DEFINE:+cmd3 |}
  cmd4 |
  ... |
  cmdN > result.txt"

Unter Linux (zumindest), statt cat, könnten Sie verwenden , pv -qwelche verwenden splice()anstelle von read()+ write()die Daten über zwischen den beiden Rohren zu passieren , die zweimal zwischen Kernel- und User - Space der Daten bewegt vermeiden.

Stéphane Chazelas
quelle
9

Als Ergänzung zur akzeptierten Antwort von manatwork : Achten Sie auf das And -False-or-Gotcha und dessen Interaktion mit Streams. Zum Beispiel,

true && false || echo foo

Ausgänge foo. Nicht überraschend,

true && (echo foo | grep bar) || echo baz

und

echo foo | (true && grep bar || echo baz)

beide ausgang baz. (Beachten Sie, dass echo foo | grep barfalsch ist und keine Ausgabe hat). Jedoch,

echo foo | (true && grep bar || sed -e abaz)

gibt nichts aus. Dies kann oder kann nicht das sein, was Sie wollen.

Jonas Kölker
quelle
Ich kann nicht sehen, wie wichtig das hier ist. Das else(oder ||) sollte als Nulloperation fungieren und die Eingabe unverändert lassen. Ersetzen Sie also catdurch echoÄnderungen alles, was Ihren Code irrelevant macht. Die in Bezug auf „und-Falsch oder Gotcha“, sehe ich keine Interferenz mit der Pipeline: pastebin.com/u6Jq0bZe
Manatwork
5
@manatwork: Eine Sache , Jonas Hinweis darauf ist , dass in [[ "${DEFINED}" ]] && cmd3 || cat, cmd3und catsind nicht gegenseitig aus thenund elseZweige derselben if, aber wenn cmd3nicht, dann catwird auch ausgeführt werden. (Natürlich, wenn cmd3gelingt es immer wieder , oder wenn sie alle die Eingabe verbraucht , so gibt es nichts mehr übrig in stdinfür catzu verarbeiten, dann die es keine Rolle kann.) Wenn Sie beide brauchen thenund elseZweige, es besser ist , einen expliziten zu verwenden ifBefehl nicht &&und ||.
Musiphil
1
Vielen Dank für die Erklärung, @musiphil. Jetzt verstehe ich Jonas Argument.
Manatwork
4

Ich hatte eine ähnliche Situation, die ich mit Bash- Funktionen gelöst habe :

if ...; then
  my_cmd3() { cmd3; }
else
  my_cmd3() { cat; }
if

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
jfg956
quelle
3

Hier ist eine alternative mögliche Lösung:
Verketten von Named Pipes, um eine Pseudo-Pipeline zu erstellen:

dir="$(mktemp -d)"
fifo_n='0'
function addfifo {
 stdin="$dir/$fifo_n"
((fifo_n++))
[ "$1" ] || mkfifo "$dir/$fifo_n"
stdout="$dir/$fifo_n"; }

addfifo; echo "The slow pink fox crawl under the brilliant dog" >"$stdout" &

# Restoring the famous line: "The quick brown fox jumps over the lazy dog"
# just replace 'true' with 'false' in any of the 5 following lines and the pseudo-pipeline will still work
true && { addfifo; sed 's/brilliant/lazy/' <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/under/over/'     <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/crawl/jumps/'    <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/pink/brown/'     <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/slow/quick/'     <"$stdin" >"$stdout" & }

addfifo -last; cat <"$stdin"

wait; rm -r "$dir"; exit 0
Claudio
quelle
2

Wenn Sie hierher kommen, um nach bedingten Pfeifen in Fischen zu suchen , gehen Sie wie folgt vor:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
Jorge Bucaran
quelle