我有一个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
调用会在没有实际读取内容的情况下返回?
答案 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 两次才能停止阅读并设置将代码退出到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)
并退出循环。