dash: Pipe STDIN para vários comandos e sua saída para STDOUT em ordem definida

6

No começo, pensei que esta resposta era a solução, mas agora acho que preciso de um arquivo temporário como buffer .

Isso funciona de maneira não confiável:

#!/bin/sh
echo 'OK' |
{
    {
        tee /dev/fd/3 | head --bytes=1 >&4
    } 3>&1 | tail --bytes=+2 >&4
} 4>&1

Quando eu executo isso em um terminal, às vezes eu recebo:

OK

e às vezes eu recebo:

K
O

Parece totalmente aleatório. Então, como uma solução alternativa, estou escrevendo a saída de tail em um arquivo e lendo de volta para stdout após o término do pipe.

#!/bin/sh
echo 'OK' |
{
    {
        tee /dev/fd/3 | head --bytes=1 >&4
    } 3>&1 | tail --bytes=+2 >file
} 4>&1
cat file

Isso pode ser feito em dash sem arquivos temporários? As variáveis do shell como buffer não são uma opção, pois a saída pode conter bytes NUL.

    
por Kontrollfreak 05.02.2017 / 19:13

2 respostas

1

Se você quisesse executar os consumidores e o produtor em paralelo, mas serializasse a saída dos consumidores, seria necessário atrasar a saída do segundo consumidor. Para isso, você precisa armazenar sua saída de alguma forma e a melhor maneira é com um arquivo temporário.

com zsh :

{cat =(producer > >(consumer1 >&3) | consumer2)} 3>&1

bash tem um problema, pois não espera pelos comandos de substituição do processo, portanto, é necessário usar métodos de trabalho desagradáveis .

Aqui, usamos a forma =(...) de substituição de processo para armazenar a saída de comsumer2 em um arquivo temporário e cat depois. Não podemos fazer isso por mais de dois consumidores. Para isso, precisaríamos criar os arquivos temporários manualmente.

Quando não estivermos usando =(...) , teremos que manipular a limpeza dos arquivos temporários manualmente. Podemos lidar com isso criando e excluindo-os na frente para não termos que nos preocupar com os casos em que o script é morto. Ainda com zsh :

tmp1=$(mktemp) && tmp2=$(mktemp) || exit
{
  rm -f -- $tmp1 $tmp2
  producer > >(consumer1) > >(consumer2 >&3) > >(consumer3 >&5)
  cat <&4 <&6
} 3> $tmp1 4< $tmp1 5> $tmp2 6< $tmp2

Editar (eu inicialmente perdi o fato de que uma solução para dash era necessária)

Para dash (ou qualquer shell POSIX que não defina o sinalizador close-on-exec em fds acima de 2 e use pipes e não pares de soquetes para | ) e em sistemas com /dev/fd/x support:

tmp1=$(mktemp) && tmp2=$(mktemp) || exit
{
  rm -f -- "$tmp1" "$tmp2"
  {
    {
      {
        producer | tee /dev/fd/4 /dev/fd/6 | consumer1 >&7
      } 4>&1 | consumer2 >&3
    } 6>&1 | consumer3 >&5
  } 7>&1
  cat - /dev/fd/6 <&4
} 3> "$tmp1" 4< "$tmp1" 5> "$tmp2" 6< "$tmp2"

Isso funcionaria com dash , bash , zsh , mksh , busybox sh , posh no Linux, mas não ksh93 . Essa abordagem não pode ir além de 4 consumidores, pois estamos limitados a fds de 0 a 9.

    
por 17.09.2017 / 15:50
1

A melhor solução é para usar arquivos temporários. Isso torna o código legível e fácil de entender quando a substituição do processo não é uma opção.

tmpfile=$(mktemp)

producer | tee "$tmpfile" | consumer1
consumer2 <"$tmpfile"

rm -f "$tmpfile"

ou até mesmo

tmpfile=$(mktemp)

producer >"$tmpfile"

consumer1 <"$tmpfile"
consumer2 <"$tmpfile"

rm -f "$tmpfile"
    
por 17.09.2017 / 15:07