¿Cómo puedo enviar stdout a múltiples comandos?

187

Hay algunos comandos que filtran o actúan en la entrada, y luego la pasan como salida, creo que generalmente stdout, pero algunos comandos simplemente tomarán stdiny harán lo que hagan con ella, y no generarán nada.

Estoy más familiarizado con OS X, por lo que hay dos que se me ocurren de inmediato pbcopyy pbpaste, que son medios para acceder al portapapeles del sistema.

De todos modos, sé que si quiero tomar stdout y escupir la salida para ir a ambos stdouty a un archivo, entonces puedo usar el teecomando. Y sé un poco sobre eso xargs, pero no creo que eso sea lo que estoy buscando.

Quiero saber cómo puedo dividir stdoutpara ir entre dos (o más) comandos. Por ejemplo:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Probablemente haya un mejor ejemplo que ese, pero realmente estoy interesado en saber cómo puedo enviar stdout a un comando que no lo retransmite y mientras evito que stdoutsea ​​"silenciado": no estoy preguntando cómo hacer catun archivo y grepparte de él y cópielo en el portapapeles; los comandos específicos no son tan importantes.

Además, no estoy preguntando cómo enviar esto a un archivo y stdoutesta puede ser una pregunta "duplicada" (lo siento), pero busqué un poco y solo pude encontrar otras similares que preguntaban cómo dividir entre stdout y un archivo - y las respuestas a esas preguntas parecían ser tee, lo cual no creo que funcione para mí.

Finalmente, puede preguntar "¿por qué no hacer que pbcopy sea lo último en la cadena de tuberías?" y mi respuesta es 1) ¿qué pasa si quiero usarlo y aún así ver el resultado en la consola? 2) ¿Qué pasa si quiero usar dos comandos que no salen stdoutdespués de que procesan la entrada?

Ah, y una cosa más: me doy cuenta de que podría usar teeuna tubería con nombre ( mkfifo), pero esperaba que esto se pudiera hacer en línea, de manera concisa, sin una configuración previa :)

cwd
fuente
Posible duplicado de unix.stackexchange.com/questions/26964/…
Nikhil Mulley

Respuestas:

241

Puede usar teey procesar la sustitución para esto:

cat file.txt | tee >(pbcopy) | grep errors

Esto enviará toda la salida de cat file.txta pbcopy, y solo obtendrá el resultado grepen su consola.

Puede poner múltiples procesos en la teeparte:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
Estera
fuente
21
No es una preocupación pbcopy, pero vale la pena mencionar en general: cualquiera que sea la salida de sustitución del proceso también se ve en el siguiente segmento de tubería, después de la entrada original; por ejemplo: seq 3 | tee >(cat -n) | cat -e( cat -nnumera las líneas de entrada, cat -emarca nuevas líneas con $; verá que cat -ese aplica tanto a la entrada original (primero) como (luego) a la salida de cat -n). La salida de múltiples sustituciones de proceso llegará en un orden no determinista.
mklement0
49
El >(único funciona en bash. Si intenta eso usando, por ejemplo sh, no funcionará. Es importante hacer este aviso.
AAlvz
10
@AAlvz: Buen punto: la sustitución de procesos no es una característica POSIX; dash, que actúa como shen Ubuntu, no lo admite, e incluso Bash desactiva la función cuando se invoca shcuando set -o posixestá vigente. Sin embargo, no es solo Bash el que admite las sustituciones de procesos: kshy también las zshadmite (no estoy seguro acerca de los demás).
mklement0
2
@ mklement0 que no parece ser cierto. En zsh (Ubuntu 14.04) su línea se imprime: 1 1 2 2 3 3 1 $ 2 $ 3 $ Lo cual es triste, porque realmente quería que la funcionalidad fuera como la dices.
Aktau
2
@ Aktau: De hecho, mi comando de muestra solo funciona como se describe en bashy ksh, zshaparentemente, no envía la salida de las sustituciones del proceso de salida a través de la tubería (posiblemente, eso es preferible , porque no contamina lo que se envía al siguiente segmento de tubería) todavía se imprime ). Sin embargo, en todos los shells mencionados, generalmente no es una buena idea tener una sola tubería en la que se mezclen la salida estándar y la salida de las sustituciones del proceso: el orden de salida no será predecible, de una manera que solo puede surgir con poca frecuencia o con grandes conjuntos de datos de salida.
mklement0
124

Puede especificar varios nombres de archivo teey, además, la salida estándar se puede canalizar en un comando. Para enviar la salida a múltiples comandos, debe crear varias tuberías y especificar cada una de ellas como una salida de tee. Hay varias maneras de hacer esto.

Proceso de sustitución

Si su shell es ksh93, bash o zsh, puede usar la sustitución de procesos. Esta es una manera de pasar una tubería a un comando que espera un nombre de archivo. El shell crea la tubería y pasa un nombre de archivo como /dev/fd/3al comando. El número es el descriptor de archivo al que está conectada la tubería. Algunas variantes de Unix no son compatibles /dev/fd; en estos, se usa una tubería con nombre en su lugar (ver más abajo).

tee >(command1) >(command2) | command3

Descriptores de archivo

En cualquier shell POSIX, puede usar múltiples descriptores de archivo explícitamente. Esto requiere una variante de Unix que admita /dev/fd, ya que todas menos una de las salidas de teedeben especificarse por nombre.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Tubos con nombre

El método más básico y portátil es utilizar tuberías con nombre . La desventaja es que necesita encontrar un directorio de escritura, crear las tuberías y limpiar después.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2
Gilles
fuente
10
Muchas gracias por proporcionar las dos versiones alternativas para aquellos que no quieren confiar en bash o un cierto ksh.
trr
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3seguramente debería ser command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", como quieres stdout de command3tubería tee, ¿no? Probé su versión dashy teebloquea indefinidamente la espera de entrada, pero cambiar el orden produjo el resultado esperado.
Adrian Günter
1
@ AdrianGünter No. Los tres ejemplos leen datos de la entrada estándar y los envían a cada uno de command, command2y command3.
Gilles
@Gilles Ya veo, interpreté mal la intención e intenté usar el fragmento incorrectamente. ¡Gracias por la aclaración!
Adrian Günter
Si no tiene control sobre el shell utilizado, pero puede usar bash explícitamente, puede hacerlo <command> | bash -c 'tee >(command1) >(command2) | command3'. Ayudó en mi caso.
gc5
16

Solo juega con la sustitución del proceso.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepson dos binarios que tienen el mismo resultado mycommand_execque su entrada específica del proceso.

Nikhil Mulley
fuente
16

Si está utilizando zsh, puede aprovechar el poder de la MULTIOSfunción, es decir, deshacerse del teecomando por completo:

uname >file1 >file2

solo escribirá la salida de unamedos archivos diferentes: file1y file2, lo que es equivalente auname | tee file1 >file2

De manera similar, la redirección de entradas estándar

wc -l <file1 <file2

es equivalente a cat file1 file2 | wc -l(tenga en cuenta que esto no es lo mismo que wc -l file1 file2, el último cuenta el número de líneas en cada archivo por separado).

Por supuesto, también puede usarlo MULTIOSpara redirigir la salida, no a archivos, sino a otros procesos, mediante la sustitución de procesos, por ejemplo:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')
jimmij
fuente
3
Bueno saber. MULTIOSes una opción que está activada de forma predeterminada (y se puede desactivar con unsetopt MULTIOS).
mklement0
6

Para una salida razonablemente pequeña producida por un comando, podemos redirigir la salida a un archivo temporal y enviar esos archivos temporales a los comandos en bucle. Esto puede ser útil cuando el orden de los comandos ejecutados puede ser importante.

El siguiente script, por ejemplo, podría hacer eso:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Prueba de ejecución en Ubuntu 16.04 con /bin/shcomo dashshell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash
Sergiy Kolodyazhnyy
fuente
5

Capture el comando STDOUTen una variable y reutilícelo tantas veces como desee:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Si necesita capturar STDERRtambién, use 2>&1al final del comando, así:

commandoutput="$(command-to-run 2>&1)"
sombreador
fuente
3
¿Dónde se almacenan las variables? Si se tratara de un archivo grande o algo por el estilo, ¿no acapararía mucha memoria? ¿Las variables tienen un tamaño limitado?
cwd
1
¿Qué pasa si $ commandoutput es enorme ?, es mejor usar tuberías y sustitución de procesos.
Nikhil Mulley
44
Obviamente, esta solución es posible solo cuando sabe que el tamaño de la salida se ajustará fácilmente en la memoria, y está de acuerdo con almacenar en búfer toda la salida antes de ejecutar los siguientes comandos. Las tuberías resuelven estos dos problemas al permitir datos de longitud arbitraria y transmitirlos en tiempo real al receptor a medida que se generan.
trr
2
Esta es una buena solución si tiene un resultado pequeño y sabe que el resultado será texto y no binario. (variables de shell a menudo no son seguros binario)
Rucent88
1
No puedo hacer que esto funcione con datos binarios. Creo que es algo con echo que intenta interpretar bytes nulos u otros datos que no son caracteres.
Rolf
1

Esto puede ser útil: http://www.spinellis.gr/sw/dgsh/ (shell de gráfico dirigido) Parece un reemplazo de bash que admite una sintaxis más fácil para los comandos "multipipe".

sivann
fuente
0

Aquí hay una solución parcial rápida y sucia, compatible con cualquier shell incluido busybox.

El problema más estrecho que resuelve es: imprimir el completo stdouten una consola y filtrarlo en otra, sin archivos temporales ni canalizaciones con nombre.

  • Inicie otra sesión en el mismo host. Para averiguar su nombre TTY, escriba tty. Vamos a suponer /dev/pty/2.
  • En la primera sesión, ejecute the_program | tee /dev/pty/2 | grep ImportantLog:

Obtiene un registro completo y uno filtrado.

Victor Sergienko
fuente