Linux Bash相当于Windows批处理暂停
1. 简介
出于多种原因,我们可能希望停止脚本的执行并等待单个按键。虽然在 Windows 中存在这样的工具作为暂停 批处理命令,但 Linux 没有直接的等价物。
在本教程中,我们将讨论 Linux 用户使用 Bash模拟或模拟批处理暂停命令的选项。首先,我们检查 Bash 脚本的运行方式以及我们对其流程的控制。接下来,我们讨论用户输入机制以及如何使用这些机制来获取批处理暂停的行为。最后,我们探索了一种在 Linux 下开发具有相同功能的工具的选项。
我们使用 GNU Bash 5.1.4 在 Debian 11 (Bullseye) 上测试了本教程中的代码。它应该在大多数符合 POSIX 的环境中工作。
2. 运行 Bash 脚本
在大多数操作系统下,包括 Linux,我们可以通过多种方式运行脚本:
- shell 中的单行命令
- shell中的脚本文件
- 来自图形用户界面 (GUI) 的脚本文件
- 来自第三方应用程序的单行或脚本文件
前两种方法保留上下文 shell,除非我们执行的命令将其杀死:
$ echo 'Output 1.'; echo 'Output 2.'
Output 1.
Output 2.
$
$ echo "echo 'Output.'" > script.sh
$ bash script.sh
Output.
$
另一方面,后两者经常生成一个 shell 来运行我们的脚本。之后,shell 可能会退出并消失。
此外,某些运行脚本的方式会禁用交互式输入。事实上,如果我们想修改执行流程,这至关重要。我们什么时候想这样做?
3. Bash 中的流控制
许多脚本在无人看管的情况下执行其任务并退出。其他人需要用户输入,具体取决于条件。尽管如此,当用户控制他们的执行速度时,其他人会更方便。
让我们检查以下简单脚本longfast.sh:
for row in {1..1000}; do
echo $row
done
上面的代码在连续的行上打印从一到一百的数字。值得注意的是,这里有两个潜在的可用性问题。
首先,根据屏幕和设置,用户可能无法一次看到所有输出。其次,根据脚本的运行方式,用户最终可能什么都看不到,因为脚本通常结束得太快。
事实上,由于这些缺点,诸如more、less和most 之类的工具会在文本到达屏幕末尾时进行检测。至关重要的是,一旦他们这样做了,默认情况下输出会停止,直到用户按下一些特定的键:
$ bash longfest.sh | more
1
2
[...]
65
66
--More--
虽然我们不能轻易改变这些工具的行为,但我们可以编写自己的。当我们想告诉 Bash 任何键是可接受的以便继续时会发生什么?
4. Bash 用户输入
当然,在众多获取输入的方式中,并非所有方式都是交互式的。由于我们要启用用户流控制,让我们看看一些选项。
4.1. read命令
事实上,Linux 提供了内置的多功能read . 在最基本的形式中,它允许我们将输入分配给变量:
$ read var
value
$ echo $var
value
在这里,我们将直到默认分隔符IFS 的任何输入分配给*$var*。但是,我们也可以使用*-p标志添加提示,使用**-t指定超时并通过-N* 限制字符数。
4.2. /dev/tty和*/dev/console*
一种更有针对性的获取输入的方法是使用重定向 。具体来说,重定向到 tty 驱动程序或从tty驱动程序 重定向:
$ read var < /dev/tty
value
$ echo $var
value
此外,我们可以将该方法与dd (Data Duplicator)等其他工具结合使用。特别是,我们可以从*/dev/tty或/dev/console*复制数据以获取输入。
那么我们如何使用上述方法来复制我们所追求的暂停?
5. Bash 批处理暂停
所讨论的方法的变体使我们能够根据具体情况选择最佳解决方案。让我们探索我们的暂停选项。
5.1. 纯read
当然,我们总是可以只使用最简单的read形式,并要求用户按“返回”以继续:
$ read
any input until delimiter
$
除了简单之外,我们并没有什么收获:
- 没有提示
- 用户可以按任意数量的键
- 屏幕回显按下的键
- 要继续,必须按 Return 或终止信号 组合键
- 在下一个提示之前有一个换行符
我们怎样才能使它变得更好?
5.2. 增强read
作为对基本read版本的增强,我们可以使用*-p添加提示,使用-s静音输出,使用-r*接收原始输入:
$ read -rsp $'Press enter to continue...\n'
Press enter to continue...
$
注意在下一个提示之前没有换行符,因为我们不输出按键。
现在read更加人性化,但仍然需要“返回”才能继续。为了解决这个问题,我们可以使用*-d*标志:
$ read -rsp $'Press escape to continue...\n' -d $'\e'
Press escape to continue...
$
请注意,-d设置输入应结束的分隔符。或者,我们可以在切断输入之前设置允许的字符数。
5.3. 任意键read
实际上,只允许缓冲一个字符将在任何键上取消暂停:
$ read -rsp $'Press any key to continue...\n' -n 1
Press any key to continue...
$
此版本通过使用*-n标志非常接近地模拟 Windows暂停*。不过,还有其他方法。
5.4. 使用*/dev/tty和dd*暂停
使用*/dev/tty*,我们可以捕获 所有 EXIT 条件,并与stty 工具做出反应:
$ ( trap "stty $(stty -g;stty -icanon;)" EXIT
stty -echo
echo 'Press any key to continue...'
LC_ALL=C dd bs=1 count=1 >/dev/null 2>&1
) </dev/tty
Press any key to continue...
$
首先,我们捕获脚本 EXIT,确保正确恢复所有tty设置。之后,我们使用stty -echo来防止将我们按下的字符回显到终端。接下来,我们向用户呈现一个提示。最后,我们将来自*/dev/tty的任何输入定向到单个字符的dd*。
6. 实现暂停和缓冲
Linux 允许我们开发自己的批量暂停工具。但是,有一个大问题:行缓冲 。
由于默认缓冲区,实际上没有规范的方法来防止在单个字符之后进一步输入。因此,我们在上面使用了icanon。此标志处理输入的 POSIX 模式 之间的切换。
事实上,在非规范模式下,我们可以更好地控制输入何时结束。这意味着我们可以一个一个地处理原始字符。
所以,要模拟我们的暂停,我们需要:
- 在终端中进入非规范模式
- 使用支持此模式及其缓冲的库
- 使用模拟暂停的功能
让我们看看我们拥有什么。
7. 使用*getch()*暂停
有几个非标准编译器库包含一个名为*getch()*的函数,它也不是 C 标准的一部分。*getch()*函数在单次按键后等待并终止,返回相关的字符代码。
特别是,我们可以在conio.h和curses.h头文件中找到它。虽然前者大多只在 MS-DOS 和 Win32 下可用,但后者在多个平台上都有端口。
在 Linux 下使用curses.h中的getch(),我们可以轻松地实现我们的批处理暂停作为pause.c源:
#include <curses.h>
int main() {
filter();
initscr();
cbreak();
printw("Press any key to continue...");
getch();
endwin();
return 0;
}
首先,我们需要安装ncurses 库包,这是在 Debian 中使用apt-get install libncurses5-dev libncursesw5-dev完成的。之后,我们使用gcc pause.c -o pause -lncurses进行编译。
然而,这就是简单性结束的地方,因为这种方法存在一个已知的错误。对于我们使用ncurses,必须清除屏幕。这首先破坏了使用暂停的部分目的:能够检查当前屏幕内容。
克服这个问题不在本教程的范围内,但解决方案涉及与我们在上面看到的 Bash 类似的终端修改。