![从标准输入处理时,使用 fifos 的脚本不生成输出](https://linux22.com/image/10832/%E4%BB%8E%E6%A0%87%E5%87%86%E8%BE%93%E5%85%A5%E5%A4%84%E7%90%86%E6%97%B6%EF%BC%8C%E4%BD%BF%E7%94%A8%20fifos%20%E7%9A%84%E8%84%9A%E6%9C%AC%E4%B8%8D%E7%94%9F%E6%88%90%E8%BE%93%E5%87%BA.png)
我正在尝试使用命名管道并行处理输入数据,然后将结果粘贴回一起。我有一些工作,直到我添加了从标准输入获取输入的可能性(以下这个答案)。
在这里,我使用一个简化的示例报告我的问题,该示例仅选择列并且在粘贴之前不进行进一步处理,但实际脚本的作用远不止于此。
示例数据:
$ 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}) \
...