¿Cuál es la forma portátil (POSIX) de lograr la sustitución del proceso?

25

Algunos shells, como bash, admiten la sustitución de procesos, que es una forma de presentar la salida del proceso como un archivo, como este:

$ diff <(sort file1) <(sort file2)

Sin embargo, esta construcción no es POSIX y, por lo tanto, no es portátil. ¿Cómo se puede lograr la sustitución del proceso de una manera amigable con POSIX (es decir, una que funcione /bin/sh) ?

nota: la pregunta no es cómo diferenciar dos archivos ordenados, ¡eso es solo un ejemplo artificial para demostrar la sustitución del proceso !

starfry
fuente
mywiki.wooledge.org/ProcessSubstitution tiene una nota de que mywiki.wooledge.org/NamedPipes se puede usar ..
Sundeep
2
Ver también unix.stackexchange.com/a/43536/117549
Jeff Schaller
1
POSIX no requiere /bin/shser un shell POSIX, solo que haya un comando llamado en shalgún lugar que, en el entorno correcto, sea compatible con POSIX. Por ejemplo, en Solaris 10, /bin/shno es un shell POSIX, es un antiguo shell Bourne y el shell POSIX está dentro /usr/xpg4/bin/sh.
Stéphane Chazelas

Respuestas:

17

Esa característica fue introducida por ksh(documentada por primera vez en ksh86) y estaba haciendo uso de la /dev/fd/ncaracterística (agregada de forma independiente en algunos sistemas BSD y AT&T anteriormente). En kshy hasta ksh93u, no funcionaría a menos que su sistema tuviera soporte para / dev / fd / n. zsh, bash y ksh93u+superiores pueden hacer uso de canalizaciones con nombre temporales (canalizaciones con nombre agregadas en SysIII, creo) donde / dev / fd / n no están disponibles.

En los sistemas donde está disponible (POSIX no los especifica), puede realizar la sustitución del proceso usted mismo ( ) con:/dev/fd/ndiff <(cmd1) <(cmd2)

{
  cmd1 4<&- | {
    # in here fd 3 points to the reading end of the pipe
    # from cmd1, while fd 0 has been restored from the original
    # stdin (saved on fd 4, now closed as no longer needed)

    cmd2 3<&- | diff /dev/fd/3 -

  } 3<&0 <&4 4<&- # restore the original stdin for cmd2

} 4<&0 # save a copy of stdin for cmd2

Sin embargo, eso no funciona ksh93en Linux, ya que las tuberías de shell se implementan con pares de sockets en lugar de tuberías y la apertura /dev/fd/3donde fd 3 apunta a un socket no funciona en Linux.

Aunque POSIX no especifica . Sí especifica tuberías con nombre. Las canalizaciones con nombre funcionan como las canalizaciones normales, excepto que puede acceder a ellas desde el sistema de archivos. El problema aquí es que debe crear archivos temporales y limpiar después, lo cual es difícil de hacer de manera confiable, especialmente teniendo en cuenta que POSIX no tiene un mecanismo estándar (como el que se encuentra en algunos sistemas) para crear archivos o directorios temporales, y hacer el manejo de la señal de manera portátil (para limpiar al colgar o matar) también es difícil de hacer de forma portátil./dev/fd/nmktemp -d

Podrías hacer algo como:

tmpfifo() (
  n=0
  until
    fifo=$1.$$.$n
    mkfifo -m 600 -- "$fifo" 2> /dev/null
  do
    n=$((n + 1))
    # give up after 20 attempts as it could be a permanent condition
    # that prevents us from creating fifos. You'd need to raise that
    # limit if you intend to create (and use at the same time)
    # more than 20 fifos in your script
    [ "$n" -lt 20 ] || exit 1
  done
  printf '%s\n' "$fifo"
)

cleanup() { rm -f -- "$fifo"; }
fifo=$(tmpfifo /tmp/fifo) || exit

cmd2 > "$fifo" & cmd1 | diff - "$fifo"
rm -f -- "$fifo"

(no cuidar el manejo de la señal aquí).

Stéphane Chazelas
fuente
El ejemplo de canalización con nombre es completamente claro para mí (¿supongo que 10 es un límite arbitrario?) Pero no puedo entender su /dev/fd/nejemplo. ¿Por qué el descriptor 4 se cierra dos veces? (Y también lo es el descriptor 3.) Estoy perdido.
Comodín el
@Wildcard, está cerrado para comandos que no lo necesitan. Aquí, solo diff necesita ese fd 3. Ninguno necesita el fd 4, solo se usa para propagar el stdin original a cmd2( dup2(0,4)en el exterior {...}, restaurado con dup2(4,0)el interior {...}).
Stéphane Chazelas
Puede utilizarlo mktemp -dpara asegurarse de que puede obtener FIFO, ya que nadie debería escribir en su nuevo directorio temporal aleatorio.
Daniel H
@DanielH, ya lo menciono mktemp -d. Pero ese no es un comando estándar / POSIX.
Stéphane Chazelas
Huh, no me di cuenta de eso. Ups La búsqueda rápida parece mostrar que la mayoría de los sistemas lo admiten, por lo que aún puede ser portátil, pero no es POSIX.
Daniel H
-1

Si es necesario para evitar la variable perdida en lo útil cmd | while read A B C, en lugar de:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done < <(cmd)
echo "$VAR"

puedes usar:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done << EndOfText
`cmd`
EndOfText
echo "$VAR"

Entonces para responder la pregunta:

sort file1 | diff /dev/stdin /dev/stdout 2<<EOT
`sort file2`
EOT
defdefred
fuente