Pipeline condicional

39

Digamos que eu tenha o seguinte pipeline:

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

Sob certas condições, gostaria de adicionar um cmd3entre cmd2e cmd4. Existe uma maneira de criar um pipeline condicional tipo sem salvar o resultado do cmd2 em um arquivo temporário? Eu pensaria em algo como:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Pierre
fonte
related: stackoverflow.com/questions/2520085/…
Ciro Santilli escreveu:

Respostas:

36

Apenas o habitual &&e ||operadores:

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

(Observe que nenhuma barra invertida à direita é necessária quando a linha termina com tubo.)

Atualização de acordo com a observação de Jonas .
Se o cmd3 puder terminar com um código de saída diferente de zero e você não desejar catprocessar a entrada restante, inverta a lógica:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
homem a trabalhar
fonte
Dica útil Observado!
Invert
6
Esteja ciente das dicas: unix.stackexchange.com/a/39043/19055
Jonas Kölker
2
Inverter a lógica não ajuda. Se catretornar com um status de saída diferente de zero (comum ao obter um SIGPIPE), cmd3será executado. É melhor usar if / then / else como na resposta de Thor ou outras soluções que evitam a execução cat.
Stéphane Chazelas
assim gato pode ser usado para coisas de conexão, como uma espécie de "através de" fluxo
Alexander Mills
19

if/ else/ fiObras. Supondo que qualquer shell tipo Bourne:

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

Todas as respostas dadas até agora são substituídas cmd3por 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

Esse é o POSIX, mas observe que, se em um bashscript onde bashnão está em sh-mode (como em um script começando com #! /path/to/bash), você precisará 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 os pv -qusos em splice()vez de read()+ write()para passar os dados entre os dois canais, evitando que os dados sejam movidos duas vezes entre o kernel e o espaço do usuário.

Stéphane Chazelas
fonte
9

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

true && false || echo foo

saídas foo. Não surpreendentemente,

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

e

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

ambos de saída baz. (Observe que echo foo | grep baré falso e não tem saída). Contudo,

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

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

Jonas Kölker
fonte
Não vejo como isso é relevante aqui. O else(ou ||) deve atuar como uma operação nula, mantendo a entrada inalterada. Portanto, substituir catpor echoalterações tudo torna seu código irrelevante. Em relação ao “and-false-or gotcha”, não vejo interferência no pipeline: pastebin.com/u6Jq0bZe
manatwork
5
@manatwork: Uma coisa que Jonas está apontando é que, na [[ "${DEFINED}" ]] && cmd3 || cat, cmd3e catnão são o mutuamente exclusivas thene elseramos da mesma if, mas que, se cmd3falhar, então cattambém será executado. (Claro, se cmd3sempre tem sucesso, ou se consome toda a entrada de modo que não há nada deixado em stdinpara catpara processar, então o que pode não importa.) Se você precisa de ambos thene elsefiliais, é melhor usar um explícita ifde comando, não &&e ||.
Musiphil 02/12/12
11
Obrigado pela explicação, @musiphil. Agora eu entendo o argumento de Jonas.
Manatwork
4

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

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

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

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
Claudio
fonte
2

Para referência futura, se você vier aqui procurando tubos condicionais em peixes , é 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:]'
Jorge Bucaran
fonte