windows - separador - manejo de cadenas en shell script
¿Cómo puedo enviar el stdout de un proceso a múltiples procesos usando pipes(preferiblemente sin nombre) en Unix(o Windows)? (5)
Unix ( bash
, ksh
, zsh
)
La respuesta de dF. contiene la semilla de una respuesta basada en tee
y sustituciones del proceso de salida
( >(...)
) que pueden funcionar o no , según sus requisitos:
Tenga en cuenta que las sustituciones de procesos son una característica no estándar que (en su mayoría) las shells POSIX-features-only como dash
(que actúa como /bin/sh
en Ubuntu, por ejemplo) no son compatibles. Las secuencias de comandos de shell /bin/sh
no deberían depender de ellas.
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
Las trampas de este enfoque son:
comportamiento de salida impredecible y asíncrono : las secuencias de salida de los comandos dentro de las sustituciones de proceso de salida
>(...)
entrelazan de maneras impredecibles.En
bash
yksh
(a diferencia dezsh
, pero vea la excepción a continuación):- la salida puede llegar después de que el comando haya terminado.
- los comandos posteriores pueden comenzar a ejecutarse antes de que los comandos en las sustituciones del proceso hayan finalizado ;
bash
yksh
no esperan a que finalicen los procesos de sustitución de procesos de salida, al menos de forma predeterminada. - jmb lo jmb bien en un comentario sobre la respuesta de dF.
tenga en cuenta que los comandos iniciados dentro
>(...)
están disociados del shell original y no puede determinar fácilmente cuándo terminan; eltee
terminará después de escribir todo, pero los procesos sustituidos seguirán consumiendo los datos de varios almacenamientos intermedios en el kernel y el archivo de E / S, más el tiempo que sea necesario para el manejo interno de los datos. Puede encontrar condiciones de carrera si su capa exterior continúa dependiendo de cualquier cosa producida por los subprocesos.
zsh
es el único intérprete de comandos que espera de forma predeterminada que los procesos ejecutados en las sustituciones del proceso de salida finalicen , excepto si se trata de stderr que se redirige a uno (2> >(...)
).ksh
(al menos a partir de la versión93u+
) permite el uso dewait
argumentos para esperar a que finalicen los procesos de sustitución por sustitución del proceso de salida.
Sin embargo, tenga en cuenta que en una sesión interactiva que podría provocar la espera de trabajos en segundo plano pendientes.bash v4.4+
puede esperar la sustitución del proceso de salida más reciente conwait $!
, pero lawait
sin argumentos no funciona, lo que no es adecuado para un comando con múltiples sustituciones de procesos de salida.Sin embargo,
bash
yksh
pueden verse obligados a esperar al conectar el comando| cat
| cat
, pero tenga en cuenta que esto hace que el comando se ejecute en una subshell . Advertencias :ksh
(a partir deksh 93u+
) no admite el envío de stderr a una sustitución de proceso de salida (2> >(...)
); tal intento es silenciosamente ignorado .Mientras que
zsh
es (encomiablemente) síncrono por defecto con las sustituciones (mucho más comunes) del proceso de salida stdout , incluso el| cat
| cat
técnica| cat
no puede hacer que sean síncronas con las sustituciones del proceso de salida de stderr (2> >(...)
).
Sin embargo, incluso si garantiza la ejecución sincrónica , el problema de la salida impredeciblemente intercalada permanece.
El siguiente comando, cuando se ejecuta en bash
o ksh
, ilustra los comportamientos problemáticos (puede que tenga que ejecutarlo varias veces para ver ambos síntomas): el AFTER
normalmente imprimirá antes de la salida de las sustituciones de salida, y la salida de este último puede ser entrelazado impredeciblemente.
printf ''line %s/n'' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
En resumen :
Garantizando una secuencia de salida por comando particular:
- Ni
bash
niksh
nizsh
apoyan eso.
- Ni
Ejecución sincrónica:
- Se puede hacer, excepto con sustituciones del proceso de salida de origen stderr :
- En
zsh
, son invariablemente asincrónicos. - En
ksh
, no funcionan en absoluto .
- En
- Se puede hacer, excepto con sustituciones del proceso de salida de origen stderr :
Si puede vivir con estas limitaciones, usar sustituciones de procesos de salida es una opción viable (por ejemplo, si todas escriben en archivos de salida separados).
Tenga en cuenta que la solución mucho más engorrosa, pero potencialmente POSIX, de tzot también exhibe un comportamiento de salida impredecible ; sin embargo, al usar wait
puede asegurarse de que los comandos subsiguientes no comiencen a ejecutarse hasta que todos los procesos en segundo plano hayan finalizado.
Consulte la parte inferior para obtener una implementación de salida en serie más sólida y sincrónica .
La única solución de bash
directa con comportamiento de salida predecible es la siguiente, que, sin embargo, es prohibitivamente lenta con grandes conjuntos de entrada , porque los bucles de shell son inherentemente lentos.
También tenga en cuenta que esto alterna las líneas de salida de los comandos de destino .
while IFS= read -r line; do
tr 1 a <<<"$line"
tr 1 b <<<"$line"
done < <(echo ''123'')
Unix (usando GNU Parallel)
La instalación de GNU parallel
permite una solución robusta con salida serializada (por comando) que además permite la ejecución en paralelo :
$ echo ''123'' | parallel --pipe --tee {} ::: ''tr 1 a'' ''tr 1 b''
a23
b23
parallel
por defecto asegura que la salida de los diferentes comandos no se entrelaza (este comportamiento se puede modificar; ver el man parallel
).
Nota: Algunas distribuciones de Linux vienen con una utilidad parallel
diferente , que no funcionará con el comando anterior; use parallel --version
para determinar cuál, si tiene alguno, tiene.
Windows
La útil respuesta de Jay Bazuzi muestra cómo hacerlo en PowerShell . Dicho esto: su respuesta es el análogo de la respuesta bash
bucle anterior, será prohibitivamente lento con grandes conjuntos de entrada y también alterna las líneas de salida de los comandos de destino .
basada en bash
, pero por lo demás portátil Solución de Unix con ejecución sincrónica y serialización de salida
La siguiente es una implementación simple, pero razonablemente robusta del enfoque presentado en la respuesta de tzot que además proporciona:
- ejecución sincrónica
- salida serializada (agrupada)
Si bien no es estrictamente compatible con POSIX, porque es un script bash
, debe ser portable a cualquier plataforma Unix que tenga bash
.
Nota: Puede encontrar una implementación más completa lanzada bajo la licencia de MIT en este Gist .
Si guarda el código siguiente como fanout
script, fanout
ejecutable y ponga int en su PATH
, el comando de la pregunta funcionaría de la siguiente manera:
$ echo 123 | fanout ''tr 1 a'' ''tr 1 b''
# tr 1 a
a23
# tr 1 b
b23
código fuente del script de fanout
:
#!/usr/bin/env bash
# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )
# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap ''rm -rf "$tmpDir"'' EXIT
# Determine the number padding for the sequential FIFO / output-capture names,
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"
# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
printf -v suffix "$fmtString" $i
aFifos[i]="$tmpDir/fifo-$suffix"
aOutFiles[i]="$tmpDir/out-$suffix"
done
# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit
# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
fifo=${aFifos[i]}
outFile=${aOutFiles[i]}
cmd=${aCmds[i]}
printf ''# %s/n'' "$cmd" > "$outFile"
eval "$cmd" < "$fifo" >> "$outFile" &
done
# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit
# Wait for all background processes to finish.
wait
# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"
Me gustaría redirigir la rutina del proceso proc1 a dos procesos proc2 y proc3:
proc2 -> stdout
/
proc1
/
proc3 -> stdout
Lo intenté
proc1 | (proc2 & proc3)
pero no parece funcionar, es decir
echo 123 | (tr 1 a & tr 1 b)
escribe
b23
a stdout en lugar de
a23
b23
Como dijo dF, bash
permite usar el constructo >(…)
ejecutando un comando en lugar de un nombre de archivo. (También está la construcción <(…)
para sustituir la salida de otro comando en lugar de un nombre de archivo, pero eso es irrelevante ahora, lo menciono solo para completarlo).
Si no tiene bash, o ejecuta en un sistema con una versión anterior de bash, puede hacer manualmente lo que hace bash, haciendo uso de archivos FIFO.
La forma genérica para lograr lo que quieres es:
- decida cuántos procesos deben recibir el resultado de su comando y cree tantos FIFO, preferiblemente en una carpeta temporal global:
subprocesses="a b c d" mypid=$$ for i in $subprocesses # this way we are compatible with all sh-derived shells do mkfifo /tmp/pipe.$mypid.$i done
- inicie todos sus subprocesos esperando la entrada de los FIFO:
for i in $subprocesses do tr 1 $i </tmp/pipe.$mypid.$i & # background! done
- ejecuta tu comando teeing a los FIFO:
proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
- finalmente, elimine los FIFO:
for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done
NOTA: por razones de compatibilidad, yo haría los $(…)
con las comillas invertidas, pero no pude hacerlo escribiendo esta respuesta (la comilla inversa se usa en SO). Normalmente, $(…)
tiene la edad suficiente para funcionar incluso en las versiones anteriores de ksh, pero si no lo hace, encierre la …
parte en las comillas inversas.
Dado que @dF: mencionó que PowerShell tiene tee, pensé que mostraría una forma de hacerlo en PowerShell.
PS > "123" | % {
$_.Replace( "1", "a"),
$_.Replace( "2", "b" )
}
a23
1b3
Tenga en cuenta que cada objeto que sale del primer comando se procesa antes de que se cree el siguiente objeto. Esto puede permitir escalar a entradas muy grandes.
Otra forma de hacerlo sería
eval `echo ''&& echo 123 |''{''tr 1 a'',''tr 1 b''} | sed -n ''s/^&&//gp''`
salida:
a23
b23
no es necesario crear una subshell aquí
Nota del editor :
- >(…)
es una sustitución de proceso que es una característica de shell no estándar de algunas shells compatibles con POSIX: bash
, ksh
, zsh
.
- Tal como está escrito, la respuesta envía accidentalmente la salida de la sustitución del proceso de salida a través de la canalización también .
- echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
evitaría eso, pero tiene dificultades: la salida de las subsituciones del proceso se intercalará impredeciblemente, y, excepto en zsh
, la tubería puede terminar antes de que los comandos adentro >(…)
do.
En Unix (o en un Mac), use el comando tee
:
$ echo 123 | tee >(tr 1 a) | tr 1 b
b23
a23
Por lo general, usaría tee
para redireccionar la salida a varios archivos, pero al usar> (...) puede redirigir a otro proceso. Entonces, en general,
$ proc1 | tee >(proc2) ... >(procN-1) | procN
hará lo que quieras
Bajo Windows, no creo que el shell incorporado tenga un equivalente. Sin embargo, Windows PowerShell de Microsoft tiene un comando de tee
.