texto separador script manejo lineas leer fichero extraer como comando columnas campos cadenas cadena buscar archivo agregar windows bash unix shell pipe

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 y ksh (a diferencia de zsh , 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 y ksh 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; el tee 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ón 93u+ ) permite el uso de wait 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 con wait $! , pero la wait sin argumentos no funciona, lo que no es adecuado para un comando con múltiples sustituciones de procesos de salida.

  • Sin embargo, bash y ksh 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 de ksh 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 ni ksh ni zsh apoyan eso.
  • 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 .

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 .