Pipeline condizionale


39

Di 'che ho la seguente pipeline:

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

In determinate condizioni, vorrei aggiungere una cmd3tra cmd2e cmd4. C'è un modo per creare una pipeline condizionale gentile senza salvare il risultato di cmd2 in un file temporaneo? Vorrei pensare a qualcosa del tipo:

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

Risposte:


36

Solo il solito &&e gli ||operatori:

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

(Si noti che non è necessaria alcuna barra rovesciata finale quando la linea termina con il tubo.)

Aggiornamento secondo l'osservazione di Jonas .
Se cmd3 può terminare con un codice di uscita diverso da zero e non si desidera catelaborare l'ingresso rimanente, invertire la logica:

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

Consiglio pratico Notato!
inverti


2
Invertire la logica non aiuta. Se catritorna con uno stato di uscita diverso da zero (comune quando si ottiene un SIGPIPE), cmd3verrà eseguito. Meglio usare if / then / else come nella risposta di Thor o altre soluzioni che evitano di correre cat.
Stéphane Chazelas,

così il gatto può essere usato per connettere le cose, un po 'come un flusso "attraverso"
Alexander Mills

19

if/ else/ fifunziona. Supponendo qualsiasi shell tipo Bourne:

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

10

Tutte le risposte fornite finora sostituiscono cmd3con cat. Puoi anche evitare di eseguire qualsiasi comando con:

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

Questo è POSIX, ma nota che se in uno bashscript in cui bashnon è presente la shmodalità (come con uno script che inizia con #! /path/to/bash), dovrai abilitare l'espansione dell'alias con shopt -s expand_aliases(o set -o posix).

Un altro approccio che non esegue ancora alcun comando non necessario è l'uso di eval:

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

O:

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

Su Linux (almeno), invece di cat, potresti usare pv -qquale usa splice()invece di read()+ write()per far passare i dati tra le due pipe, evitando così che i dati vengano spostati due volte tra il kernel e lo spazio utente.


9

Come addendum alla risposta accettata dall'uomo : sii consapevole di e-false-o gotcha e della sua interazione con i flussi. Per esempio,

true && false || echo foo

uscite foo. Non sorprendentemente,

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

e

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

entrambi in uscita baz. (Nota che echo foo | grep barè falso e non ha output). Tuttavia,

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

non genera nulla. Questo può o meno essere quello che vuoi.


Non riesco a vedere come sia rilevante qui. Il else(o ||) dovrebbe agire come un'operazione nulla mantenendo immutato l'input. Quindi sostituendo catcon le echomodifiche tutto ciò rende il codice irrilevante. Per quanto riguarda il "e-falso-o gotcha", non vedo alcuna interferenza con la pipeline: pastebin.com/u6Jq0bZe
manatwork

5
@manatwork: Una cosa che Jonas sta sottolineando è che, in [[ "${DEFINED}" ]] && cmd3 || cat, cmd3e catnon si escludono a vicenda thene elserami dello stesso if, ma che se cmd3falliscono, catverranno eseguiti anche. (Naturalmente, se cmd3riesce sempre, o se consuma tutto l'input quindi non c'è nulla di stdinper catal processo, poi la si potrebbe non importa.) Se avete bisogno di entrambi thene elserami, è meglio utilizzare un esplicito ifcomando, non &&e ||.
musiphil,

1
Grazie per la spiegazione, @musiphil. Ora capisco l'argomento di Jonas.
arte

4

Ho avuto una situazione simile che ho risolto con le funzioni bash :

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

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

3

Ecco una possibile soluzione alternativa:
concatenare le pipe con nome per creare una pseudo-pipeline:

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

2

Per riferimento futuro, se vieni qui alla ricerca di condizionale nei pesci , ecco come lo fai:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.