为什么Bash读命令在没有任何输入的情况下返回?

时间:2016-07-20 14:29:23

标签: bash unix stdin

我有一个Bash脚本foo,它接受​​STDIN上的单词列表。脚本应该将单词读入数组,然后询问用户输入:

while IFS= read -r; do
  words+=( "$REPLY" )
done

read -r ans -p "Do stuff? [Yn] "
echo "ans: |$ans|"

问题是,Bash立即将空字符串读入ans变量,而不等待实际的用户输入。也就是说,我得到以下输出(没有提示或延迟):

$ cat words.txt | foo
ans: ||

由于第一次read通话已经消耗了所有传输到STDIN的内容,为什么第二个read调用会在没有实际读取内容的情况下返回?

1 个答案:

答案 0 :(得分:6)

根据您的症状判断,看起来重定向stdin ,以通过输入文件向while循环提供单词列表( foo < file)或通过管道(... | foo)。

如果是这样,您的第二个read命令将不会自动切换回从终端 读取;它仍在读取被重定向到的任何stdin,并且如果已经消耗了该输入(这正是您的while循环所做的那样,chepner在评论中指出),read什么都不读,并返回退出代码1(这是终止while循环开始的。)

如果您明确希望第二个read命令终端获取用户输入 ,请使用:

read -r -p "Do stuff? [Yn] " ans </dev/tty

注意:

  • 从(有限)文件重定向的Stdin (或使用有限输出的管道或进程替换)是有限资源,所有输入消耗后,最终会报告EOF条件

    • read将EOF条件转换为退出代码1,导致while循环退出:

      • 具体来说,如果read无法读取任何更多字符,则会将空字符串(空字符串)分配给指定的变量(或{如果没有指定,则{1}},并将退出代码设置为$REPLY 注意:1即使 读取字符(并将其存储在指定的变量/ read中),也可以设置退出代码1,即输入结束没有分隔符;默认情况下,分隔符为$REPLY,否则使用\n明确指定分隔符。
    • 消耗完所有输入后, 后续 -d命令不能再读取任何内容(EOF条件仍然存在,行为如上所述上文)。

  • 相比之下,来自终端 交互式标准输入 可能无限 < / strong>:无论何时用户在请求stdin输入时以交互方式输入附加数据。

    • 交互式多行输入期间 模拟 EOF条件的方法(即< strong>终止输入循环 )是按 ^ D Control-D ):

      • 并将退出代码设置为read,就像遇到EOF一样。

        • 换句话说:在循环中终止无限制交互式输入的方法是在 提交之后按 ^ D 最后一行输入。
      • 相比之下,在输入行的内部中,需要按 ^ D 两次才能停止阅读并设置将代码退出到read,但请注意,到目前为止输入的行已保存到目标变量/ 1 [1]

    • 由于标准输入流实际上并未关闭 后续1命令正常工作并继续征求互动用户意见。

    • 警告:如果您在 shell 的提示符下按 ^ D (而不是在运行时)程序正在请求输入),你将终止shell本身。

P.S:

问题中有 偶然的错误

  • 第二个$REPLY命令必须在所有选项之后放置操作数read(用于存储输入的变量的名称)以便在语法上工作:{{1 }}

[1]正如William Pursell在对该问题的评论中指出: ^ D 导致read系统调用返回缓冲区中的任何内容在那时候;返回的直接值是读取的字符数 ans的计数是指示EOF条件的信号,Bash的read -r -p "Do stuff? [Yn] " ans会将其转换为退出代码read(2),从而导致循环终止。
因此,在行的开头按 ^ D ,当输入缓冲区为空时,立即退出循环。
相比之下,如果已在行上键入字符,那么 first ^ D 会导致0返回,但到目前为止输入的字符很多, Bash的read 重新调用 1,因为尚未遇到分隔符(默认为换行符)。
紧接着第二个 ^ D 之后的会导致read(2)返回read,因为没有输入任何字符,导致Bash的read(2)被设置退出代码read(2)并退出循环。