Como emular Substituição de Processo no Dash?

18

Em bash, posso usar a Substituição de Processo e tratar a saída de um processo como se fosse um arquivo salvo no disco:

$ echo <(ls)
/dev/fd/63

$ ls -lAhF <(ls)
lr-x------ 1 root root 64 Sep 17 12:55 /dev/fd/63 -> pipe:[1652825]

infelizmente, a Substituição de Processo não é suportada no dash.

Qual seria a melhor maneira de emular Process Substitutionem hífen?

Não quero salvar a saída como um arquivo temporário em algum lugar ( /tmp/) e, em seguida, tenho que excluí-la. Existe uma maneira alternativa?

Martin Vegter
fonte
Dependendo do que você está tentando fazer, você poderá usar tubos.
Eric Renouf
Honestamente, se a portabilidade não é sua principal preocupação aqui, não seria mais fácil instalar bashno seu dispositivo?
Arkadiusz Drabczyk 7/08/19
11
O exemplo que você fornece no aviso de recompensa é o assunto desta resposta vinculada . Como mostrado aqui, uma versão simplificada da resposta de Gilles (assumindo a disponibilidade /dev/fde usando em xz -cd <file>vez de cat <file> | xz -d) poderia ser xz -cd "$1" | { xz -cd "$2" | { diff /dev/fd/3 /dev/fd/4; } 3<&0; } 4<&0.
fra-san
11
@ fra-san - é realmente o que eu precisava. Você pode fazer uma resposta, se quiser. Obrigado.
Martin Vegter 11/08/19

Respostas:

4

A pergunta no atual aviso de recompensa:

o exemplo geral é muito complicado. Alguém pode explicar como implementar o exemplo a seguir?diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

parece ter uma resposta aqui .

Conforme mostrado na resposta de Gilles , a idéia geral é enviar a saída dos comandos "produtor" para os novos arquivos de dispositivo 1 em diferentes estágios de um pipeline, disponibilizando-os para os comandos "consumidor", que podem ter nomes de arquivos como argumentos ( assumindo que seu sistema lhe dá acesso aos descritores de arquivos como /dev/fd/X).

A maneira mais simples de conseguir o que você está procurando é provavelmente:

xz -cd file1.xz | { xz -cd file2.xz | diff /dev/fd/3 -; } 3<&0

(Usando file1.xzno lugar de "$1"legibilidade e em xz -cdvez de cat ... | xz -dporque um único comando é suficiente).

A saída do primeiro comando "produtor" xz -cd file1.xz,, é canalizada para um comando composto ( {...}); mas, em vez de ser consumido imediatamente como entrada padrão do próximo comando, ele é duplicado para o descritor de arquivo 3e, portanto, tornado acessível a tudo dentro do comando composto como /dev/fd/3. A saída do segundo comando "produtor" xz -cd file2.xz, que não consome sua entrada padrão nem nada do descritor de arquivo 3, é canalizada para o comando "consumidor" diff, que lê sua entrada padrão e de /dev/fd/3.

A duplicação de tubulação e descritor de arquivo pode ser adicionada para fornecer arquivos de dispositivo para quantos comandos "produtor" forem necessários, por exemplo:

xz -cd file1.xz | { xz -cd file2.xz | { diff /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0

Embora possa ser irrelevante no contexto de sua pergunta específica, vale a pena notar que:

  1. cmd1 <(cmd2) <(cmd3), cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0E ( cmd2 | ( cmd3 | ( cmd1 /dev/fd/3 /dev/fd/4 ) 4<&0 ) 3<&0 )têm diferentes potenciais efeitos sobre o ambiente de execução inicial.

  2. Ao contrário do que acontece na cmd1 <(cmd2) <(cmd3), cmd3e cmd1em cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0não vai ser capaz de ler qualquer entrada do usuário. Isso exigirá mais descritores de arquivo. Por exemplo, para combinar

    diff <(echo foo) <(read var; echo "$var")
    

    você vai precisar de algo como

    { echo foo | { read var 0<&9; echo "$var" | diff /dev/fd/3 -; } 3<&0; } 9<&0
    

1 Mais sobre eles podem ser encontrados em U&L, por exemplo, em Entendendo / dev e seus subdiretórios e arquivos .

fra-san
fonte
12

Você pode reproduzir o que a concha faz sob o capô, fazendo o encanamento manualmente. Se o seu sistema tiver entradas, você pode usar a desarmação de descritores de arquivos: você pode traduzir/dev/fd/NNN

main_command <(produce_arg1) <(produce_arg2) >(consume_arg3) >(consume_arg4)

para

{ produce_arg1 |
  { produce_arg2 |
    { main_command /dev/fd5 /dev/fd6 /dev/fd3 /dev/fd4 </dev/fd/8 >/dev/fd/9; } 5<&0 3>&1 |
    consume_arg3; } 6<&0 4>&1; |
  consume_arg4; } 8<&0 9>&1

Eu mostrei um exemplo mais complexo para ilustrar várias entradas e saídas. Se você não precisar ler da entrada padrão, e o único motivo pelo qual você está usando a substituição do processo é que o comando requer um nome explícito de arquivo, você pode simplesmente usar /dev/stdin:

main_command <(produce_arg1)
produce_arg1 | main_command /dev/stdin

Sem , você precisa usar um pipe nomeado . Um pipe nomeado é uma entrada de diretório, portanto, você precisa criar um arquivo temporário em algum lugar, mas esse arquivo é apenas um nome, não contém dados./dev/fd/NNN

tmp=$(mktemp -d)
mkfifo "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
produce_arg1 >"$tmp/f1" &
produce_arg2 >"$tmp/f2" &
consume_arg3 <"$tmp/f3" &
consume_arg4 <"$tmp/f4" &
main_command "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
rm -r "$tmp"
Gilles 'SO- parar de ser mau'
fonte
2
</dev/fd/8 >/dev/fd/9não são equivalentes <&8 >&9no Linux e devem ser evitados lá.
Stéphane Chazelas 17/04/19
@ Gilles - Estou confuso com o exemplo complexo. Você poderia mostrar como emular diff <(cat "$2" | xz -d) <(cat "$1" | xz -d):?
Martin Vegter 02/08/19
3

Alguém pode explicar como implementar o exemplo a seguir?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

Eu acho que pipes nomeados são mais fáceis de grok do que redirecionamentos, portanto, em termos mais simples:

mkfifo p1 p2               # create pipes
cat "$2" | xz -d > p1 &    # start the commands in the background
cat "$1" | xz -d > p2 &    #    with output to the pipes
diff p1 p2                 # run 'diff', reading from the pipes
rm p1 p2                   # remove them at the end

p1e p2são pipes nomeados temporários, eles podem ter qualquer nome.

Seria mais inteligente criar os pipes /tmp, por exemplo, em um diretório, como mostra a resposta de Gilles , e observe que você não precisa cataqui, portanto:

tmpdir=$(mktemp -d)
mkfifo "$tmpdir/p1" "$tmpdir/p2"
xz -d < "$2" > "$tmpdir/p1" &
xz -d < "$1" > "$tmpdir/p2" &
diff "$tmpdir/p1" "$tmpdir/p2"
rm -r "$tmpdir"

(Você também pode fugir sem as aspas, pois mktempé provável que crie nomes de arquivos "agradáveis".)

A solução de tubulação tem a ressalva de que, se o comando principal ( diff) morrer antes de ler todas as entradas, os processos em segundo plano poderão ser interrompidos.

ilkkachu
fonte
0

A respeito:

cat "$2" | xz -d | diff /dev/sdtin /dev/stderr 2<<EOT
`cat "$1" | xz -d`
EOT
defdefred
fonte