为什么在bash中的 while read 循环中要重定向 stdin?

5
考虑以下示例脚本:

请注意以下示例脚本:

#!/bin/sh

do_something() {
    echo $@
    return 1
}

cat <<EOF > sample.text
This is a sample text
It serves no other purpose
EOF

cat sample.text | while read arg1 arg2 arg3 arg4 arg5; do
    ret=0
    do_something "$arg1" "$sarg2" "$arg3" "$arg4" "$arg5" <&3 || ret=$?
done 3<&1

重定向 stdout 作为文件描述符 3 的输入的目的是什么? 至少在 Bash 中,如果省略了它似乎没有任何区别。如果在除 bash 以外的任何其他 shell 中执行,它是否有任何影响?

更新

对于那些想知道这是从哪里来的人,这是来自 Debian 的 cryptdisks_start 脚本的简化示例。


1
这个脚本是从哪里来的?我很好奇作者的意图。由于 do_something() 没有使用标准输入,所以 <&3 == 0<&3 没有任何区别,正如你所观察到的那样。 - cxw
@cxw 请看我的更新 - user4918296
@chepner 原始代码来自 cryptdisks_start(请参见更新)。我希望我没有过于简化问题。 - user4918296
@CharlesDuffy 这是一个示例脚本,即我在帖子的更新部分链接到的另一个脚本的简化版本 - user4918296
1
在基于Web的SCM界面(sourceforge、github、launchpad等)的时代,大多数项目都有一个单独的源代码行可以直接链接到Web上。因此,“下载这个tarball,解压缩它,并找到包含[...]的文件”不是一个非常合理的请求。 - Charles Duffy
显示剩余11条评论
1个回答

12

这里的明确意图是防止 do_somethingsample.text 流中读取,通过确保其标准输入来自其他位置。如果您在测试中使用重定向与否没有看到行为差异,那是因为 do_something 实际上没有从标准输入中读取。

如果你让 readdo_something 都从同一个流中读取,那么被 do_something 消耗的任何内容都不会对后续实例的 read 可用 - 当然,您将会得到非法内容作为 do_something 的输入,导致诸如尝试使用错误的加密密钥(如果真实世界应用场景类似于 cryptmount)等后果。

cat sample.text | while read arg1 arg2 arg3 arg4 arg5; do
    ret=0
    do_something "$arg1" "$sarg2" "$arg3" "$arg4" "$arg5" <&3 || ret=$?
done 3<&1

现在它有一个bug--相比于3<&03<&1是不好的实践,因为它基于毫无根据的假设,即标准输出(stdout)可以用作输入。但它确实成功地达到了目标。


顺便提一下,我会更改成以下方式:

exec 3</dev/tty || exec 3<&0     ## make FD 3 point to the TTY or stdin (as fallback)

while read -a args; do           ## |- loop over lines read from FD 0
  do_something "${args[@]}" <&3  ## |- run do_something with its stdin copied from FD 3
done <sample.text                ## \-> ...while the loop is run with sample.txt on FD 0

exec 3<&-                        ## close FD 3 when done.

这样写有点啰嗦,需要显式关闭FD 3,但它意味着我们的代码不再因为stdout附加到FIFO(或任何其他只写接口)的写入端而直接连接到TTY而出现错误。


至于此做法可以防止的bug,它是非常常见的。例如,以下是关于它的几个StackOverflow问答:

等等。





















抱歉回复晚了。我终于有时间回到这个问题上了。我知道如果stdin没有被正确重定向/保护,read可能会产生“有趣”的结果。让我困惑的是将stdout重定向为输入。作者似乎试图强制实现某种管道行为。我以前从未见过这种情况。在我的问题中,是否真的有合法的理由来重定向stdout - user4918296
@nautical,“ever”覆盖了很多情况。也许stdin被打开到/dev/null,stdout是读/写打开的(所以您也可以从中读取),并且您没有控制TTY。但这是一个边角案例,必须有人知道他们的脚本将在该边角案例中使用才有意义。 - Charles Duffy
1
@CharlesDuffy:在你添加代码注释之前,我对解决方案的理解有些困难。现在有了注释,我能够理解了。谢谢。 - adfaklsdjf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接