从标准输入处理时,使用 fifos 的脚本不生成输出

从标准输入处理时,使用 fifos 的脚本不生成输出

我正在尝试使用命名管道并行处理输入数据,然后将结果粘贴回一起。我有一些工作,直到我添加了从标准输入获取输入的可能性(以下这个答案)。

在这里,我使用一个简化的示例报告我的问题,该示例仅选择列并且在粘贴之前不进行进一步处理,但实际脚本的作用远不止于此。

示例数据:

$ 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

使用普通文件的脚本:

$ 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

这个在两种模式下都能很好地工作:

$ ./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

这是使用 fifos 的版本,我在后台执行列提取并从 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

当将输入文件作为参数时它可以工作:

$ ./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

但是当从 stdin 获取数据时,没有输出:

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

通过进行一些实验来生成一个最小的示例,我意识到处理可能错误的代码似乎是问题的一部分。这是使用 fifos 且不尝试处理错误的版本:

$ 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

这个可以在两种模式下工作:

$ ./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

当我在从 stdin 获取数据时处理可能的错误并使用 fifos 时,为什么没有输出?


编辑:一些调试

我向失败的脚本添加了一些调试输出:

$ 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

从 stdin 读取数据时会发生以下情况:

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

所以这意味着该else分支被执行了。

答案1

&使用在后台运行命令的问题是 shell 会自动关闭该命令的标准输入。所以你cat -会立即读取文件结尾。例如,作为解决方法,您可以将文件描述符 0 复制到 3,然后使用:

...
else    exec 3<&0
        cat - <&3 \
        | tee >(get_1 ${col1}) \
...

相关内容