use*_*456 9 scripting bash shell-script user-input read
此脚本一行接一行地接受用户输入,并myfunction在每一行上执行
#!/bin/bash
SENTENCE=""
while read word
do
myfunction $word"
done
echo $SENTENCE
Run Code Online (Sandbox Code Playgroud)
要停止输入,用户必须按[ENTER],然后按Ctrl+D。
如何重建我的脚本以仅结束Ctrl+D并处理Ctrl+D按下的行。
为此,您必须逐个字符地阅读,而不是逐行阅读。
为什么?shell 很可能使用标准 C 库函数read()
来读取用户输入的数据,并且该函数返回实际读取的字节数。如果它返回零,那意味着它遇到了 EOF(参见read(2)手册;man 2 read)。请注意,EOF 不是字符而是条件,即条件“没有什么可读取的”,文件结尾。
Ctrl+D向终端驱动程序发送一个传输结束字符
(EOT,ASCII 字符代码 4,$'\04'输入bash)。这具有将要发送的任何内容发送到read()shell的等待调用的效果。
当您Ctrl+D在一行中输入文本时按下一半,到目前为止您输入的任何内容都会发送到外壳1。这意味着如果您Ctrl+D在一行中输入内容后输入
两次,第一个将发送一些数据,第二个将不发送任何数据,并且read()调用将返回零并且 shell 将其解释为 EOF。同样,如果您按下Enter后跟Ctrl+D,外壳会立即获得 EOF,因为没有任何数据要发送。
那么如何避免必须输入Ctrl+D两次呢?
正如我所说,阅读单个字符。当您使用readshell 内置命令时,它可能有一个输入缓冲区并要求read()从输入流中读取最多那么多字符(可能是 16 kb 左右)。这意味着 shell 将获得一堆 16 kb 的输入块,然后是一个可能小于 16 kb 的块,然后是零字节 (EOF)。一旦遇到输入的结尾(或换行符或指定的分隔符),控制权将返回给脚本。
如果您习惯于read -n 1读取单个字符,shell 将在调用 时使用单个字节的缓冲区read(),即它将坐在一个紧密循环中逐个字符读取,在每个字符之后将控制权返回给 shell 脚本。
唯一的问题read -n是它将终端设置为“原始模式”,这意味着字符在没有任何解释的情况下按原样发送。例如,如果您按Ctrl+D,您将在字符串中得到一个文字 EOT 字符。所以我们必须检查一下。这也有副作用,即用户将无法在将其提交到脚本之前编辑该行,例如通过按Backspace,或使用Ctrl+W(删除前一个单词)或Ctrl+U(删除到行首) .
长话短说:以下是您的bash脚本在读取一行输入时需要执行的最后一个循环
,同时允许用户通过按 随时中断输入
Ctrl+D:
while true; do
line=''
while IFS= read -r -N 1 ch; do
case "$ch" in
$'\04') got_eot=1 ;&
$'\n') break ;;
*) line="$line$ch" ;;
esac
done
printf 'line: "%s"\n' "$line"
if (( got_eot )); then
break
fi
done
Run Code Online (Sandbox Code Playgroud)
对此无需过多赘述:
IFS=清除IFS变量。没有这个,我们将无法读取空格。我使用read -N代替read -n,否则我们将无法检测到换行符。使我们能够正确读取反斜杠的-r选项read。
该case语句作用于每个读取的字符 ( $ch)。如果$'\04'检测到EOT ( ),它将设置got_eot为 1,然后通过break语句将其从内部循环中取出。如果$'\n'检测到换行符 ( ),它就会跳出内部循环。否则,它将字符添加到line变量的末尾。
在循环之后,该行被打印到标准输出。这将是您调用使用"$line". 如果我们通过检测到 EOT 到达这里,我们将退出最外层循环。
1您可以通过cat >file在一个终端和tail -f file另一个终端中运行来测试这一点,然后在 中输入部分行
cat并按下Ctrl+D以查看 的输出中会发生什么tail。
对于ksh93用户:上面的循环将读取一个回车符而不是换行符ksh93,这意味着测试 for$'\n'将需要更改为测试 for $'\r'。shell 也会将这些显示为^M.
要解决此问题:
stty_saved="$(stty -g)" stty - echoctl #循环到这里,$'\n' 替换为 $'\r' stty "$stty_saved"
您可能还想在 之前显式输出换行符break以获得与 完全相同的行为bash。