我有一个Bash脚本foo,它接受STDIN上的单词列表.脚本应该将单词读入数组,然后询问用户输入:
while IFS= read -r; do
words+=( "$REPLY" )
done
read -r ans -p "Do stuff? [Yn] "
echo "ans: |$ans|"
Run Code Online (Sandbox Code Playgroud)
问题是,Bash立即将空字符串读入ans变量,而不等待实际的用户输入.也就是说,我得到以下输出(没有提示或延迟):
$ cat words.txt | foo
ans: ||
Run Code Online (Sandbox Code Playgroud)
由于第一次read调用已经消耗了所有传输到STDIN的东西,为什么第二次read调用返回而没有实际读取任何内容?
从您的症状来看,看起来您已经重定向stdin,while通过输入文件(foo < file)或管道(... | foo)向循环提供单词列表.
如果是这样,您的第二个read命令将不会自动切换回终端读取 ; 它仍在读取stdin被重定向到的任何内容,并且如果已经消耗了该输入(这正是你的while循环所做的,正如chepner在注释中指出的那样),read什么都不读,并返回退出代码1(这就是终止了while循环开始).
如果您明确希望第二个read命令从终端获取用户输入,请使用:
read -r -p "Do stuff? [Yn] " ans </dev/tty
Run Code Online (Sandbox Code Playgroud)
注意:
从(有限)文件(或有限输出的管道或进程替换)重定向的Stdin是一种有限资源,一旦消耗了所有输入,它最终会报告EOF条件:
read将EOF条件转换为退出代码1,导致while循环退出:
read无法再读取任何字符,则将空字符串(空字符串)分配给指定的变量(或者$REPLY如果没有指定),并将退出代码设置为1.read可以设置退出代码,即如果输入结束时没有分隔符 ; 分隔符是默认的,否则分隔符明确使用指定.1$REPLY\n-d一旦消耗了所有输入,后续 read命令就不能再读取任何内容(EOF条件仍然存在,行为如上所述).
相比之下,来自终端的交互式 stdin输入可能是无限的:无论何时用户在请求stdin输入时交互式地输入附加数据.
在交互式多行输入(即终止输入循环)期间模拟 EOF条件的方法是按():^DControl-D
当^D按下一次在一行的最开始,read返回没有阅读任何东西,并设置退出代码1,就好像EOF遇到了一些.
相反,在输入行的内部,需要按^D 两次才能停止读取并将退出代码设置为1,但请注意,到目前为止键入的行将保存到目标变量/ $REPLY.[1]
由于stdin输入流实际上没有关闭,后续read命令正常工作并继续请求交互式用户输入.
警告:如果按^D在外壳的提示(相对于同时运行的程序请求输入),你将终止Shell本身.
PS:
问题中有一个偶然的错误:
read命令必须在所有选项之后放置操作数ans(用于存储输入的变量的名称)以便在语法上工作:read -r -p "Do stuff? [Yn] " ans[1]正如William Pursell在对该问题的评论中指出的那样:^D导致read(2)系统调用以此时缓冲区中的任何内容返回; 返回的直接值是读取的字符数.
计数0是EOF条件的信号,Bash read将其转换为退出代码1,从而导致循环终止.
因此,^D当输入缓冲区为空时,在行的开头按下,立即退出循环.
相比之下,如果已经在行上键入了字符,那么第一个 返回的^D原因read(2)是到目前为止输入了很多字符,Bash read 重新调用 read(2),因为尚未遇到分隔符(默认为换行符).
在紧随其后的第二 ^D进而导致read(2)返回0,因为没有字符输入一样,导致bash的read设置退出代码1和退出循环.