Pipeline conditionnel


39

Disons que j'ai le pipeline suivant:

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

Sous certaines conditions, j'aimerais ajouter un cmd3entre cmd2et cmd4. Existe-t-il un moyen de créer un type de pipeline conditionnel sans enregistrer le résultat de cmd2 dans un fichier temporaire? Je penserais à quelque chose comme:

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

Réponses:


36

Juste l'habituel &&et les ||opérateurs:

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

(Notez qu'aucune barre oblique inverse n'est nécessaire lorsque la ligne se termine par un tuyau.)

Mise à jour selon l'observation de Jonas .
Si cmd3 peut se terminer avec un code de sortie différent de zéro et que vous ne souhaitez catpas traiter l'entrée restante, inversez la logique:

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

Conseil pratique noté!
inverser

6
Soyez conscient des pièges
Jonas Kölker

2
Inverser la logique n'aide pas. Si catrenvoie avec un statut de sortie différent de zéro (commun lors de l'obtention d'un SIGPIPE), alors cmd3sera exécuté. Mieux vaut utiliser si / alors / sinon comme dans la réponse de Thor ou d'autres solutions qui évitent de courir cat.
Stéphane Chazelas

alors chat peut être utilisé pour connecter des choses, un peu comme un flux "de
Alexander Mills

19

if/ else/ fifonctionne. En supposant que tout shell Bourne-like:

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

10

Toutes les réponses données jusqu'à présent remplacent cmd3par cat. Vous pouvez également éviter d’exécuter une commande avec:

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

C'est POSIX, mais notez que si dans un bashscript où bashn'est pas en sh-mode (comme avec un script commençant par #! /path/to/bash), vous devez activer le développement d'alias avec shopt -s expand_aliases(ou set -o posix).

Une autre approche qui n'exécute toujours pas de commande inutile consiste à utiliser 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"

Sous Linux (au moins), au lieu de cat, vous pouvez utiliser pv -qlequel utilise splice()au lieu de read()+ write()pour transmettre les données entre les deux canaux, ce qui évite que les données soient déplacées deux fois entre le noyau et l’espace utilisateur.


9

En tant qu'additif à la réponse acceptée par manatwork : soyez conscient du et du faux et du gotcha et de son interaction avec les flux. Par exemple,

true && false || echo foo

sorties foo. Sans surprise,

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

et

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

les deux sorties baz. (Notez que echo foo | grep barc'est faux et n'a pas de sortie). cependant,

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

ne sort rien. Cela peut être ou ne pas être ce que vous voulez.


Je ne vois pas en quoi cela est pertinent ici. Le else(ou ||) doit agir comme une opération nulle en conservant l’entrée inchangée. Donc, en remplaçant catpar des echomodifications, tout ce qui rend votre code inutile. En ce qui concerne le "et-faux-ou-gotcha", je ne vois aucune interférence avec le pipeline: pastebin.com/u6Jq0bZe
manatwork

5
@manatwork: Jonas fait remarquer que, dans [[ "${DEFINED}" ]] && cmd3 || cat, cmd3et catne sont pas mutuellement exclusives thenet les elsebranches du même if, mais que si cmd3échoue, alors catsera également exécuté. (Bien sûr, si cmd3réussit toujours, ou si elle consomme toutes les entrées donc il n'y a rien à stdinpour catà traiter, puis il importe peu.) Si vous avez besoin à la fois thenet les elsebranches, il est préférable d'utiliser une explicite ifcommande, non &&et ||.
musiphil

1
Merci pour l'explication, @musiphil. Maintenant, je comprends l'argument de Jonas.
manatwork

4

J'ai eu une situation similaire que j'ai résolue avec les fonctions bash :

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

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

3

Voici une autre solution possible:
concaténer des canaux nommés afin de créer un 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

Pour référence future, si vous venez ici à la recherche de pipes conditionnelles dans le poisson , procédez comme suit:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.