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
Pierre
la source
liées: stackoverflow.com/questions/2520085/…
Ciro Santilli a dévoilé

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
homme au travail
la source
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
Thor
la source
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.

Stéphane Chazelas
la source
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.

Jonas Kölker
la source
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
jfg956
la source
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
Claudio
la source
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:]'
Jorge Bucaran
la source