Rurociąg warunkowy

39

Powiedzmy, że mam następujący potok:

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

Pod pewnymi warunkami chciałbym dodać cmd3między cmd2i cmd4. Czy istnieje sposób, aby utworzyć miły potok warunkowy bez zapisywania wyniku polecenia cmd2 w pliku tymczasowym? Pomyślałbym o czymś takim jak:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Pierre
źródło
powiązane: stackoverflow.com/questions/2520085/...
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

36

Tylko zwykli &&i ||operatorzy:

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

(Należy pamiętać, że nie jest wymagany końcowy ukośnik odwrotny, gdy linia kończy się rurą)

Zaktualizuj zgodnie z obserwacją Jonasa .
Jeśli cmd3 może zakończyć się niezerowym kodem wyjścia i nie chcesz catprzetwarzać pozostałych danych wejściowych, odwróć logikę:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
człowiek w pracy
źródło
Przydatna wskazówka Uwaga!
odwróć
6
Bądź świadomy gotchas: unix.stackexchange.com/a/39043/19055
Jonas Kölker
2
Odwrócenie logiki nie pomaga. Jeśli catpowróci z niezerowym statusem wyjścia (często występującym podczas uzyskiwania SIGPIPE), cmd3zostanie uruchomiony. Lepiej użyj jeśli / wtedy / else jak w odpowiedzi Thora lub innych rozwiązaniach, które unikają uruchamiania cat.
Stéphane Chazelas
więc kota można używać do łączenia rzeczy, coś w rodzaju strumienia „przelotowego”
Alexander Mills,
19

if/ else/ fidziała. Zakładając dowolną powłokę podobną do Bourne'a:

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

Wszystkie dotychczasowe odpowiedzi zastępują cmd3się cat. Możesz także uniknąć uruchamiania dowolnego polecenia za pomocą:

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

To POSIX, ale zauważ, że jeśli w bashskrypcie, który bashnie jest w trybie sh-mode (jak w skrypcie rozpoczynającym się od #! /path/to/bash), musisz włączyć rozszerzenie aliasu za pomocą shopt -s expand_aliases(lub set -o posix).

Innym podejściem, które wciąż nie uruchamia niepotrzebnych poleceń, jest użycie polecenia eval:

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

Lub:

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

W Linuksie (przynajmniej) zamiast tego catmożesz użyć, pv -qktóry używa splice()zamiast read()+ write()do przesyłania danych między dwoma potokami, co pozwala uniknąć dwukrotnego przenoszenia danych między jądrem a przestrzenią użytkownika.

Stéphane Chazelas
źródło
9

Jako dodatek do zaakceptowanej odpowiedzi manatwork : bądź świadomy i-fałszywej-lub gotcha i jej interakcji ze strumieniami. Na przykład,

true && false || echo foo

wyjścia foo. Nie jest zaskoczeniem,

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

i

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

oba wyjścia baz. (Zauważ, że echo foo | grep barjest to fałsz i nie ma danych wyjściowych). Jednak,

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

nic nie produkuje. To może, ale nie musi być to, czego chcesz.

Jonas Kölker
źródło
Nie widzę tutaj, żeby to miało znaczenie. else(A ||) powinien służyć jako działania zerowego utrzymaniu wejście. Więc zastąpienie catze echozmiany wszystko co Twój kod znaczenia. Jeśli chodzi o „i-fałszywe-lub gotcha”, nie widzę żadnej ingerencji w rurociąg: pastebin.com/u6Jq0bZe
manatwork
5
@manatwork: Jedno Jonas wskazując na to, że [[ "${DEFINED}" ]] && cmd3 || cat, cmd3i catnie są wzajemnie się wykluczają theni elseoddziałów tej samej if, ale jeśli cmd3nie, to catbędą również wykonywane. (Oczywiście, jeśli cmd3zawsze się powiedzie, lub gdy pobiera wszystkie dane wejściowe, więc nie ma nic w stdinna catdo procesu, to nie może to kwestia). Jeśli potrzebują zarówno theni elsegałęzie, to lepiej użyć wyraźne ifpolecenie, a nie &&a ||.
musiphil
1
Dziękuję za wyjaśnienie, @musiphil. Teraz rozumiem argument Jonasa.
manatwork
4

Miałem podobną sytuację, którą rozwiązałem za pomocą funkcji bash :

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

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
jfg956
źródło
3

Oto alternatywne możliwe rozwiązanie:
konkatenacja nazwanych potoków w celu utworzenia pseudo-potoku:

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

Na przyszłość, jeśli przyjeżdżasz tutaj w poszukiwaniu ryb warunkowych , możesz to zrobić w następujący sposób:

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