Script usando fifos não gerando saída ao processar de stdin

1

Estou tentando usar pipes nomeados para processar partes de dados de entrada em paralelo antes de colar os resultados juntos. Eu tinha algo funcionando até que eu adicionei a possibilidade de pegar a entrada de stdin (seguindo esta resposta ).

Aqui eu relato meu problema usando um exemplo simplificado que apenas seleciona colunas e não faz mais nenhum processamento antes de colar, mas o script real faz mais do que isso.

Exemplo de dados:

$ cat data.txt
A1  A2  A3  A4  A5
B1  B2  B3  B4  B5
C1  C2  C3  C4  C5
D1  D2  D3  D4  D5
E1  E2  E3  E4  E5
F1  F2  F3  F4  F5
G1  G2  G3  G4  G5
H1  H2  H3  H4  H5
I1  I2  I3  I4  I5
J1  J2  J3  J4  J5
K1  K2  K3  K4  K5
L1  L2  L3  L4  L5

Script usando arquivos normais:

$ cat test_files.sh
#!/bin/bash

get_1()
{
    cut -f1 - > ${1}
}

get_3()
{
    cut -f3 - > ${1}
}

get_5()
{
    cut -f5 - > ${1}
}


setup()
{
    workdir=$(mktemp -d)
    col1="${workdir}/col1.txt"
    col3="${workdir}/col3.txt"
    col5="${workdir}/col5.txt"
}

setup

cleanup()
{
    rm -rf ${workdir}
}

if [ $# -ge 1 -a -f "${1}" ]
then
    cat ${1} \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { echo "cat failed" && cleanup && exit 1; }
else
    cat - \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { echo "cat failed" && cleanup && exit 1; }
fi

paste ${col1} ${col3} ${col5}
cleanup
exit 0

Este funciona bem nos dois modos:

$ ./test_files.sh data.txt
A1  A3  A5
B1  B3  B5
C1  C3  C5
D1  D3  D5
E1  E3  E5
F1  F3  F5
G1  G3  G5
H1  H3  H5
I1  I3  I5
J1  J3  J5
K1  K3  K5
L1  L3  L5
$ cat data.txt | ./test_files.sh
A1  A3  A5
B1  B3  B5
C1  C3  C5
D1  D3  D5
E1  E3  E5
F1  F3  F5
G1  G3  G5
H1  H3  H5
I1  I3  I5
J1  J3  J5
K1  K3  K5
L1  L3  L5

Aqui está uma versão usando o fifos, onde executo a extração da coluna no fundo e cole dos fifos:

$ cat test_fifos.sh
#!/bin/bash

get_1()
{
    cut -f1 - > ${1}
}

get_3()
{
    cut -f3 - > ${1}
}

get_5()
{
    cut -f5 - > ${1}
}


setup()
{
    workdir=$(mktemp -d)
    col1="${workdir}/col1.txt"
    mkfifo ${col1}
    col3="${workdir}/col3.txt"
    mkfifo ${col3}
    col5="${workdir}/col5.txt"
    mkfifo ${col5}
}

setup

cleanup()
{
    rm -rf ${workdir}
}

if [ $# -ge 1 -a -f "${1}" ]
then
    cat ${1} \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { echo "cat failed" && cleanup && exit 1; } &
else
    cat - \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { echo "cat failed" && cleanup && exit 1; } &
fi

paste ${col1} ${col3} ${col5}
cleanup
exit 0

Funciona ao tomar um arquivo de entrada como argumento:

$ ./test_fifos.sh data.txt
A1  A3  A5
B1  B3  B5
C1  C3  C5
D1  D3  D5
E1  E3  E5
F1  F3  F5
G1  G3  G5
H1  H3  H5
I1  I3  I5
J1  J3  J5
K1  K3  K5
L1  L3  L5

Mas ao obter os dados do stdin, não há saída:

$ cat data.txt | ./test_fifos.sh
$ # Nothing here, no error message

Fazendo algumas experiências para gerar um exemplo mínimo, percebi que o código para lidar com possíveis erros parecia ser parte do problema. Aqui está uma versão usando fifos e não tentando lidar com erros:

$ cat test_fifos_noerr.sh
#!/bin/bash

get_1()
{
    cut -f1 - > ${1}
}

get_3()
{
    cut -f3 - > ${1}
}

get_5()
{
    cut -f5 - > ${1}
}


setup()
{
    workdir=$(mktemp -d)
    col1="${workdir}/col1.txt"
    mkfifo ${col1}
    col3="${workdir}/col3.txt"
    mkfifo ${col3}
    col5="${workdir}/col5.txt"
    mkfifo ${col5}
}

setup

cleanup()
{
    rm -rf ${workdir}
}

if [ $# -ge 1 -a -f "${1}" ]
then
    cat ${1} \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} &
else
    cat - \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} &
fi

paste ${col1} ${col3} ${col5}
cleanup
exit 0

Este funciona nos dois modos:

$ ./test_fifos_noerr.sh data.txt
A1  A3  A5
B1  B3  B5
C1  C3  C5
D1  D3  D5
E1  E3  E5
F1  F3  F5
G1  G3  G5
H1  H3  H5
I1  I3  I5
J1  J3  J5
K1  K3  K5
L1  L3  L5
$ cat data.txt | ./test_fifos_noerr.sh
A1  A3  A5
B1  B3  B5
C1  C3  C5
D1  D3  D5
E1  E3  E5
F1  F3  F5
G1  G3  G5
H1  H3  H5
I1  I3  I5
J1  J3  J5
K1  K3  K5
L1  L3  L5

Por que não há saída quando ambos lidam com possíveis erros e usam fifos enquanto pegam os dados de stdin?

Editar: alguma depuração

Adicionei alguns resultados de depuração ao script com falha:

$ cat test_fifos.sh
#!/bin/bash

get_1()
{
    >&2 echo "get_1"
    cut -f1 - > ${1}
}

get_3()
{
    >&2 echo "get_3"
    cut -f3 - > ${1}
}

get_5()
{
    >&2 echo "get_5"
    cut -f5 - > ${1}
}


setup()
{
    workdir=$(mktemp -d)
    col1="${workdir}/col1.txt"
    mkfifo ${col1}
    col3="${workdir}/col3.txt"
    mkfifo ${col3}
    col5="${workdir}/col5.txt"
    mkfifo ${col5}
}

setup

cleanup()
{
    >&2 echo "cleanup"
    rm -rf ${workdir}
}

if [ $# -ge 1 -a -f "${1}" ]
then
    >&2 echo "then"
    cat ${1} \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { >&2 echo "cat failed" && cleanup && exit 1; } &
else
    >&2 echo "else"
    cat - \
        | tee >(get_1 ${col1}) \
        | tee >(get_3 ${col3}) \
        | get_5 ${col5} \
        || { >&2 echo "cat failed" && cleanup && exit 1; } &
fi

>&2 echo "before paste"
paste ${col1} ${col3} ${col5}
>&2 echo "after paste"
cleanup
exit 0

Aqui está o que acontece quando os dados são lidos de stdin:

$ cat data.txt | ./test_fifos.sh
else
before paste
get_3
get_5
get_1
after paste
cleanup

Então, isso significa que a ramificação else é executada.

    
por bli 23.08.2017 / 14:16

1 resposta

1

O problema com o uso de & para executar comandos em segundo plano é que o shell fechará automaticamente o stdin para esse comando. Portanto, seu cat - está lendo imediatamente o final do arquivo. Como solução alternativa, você pode duplicar o descritor de arquivo 0 a 3, por exemplo, e usá-lo:

...
else    exec 3<&0
        cat - <&3 \
        | tee >(get_1 ${col1}) \
...
    
por 23.08.2017 / 19:30