Tubería condicional


39

Digamos que tengo la siguiente tubería:

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

Bajo ciertas condiciones me gustaría agregar un cmd3entre cmd2y cmd4. ¿Hay alguna manera de crear una tubería condicional amable sin guardar el resultado de cmd2 en un archivo temporal? Yo pensaría en algo como:

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

Respuestas:


36

Solo lo habitual &&y los ||operadores:

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

(Tenga en cuenta que no se necesita una barra diagonal inversa posterior cuando la línea termina con una tubería).

Actualización según la observación de Jonas .
Si cmd3 puede terminar con un código de salida distinto de cero y no desea catprocesar la entrada restante, invierta la lógica:

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

Sugerencia práctica
invertir

66
Tenga en cuenta las trampas
Jonas Kölker

2
Invertir la lógica no ayuda. Si catregresa con un estado de salida distinto de cero (común al obtener un SIGPIPE), cmd3se ejecutará. Mejor use if / then / else como en la respuesta de Thor u otras soluciones que eviten la ejecución cat.
Stéphane Chazelas

para que el gato se pueda usar para conectar cosas, algo así como una secuencia "a través"
Alexander Mills

19

if/ else/ fiObras. Suponiendo cualquier shell similar a Bourne:

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

10

Todas las respuestas dadas hasta ahora se reemplazan cmd3con cat. También puede evitar ejecutar cualquier comando con:

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

Eso es POSIX, pero tenga en cuenta que si está en un bashscript donde bashno está en shmodo (como con un script que comienza con #! /path/to/bash), deberá habilitar la expansión de alias con shopt -s expand_aliases(o set -o posix).

Otro enfoque que aún no ejecuta ningún comando innecesario es usar 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"

En Linux (al menos), en lugar de cat, podría usar pv -qwhich uses en splice()lugar de read()+ write()para pasar los datos entre las dos tuberías, lo que evita que los datos se muevan dos veces entre el kernel y el espacio de usuario.


9

Como una adición a la respuesta aceptada de manatwork : tenga en cuenta el y-false-o gotcha y su interacción con las corrientes. Por ejemplo,

true && false || echo foo

salidas foo. No es sorprendente,

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

y

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

Ambas salidas baz. (Tenga en cuenta que echo foo | grep bares falso y no tiene salida). Sin embargo,

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

No produce nada. Esto puede o no ser lo que quieres.


No puedo ver cómo es esto relevante aquí. El else(o ||) debe actuar como una operación nula manteniendo la entrada sin cambios. Entonces, reemplazar catcon echocambios todo hace que su código sea irrelevante. Con respecto a "y-falso-o gotcha", no veo interferencia con la tubería: pastebin.com/u6Jq0bZe
manatwork

55
@manatwork: Una cosa que Jonas está señalando es que, en [[ "${DEFINED}" ]] && cmd3 || cat, cmd3y catno son mutuamente excluyentes theny elseramas de la misma if, pero que si cmd3falla, cattambién se ejecutará. (Por supuesto, si cmd3siempre tiene éxito, o si consume toda la entrada para que no quede nada stdinpara catprocesar, entonces puede que no importe). Si necesita ambas theny elseramas, es mejor usar un ifcomando explícito , no &&y ||.
musiphil

1
Gracias por la explicación, @musiphil. Ahora entiendo el argumento de Jonas.
manatwork

4

Tuve una situación similar que resolví con las funciones bash :

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

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

3

Aquí hay una posible solución alternativa:
concatenar tuberías con nombre para crear una pseudo-tubería:

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

Para referencia futura, si vienes aquí buscando tuberías condicionales en peces , así es como lo haces:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.