Qual é a maneira portátil (POSIX) de obter substituição de processo?

25

Alguns shells, como bash, suportam a Substituição de Processo, que é uma maneira de apresentar a saída do processo como um arquivo, assim:

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

No entanto, essa construção não é POSIX e, portanto, não é portátil. Como a substituição do processo pode ser realizada de maneira amigável ao POSIX (ou seja, uma que funcione /bin/sh) ?

nota: a pergunta não está perguntando como diferenciar dois arquivos classificados - esse é apenas um exemplo artificial para demonstrar a substituição do processo !

starfry
fonte
mywiki.wooledge.org/ProcessSubstitution tem uma nota que mywiki.wooledge.org/NamedPipes pode ser utilizado ..
Sundeep
2
Veja também unix.stackexchange.com/a/43536/117549
Jeff Schaller
11
O POSIX não precisa /bin/shser um shell POSIX, apenas que exista um comando chamado em shalgum lugar que, no ambiente certo, seja compatível com POSIX. Por exemplo, no Solaris 10, /bin/shnão é um shell POSIX, é um Bourne shell antigo eo shell POSIX está em /usr/xpg4/bin/sh.
Stéphane Chazelas 13/09/16

Respostas:

17

Esse recurso foi introduzido por ksh(documentado pela primeira vez no ksh86) e estava usando o /dev/fd/nrecurso (adicionado de forma independente em alguns sistemas BSDs e AT&T anteriormente). No kshe até o ksh93u, ele não funcionaria, a menos que seu sistema tivesse suporte para / dev / fd / n. zsh, bash e ksh93u+acima podem fazer uso de pipes nomeados temporários (pipes nomeados adicionados ao SysIII, acredito), onde / dev / fd / n não estão disponíveis.

Nos sistemas em que está disponível (o POSIX não os especifica), você pode fazer a substituição do processo por conta própria ( ) com:/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

No entanto, isso não funciona ksh93no Linux, pois os shell pipes são implementados com socketpairs em vez de pipes e a abertura /dev/fd/3onde fd 3 aponta para um soquete não funciona no Linux.

Embora o POSIX não especifique . Ele especifica pipes nomeados. Os pipes nomeados funcionam como os pipes normais, exceto que você pode acessá-los no sistema de arquivos. O problema aqui é que você precisa criar arquivos temporários e limpar depois, o que é difícil de ser feito com segurança, especialmente considerando que o POSIX não possui mecanismo padrão (como o encontrado em alguns sistemas) para criar arquivos ou diretórios temporários e manipular sinais de maneira portável (limpar após desligar ou matar) também é difícil de executar de forma portável./dev/fd/nmktemp -d

Você poderia fazer 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"

(não cuidando da manipulação do sinal aqui).

Stéphane Chazelas
fonte
O exemplo de pipe nomeado é completamente claro para mim (acho que 10 é um limite arbitrário?), Mas não consigo descobrir seu /dev/fd/nexemplo. Por que o descritor 4 é fechado duas vezes? (E o descritor 3 também.) Estou perdido.
Curinga
@Wildcard, está fechado para comandos que não precisam dele. Aqui, o diff precisa apenas do fd 3. Nenhum precisa do fd 4, é usado apenas para propagar o stdin original para cmd2( dup2(0,4)no exterior {...}, restaurado com dup2(4,0)pelo interior {...}).
Stéphane Chazelas 14/09/16
Você pode mktemp -dajudar a garantir que você possa obter FIFOs, pois ninguém deve escrever no seu novo diretório temporário aleatório.
Daniel H
@ DanielH, eu já mencionei mktemp -d. Mas esse não é um comando padrão / POSIX.
Stéphane Chazelas
Não percebi isso. Opa A pesquisa rápida parece mostrar que a maioria dos sistemas suporta, por isso ainda pode ser portátil, mas não é POSIX.
Daniel H
-1

Se necessário, para evitar a perda de variável no útil cmd | while read A B C, em vez de:

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

você pode usar:

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

Então, para responder à pergunta:

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