Proceso de sustitución y tubería

86

Me preguntaba cómo entender lo siguiente:

Conectar el stdout de un comando al stdin de otro es una técnica poderosa. Pero, ¿qué pasa si necesita canalizar la salida estándar de múltiples comandos? Aquí es donde entra en juego la sustitución de procesos.

En otras palabras, ¿puede la sustitución de procesos hacer lo que pueda hacer la tubería?

¿Qué puede hacer la sustitución del proceso, pero la tubería no?

Tim
fuente

Respuestas:

134

Una buena manera de detectar la diferencia entre ellos es experimentar un poco en la línea de comando. A pesar de la similitud visual en el uso del <personaje, hace algo muy diferente a una redirección o canalización.

Usemos el datecomando para probar.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Este es un ejemplo inútil, pero muestra que cataceptó la salida de dateSTDIN y la volvió a escupir. Los mismos resultados se pueden lograr mediante la sustitución del proceso:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Sin embargo, lo que sucedió detrás de escena fue diferente. En lugar de recibir una transmisión STDIN, en catrealidad se le pasó el nombre de un archivo que necesitaba abrir y leer. Puede ver este paso utilizando en echolugar de cat.

$ echo <(date)
/proc/self/fd/11

Cuando cat recibió el nombre del archivo, leyó el contenido del archivo. Por otro lado, echo solo nos mostró el nombre del archivo que se pasó. Esta diferencia se vuelve más obvia si agrega más sustituciones:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Es posible combinar la sustitución del proceso (que genera un archivo) y la redirección de entrada (que conecta un archivo a STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Se ve más o menos igual, pero esta vez a cat se le pasó la transmisión STDIN en lugar de un nombre de archivo. Puedes ver esto probándolo con echo:

$ echo < <(date)
<blank>

Como echo no lee STDIN y no se pasó ningún argumento, no obtenemos nada.

Las canalizaciones y las redirecciones de entrada introducen contenido en la secuencia STDIN. La sustitución de procesos ejecuta los comandos, guarda su salida en un archivo temporal especial y luego pasa ese nombre de archivo en lugar del comando. Cualquier comando que esté usando lo trata como un nombre de archivo. Tenga en cuenta que el archivo creado no es un archivo normal, sino una tubería con nombre que se elimina automáticamente una vez que ya no es necesario.

Caleb
fuente
Si entendí correctamente , tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 dice que la sustitución del proceso crea archivos temporales, no tuberías con nombre. Por lo que sé, el nombre no crea archivos temporales. Escribir en la tubería nunca implica escribir en el disco: stackoverflow.com/a/6977599/788700
Adobe
Sé que esta respuesta es legítima porque usa la palabra grok : D
aqn
2
@Adobe puede confirmar si la sustitución proceso de archivo temporal produce es una tubería con nombre, con: [[ -p <(date) ]] && echo true. Esto produce truecuando lo ejecuto con bash 4.4 o 3.2.
De Novo
24

Supongo que está hablando de bashalgún otro shell avanzado, porque el shell posix no tiene sustitución de proceso .

bash informes de la página del manual:

Sustitución de
procesos La sustitución de procesos se admite en sistemas que admiten canalizaciones con nombre (FIFO) o el método / dev / fd para nombrar archivos abiertos. Toma la forma de <(lista) o> (lista). La lista de procesos se ejecuta con su entrada o salida conectada a un FIFO o algún archivo en / dev / fd. El nombre de este archivo se pasa como argumento al comando actual como resultado de la expansión. Si se usa el formulario> (lista), escribir en el archivo proporcionará información para la lista. Si se usa la forma <(lista), el archivo pasado como argumento debe leerse para obtener el resultado de la lista.

Cuando está disponible, la sustitución del proceso se realiza simultáneamente con la expansión de parámetros y variables, la sustitución de comandos y la expansión aritmética.

En otras palabras, y desde un punto de vista práctico, puede usar una expresión como la siguiente

<(commands)

como nombre de archivo para otros comandos que requieren un archivo como parámetro. O puede usar la redirección para dicho archivo:

while read line; do something; done < <(commands)

Volviendo a su pregunta, me parece que la sustitución de procesos y las tuberías no tienen mucho en común.

Si desea canalizar en secuencia la salida de varios comandos, puede utilizar uno de los siguientes formularios:

(command1; command2) | command3
{ command1; command2; } | command3

pero también puede usar la redirección en la sustitución del proceso

command3 < <(command1; command2)

finalmente, si command3acepta un parámetro de archivo (en sustitución de stdin)

command3 <(command1; command2)
enzotib
fuente
entonces <() y <<() tienen el mismo efecto, ¿verdad?
solfish
@solfish: no exactamente: lo primero se puede usar donde se espera un nombre de archivo, el segundo es una redirección de entrada para ese nombre de archivo
enzotib
23

Aquí hay tres cosas que puede hacer con la sustitución de procesos que de otra manera serían imposibles.

Múltiples entradas de proceso

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Simplemente no hay forma de hacer esto con tuberías.

Preservando STDIN

Digamos que tiene lo siguiente:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

Y quieres ejecutarlo directamente. Lo siguiente falla miserablemente. Bash ya está utilizando STDIN para leer el script, por lo que otra entrada es imposible.

curl -o - http://example.com/script.sh | bash 

Pero de esta manera funciona perfectamente.

bash <(curl -o - http://example.com/script.sh)

Sustitución de proceso de salida

También tenga en cuenta que la sustitución de procesos también funciona a la inversa. Entonces puedes hacer algo como esto:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Ese es un ejemplo un poco complicado, pero envía stdout a /dev/null, mientras canaliza stderr a un script sed para extraer los nombres de los archivos para los que se mostró un error de "Permiso denegado", y luego envía LOS resultados a un archivo.

Tenga en cuenta que el primer comando y la redirección stdout están entre paréntesis ( subshell ) para que solo se envíe el resultado de ESE comando /dev/nully no se meta con el resto de la línea.

tylerl
fuente
Vale la pena señalar que en el diffejemplo es posible que desee que se preocupan por el caso en que el cdpuede fallar: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk
"while piping stderr": ¿no es el punto que esto no es tubería, sino pasar por un archivo de quince?
Gauthier
@Gauthier no; el comando se sustituye no con un fifo sino con una referencia al descriptor de archivo. Entonces, "echo <(echo)" debería producir algo como "/ dev / fd / 63", que es un dispositivo de caracteres especiales que lee o escribe desde el número FD 63.
tylerl
10

Si un comando toma una lista de archivos como argumentos y procesa esos archivos como entrada (o salida, pero no comúnmente), cada uno de esos archivos puede ser una tubería con nombre o un pseudoarchivo / dev / fd proporcionado de forma transparente por la sustitución del proceso:

$ sort -m <(command1) <(command2) <(command3)

Esto "canalizará" la salida de los tres comandos para ordenar, ya que ordenar puede tomar una lista de archivos de entrada en la línea de comandos.

camh
fuente
1
IIRC, la sintaxis <(comando) es una característica de solo bash.
Philomath
@Philomath: También está en ZSH.
Caleb
Bueno, ZSH tiene todo ... (o al menos lo intenta).
Philomath
@Philomath: ¿Cómo se implementa la sustitución de procesos en otros shells?
camh
44
@Philomath <(), como muchas funciones avanzadas de shell, originalmente era una función ksh y fue adoptada por bash y zsh. psubes específicamente una función de peces, nada que ver con POSIX.
Gilles
3

Cabe señalar que la sustitución del proceso no se limita al formulario <(command), que utiliza la salida de commandcomo un archivo. También puede tener el formato >(command)que alimenta un archivo como entrada command. Esto también se menciona en la cita del manual bash en la respuesta de @ enzotib.

Para el date | catejemplo anterior, un comando que usa la sustitución de proceso del formulario >(command)para lograr el mismo efecto sería,

date > >(cat)

Tenga en cuenta que lo >anterior >(cat)es necesario. Esto puede ser nuevamente ilustrado claramente echocomo en la respuesta de @ Caleb.

$ echo >(cat)
/dev/fd/63

Entonces, sin el extra >, date >(cat)sería lo mismo date /dev/fd/63que imprimirá un mensaje en stderr.

Supongamos que tiene un programa que solo toma nombres de archivos como parámetros y no procesa stdinni stdout. Usaré el script simplificado psub.shpara ilustrar esto. El contenido de psub.shes

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Básicamente, prueba que sus dos argumentos son archivos (no necesariamente archivos regulares) y, si este es el caso, escriba el primer campo de cada línea "$1"para "$2"usar awk. Entonces, un comando que combina todo lo mencionado hasta ahora es,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Esto imprimirá

a
b
c

y es equivalente a

printf "a a\nc c\nb b" | awk '{print $1}' | sort

pero lo siguiente no funcionará, y tenemos que usar la sustitución del proceso aquí,

printf "a a\nc c\nb b" | ./psub.sh | sort

o su forma equivalente

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Si ./psub.shtambién se lee stdinademás de lo mencionado anteriormente, entonces no existe una forma equivalente, y en ese caso no hay nada que podamos usar en lugar de la sustitución del proceso (por supuesto, también puede usar una tubería con nombre o un archivo temporal, pero ese es otro historia).

Weijun Zhou
fuente