Pipeline condicional

37

Digamos que eu tenha o seguinte pipeline:

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

Sob determinadas condições, gostaria de adicionar um cmd3 entre cmd2 e cmd4 . Existe uma maneira de criar um pipeline condicional sem salvar o resultado de cmd2 em um arquivo temporário? Eu pensaria em algo como:

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

7 respostas

33

Apenas os usuais operadores && e || :

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

(Observe que nenhuma barra invertida é necessária quando a linha termina com o pipe.)

Atualizar de acordo com a Observação de Jonas .
Se cmd3 puder terminar com código de saída diferente de zero e você não quiser que cat processe a entrada restante, inverta a lógica:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
    
por 10.05.2012 / 10:02
18

if / else / fi funciona. Assumindo qualquer shell parecido com Bourne:

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

Todas as respostas dadas até agora substituem cmd3 por cat . Você também pode evitar executar qualquer comando com:

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

Isso é POSIX, mas observe que, se em um script bash , em que bash não está em sh -mode (como em um script que começa com #! /path/to/bash ), será necessário ativar a expansão de alias com shopt -s expand_aliases (ou set -o posix ).

Outra abordagem que ainda não executa nenhum comando desnecessário é usar eval:

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

Ou:

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

No Linux (pelo menos), em vez de cat , você pode usar pv -q , que usa splice() em vez de read() + write() para passar os dados entre os dois canais, o que evita ter os dados movido duas vezes entre o kernel e o espaço do usuário.

    
por 05.02.2013 / 23:19
8

Como um adendo à resposta aceita do manatwork : esteja ciente do e-false ou gotcha e sua interação com fluxos. Por exemplo,

true && false || echo foo

gera foo . Não surpreendentemente,

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

e

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

ambas as saídas baz . (Observe que echo foo | grep bar é falso e não tem saída). No entanto,

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

não produz nada. Isso pode ou não ser o que você quer.

    
por 20.05.2012 / 13:06
4

Eu tive uma situação semelhante que resolvi com bash funções :

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

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
    
por 19.05.2012 / 22:19
2

Aqui está uma solução alternativa possível:
concatenando pipes nomeados para criar um 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
    
por 01.09.2016 / 12:37
2

Para referência futura, se você vir aqui procurando por condicionais em fish , é assim que você faz:

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

Tags