Si llamo a algún comando, por ejemplo echo
, puedo usar los resultados de ese comando en varios otros comandos con tee
. Ejemplo:
echo "Hello world!" | tee >(command1) >(command2) >(command3)
Con cat puedo recopilar los resultados de varios comandos. Ejemplo:
cat <(command1) <(command2) <(command3)
Me gustaría poder hacer ambas cosas al mismo tiempo, de modo que pueda usar tee
para llamar a esos comandos en la salida de otra cosa (por ejemplo, lo echo
que he escrito) y luego recopilar todos sus resultados en una sola salida con cat
.
Es importante mantener los resultados en orden, esto significa que las líneas en la salida de command1
, command2
y command3
no deben estar entrelazadas, sino ordenadas como están los comandos (como sucede con cat
).
Puede haber mejores opciones que cat
y, tee
pero esas son las que conozco hasta ahora.
Quiero evitar el uso de archivos temporales porque el tamaño de la entrada y salida puede ser grande.
¿Cómo podría hacer esto?
PD: otro problema es que esto sucede en un bucle, lo que dificulta el manejo de archivos temporales. Este es el código actual que tengo y funciona para casos de prueba pequeños, pero crea bucles infinitos al leer y escribir desde el archivo auxiliar de alguna manera que no entiendo.
somefunction()
{
if [ $1 -eq 1 ]
then
echo "Hello world!"
else
somefunction $(( $1 - 1 )) > auxfile
cat <(command1 < auxfile) \
<(command2 < auxfile) \
<(command3 < auxfile)
fi
}
Las lecturas y escritos en archivos auxiliares parecen superponerse, haciendo que todo explote.
fuente
echo HelloWorld > file; (command1<file;command2<file;command3<file)
o para la salidaecho | tee cmd1 cmd2 cmd3; cat cmd1-output cmd2-output cmd3-output
. Así es como funciona: tee puede bifurcar la entrada solo si todos los comandos funcionan y se procesan en paralelo. si un comando duerme (porque no desea intercalar) simplemente bloqueará todos los comandos, para evitar llenar la memoria con la entrada ...Respuestas:
Puede usar una combinación de GNU stdbuf y
pee
de moreutils :orinar
popen(3)
esas 3 líneas de comando de shell y luegofread
la entrada yfwrite
las tres, que se almacenarán en un búfer de hasta 1M.La idea es tener un buffer al menos tan grande como la entrada. De esta manera, aunque los tres comandos se inicien al mismo tiempo, solo verán la entrada entrando cuando
pee
pclose
los tres comandos se encuentren secuencialmente.Sobre cada uno
pclose
,pee
vacía el búfer al comando y espera su finalización. Eso garantiza que mientras esoscmdx
comandos no comiencen a generar nada antes de que hayan recibido ninguna entrada (y no bifurquen un proceso que pueda continuar emitiendo después de que su padre haya regresado), la salida de los tres comandos no será intercaladoEn efecto, es un poco como usar un archivo temporal en la memoria, con el inconveniente de que los 3 comandos se inician simultáneamente.
Para evitar iniciar los comandos al mismo tiempo, puede escribir
pee
como una función de shell:Pero tenga en cuenta que los shells que
zsh
no sean fallarían para la entrada binaria con caracteres NUL.Eso evita el uso de archivos temporales, pero eso significa que toda la entrada se almacena en la memoria.
En cualquier caso, tendrá que almacenar la entrada en algún lugar, en la memoria o en un archivo temporal.
En realidad, es una pregunta bastante interesante, ya que nos muestra el límite de la idea de Unix de que varias herramientas simples cooperen en una sola tarea.
Aquí, nos gustaría que varias herramientas cooperen con la tarea:
echo
)tee
)cmd1
,cmd2
,cmd3
)cat
).Sería bueno si todos pudieran correr juntos al mismo tiempo y hacer su trabajo duro en los datos que deben procesar tan pronto como estén disponibles.
En el caso de un comando de filtro, es fácil:
Todos los comandos se ejecutan simultáneamente,
cmd1
comienza a masticar datossrc
tan pronto como está disponible.Ahora, con tres comandos de filtro, aún podemos hacer lo mismo: iniciarlos simultáneamente y conectarlos con tuberías:
Lo que podemos hacer con relativa facilidad con tuberías con nombre :
(lo anterior
} 3<&0
es evitar el hecho de que&
redirigestdin
desde/dev/null
, y usamos<>
para evitar la apertura de las tuberías para bloquear hasta que el otro extremo (cat
) también se haya abierto)O para evitar tuberías con nombre, un poco más doloroso con
zsh
coproc:Ahora, la pregunta es: una vez que todos los programas se inicien y se conecten, ¿fluirán los datos?
Tenemos dos restricciones:
tee
alimenta todas sus salidas a la misma velocidad, por lo que solo puede enviar datos a la velocidad de su tubería de salida más lenta.cat
solo comenzará a leer desde la segunda tubería (tubería 6 en el dibujo anterior) cuando todos los datos hayan sido leídos desde la primera (5).Lo que eso significa es que los datos no fluirán en la tubería 6 hasta que
cmd1
haya terminado. Y, como en el caso de lotr b B
anterior, eso puede significar que los datos tampoco fluirán en la tubería 3, lo que significa que no fluirán en ninguna de las tuberías 2, 3 o 4 ya quetee
alimentan a la velocidad más lenta de las 3.En la práctica, esas tuberías tienen un tamaño no nulo, por lo que algunos datos lograrán pasar, y al menos en mi sistema, puedo hacer que funcione hasta:
Más allá de eso, con
Tenemos un punto muerto en el que nos encontramos en esta situación:
Hemos llenado las tuberías 3 y 6 (64 kB cada una).
tee
ha leído ese byte extra, lo ha alimentadocmd1
, perocmd2
vaciarlocmd2
no puede vaciarlo porque está bloqueado escribiendo en la tubería 6, esperandocat
vaciarlocat
no puede vaciarlo porque está esperando hasta que no haya más entradas en la tubería 5.cmd1
No puedo decir quecat
no hay más entrada porque está esperando más entrada detee
.tee
no puedo decir quecmd1
no hay más entradas porque está bloqueado ... y así sucesivamente.Tenemos un bucle de dependencia y, por lo tanto, un punto muerto.
Ahora, ¿cuál es la solución? Las tuberías más grandes 3 y 4 (lo suficientemente grandes como para contener toda
src
la salida) lo harían. Podríamos hacer eso, por ejemplo, insertandopv -qB 1G
entretee
ycmd2/3
dóndepv
podría almacenar hasta 1G de datos en esperacmd2
ycmd3
leerlos. Sin embargo, eso significaría dos cosas:cmd2
logra que los 3 comandos cooperen porque en realidad solo comenzaría a procesar datos cuando cmd1 haya terminado.Una solución al segundo problema sería hacer las tuberías 6 y 7 más grandes también. Suponiendo eso
cmd2
ycmd3
produciendo tanta salida como consumen, eso no consumiría más memoria.La única forma de evitar la duplicación de datos (en el primer problema) sería implementar la retención de datos en el despachador, es decir, implementar una variación
tee
que pueda alimentar los datos a la velocidad de salida más rápida (mantener los datos para alimentar el los más lentos a su propio ritmo). No es realmente trivial.Entonces, al final, lo mejor que podemos obtener razonablemente sin programación es probablemente algo así como (sintaxis Zsh):
fuente
+1
para el bonito arte ASCII :-)Lo que usted propone no se puede hacer fácilmente con ningún comando existente, y de todos modos no tiene mucho sentido. La idea general de las canalizaciones (
|
en Unix / Linux) es que encmd1 | cmd2
lacmd1
salida de escritura (como máximo) hasta que se llena un búfer de memoria, y luegocmd2
ejecuta la lectura de datos desde el búfer (como máximo) hasta que esté vacío. Es decir,cmd1
ycmd2
ejecutar al mismo tiempo, nunca es necesario tener más que una cantidad limitada de datos "en vuelo" entre ellos. Si desea conectar varias entradas a una sola salida, si uno de los lectores va a la zaga de los demás, puede detener a los demás (¿cuál es el punto de correr en paralelo entonces?) O guardar la salida que el rezagado aún no ha leído (¿Cuál es el punto de no tener un archivo intermedio entonces?). mas complejo.En mis casi 30 años de experiencia en Unix, no recuerdo ninguna situación que realmente se hubiera beneficiado de una tubería de múltiples salidas.
Hoy en día, puede combinar varias salidas en una sola secuencia, pero no de forma intercalada (¿cómo deberían las salidas
cmd1
ycmd2
ser intercaladas? Una línea a la vez? Turnarse para escribir 10 bytes? "Párrafos" alternativos definidos de alguna manera? t escribir algo durante mucho tiempo? todo esto es complejo de manejar). Lo hacen, por ejemplo(cmd1; cmd2; cmd3) | cmd4
, los programascmd1
,cmd2
ycmd3
se ejecutan uno tras otro, la salida se envía como entrada acmd4
.fuente
Para su problema de superposición, en Linux (y con
bash
ozsh
pero no conksh93
), puede hacerlo como:Tenga en cuenta el uso de en
(...)
lugar de{...}
obtener un nuevo proceso en cada iteración para que podamos tener un nuevo fd 3 apuntando a un nuevoauxfile
.< /dev/fd/3
es un truco para acceder a ese archivo ahora eliminado. No funcionará en sistemas que no son Linux, donde< /dev/fd/3
se comodup2(3, 0)
y así fd 0 estaría abierto en modo de sólo escritura con el cursor al final del archivo.Para evitar la bifurcación de la función anidada, puede escribirla como:
El shell se encargaría de hacer una copia de seguridad del fd 3 en cada iteración. Sin embargo, terminarías quedando sin descriptores de archivo antes.
Aunque encontrará que es más eficiente hacerlo como:
Es decir, no anide las redirecciones.
fuente