Contents

如何编写回答交互式提示的Bash脚本

1. 概述

在编写 bash 脚本时,我们可能希望从我们调用的某个程序中回答交互式提示,以使流程自动化或使脚本在无人值守的情况下运行。

在本教程中,我们将了解如何使用不同的方法来回答 bash 脚本中的交互式提示。

我们将从回答是/否问题的简单脚本开始,例如常见的命令行应用程序。然后我们将继续使用更智能的脚本,这些脚本可以根据自定义逻辑回答提示。

2. 使用yes来回答是/否提示

yes 是一个简单的命令行应用程序,它内置于许多 Linux 发行版中。**如果出现提示,我们可以使用yes一遍又一遍地输出相同的答案,因此当程序询问是/否问题时,我们的脚本不会因等待输入而中断。**要使用yes,我们应该将其输出通过管道传输到我们使用它的程序的标准输入。

2.1. 询问是/否问题的示例命令

让我们看一个提示是/否答案的示例命令。如果我们运行:

touch test.txt
rm -i *.txt

我们会得到一个是/否提示:

/bin/rm: remove regular empty file 'test.txt'?

如果我们手动执行此操作,我们只需键入y并传递此提示。但是如果我们正在编写一个自动化脚本,我们不希望这个提示在执行过程中停止我们的脚本。

通常在这种情况下,我们应该更喜欢使用rm 命令的*-f标志来禁用提示。但是对于本文,我们将使用rm命令来解释如何使用yes*来回答是/否提示,通过对每个提示回答是或否来回答。

2.2. 使用yes

让我们在终端中运行yes命令:

<em>y
y
y
...</em>

它只会重复产生输出y

因此,为了自动化我们的脚本,我们可以将yes程序的标准输出流通过管道传输到我们想要回答以下提示的命令的标准输入流:

touch test.txt
yes | rm -i *.txt

因此,如果命令生成任何提示,我们的脚本将回答y,并且在执行期间不会被阻止。

2.3. 使用yes和参数

我们还可以确定yes命令的输出。例如,我们可以传递一个参数,以便它对每个提示回答n

yes n | rm -i *.txt

3. 使用printf回答多个问题

有时,我们可能需要编写一个脚本来回答多个提示——例如,自动安装软件或生成加密密钥。 如果一个脚本需要多个输入,我们可以简单地使用printf按照指定的顺序来回答它们。

3.1. 需要多个输入的示例脚本

让我们考虑一个 bash 脚本,它向用户提出三个简单的问题:

#!/usr/bin/env bash
echo "Hello, please introduce yourself."
echo -n "Your name: "
read -r name
echo "Are you human?"
echo -n "y/n: "
read -r human
echo "What is your favorite programming language?"
echo -n "Your answer: "
read -r lang
echo
echo "Answers:"
echo "1. $name"
echo "2. $human"
echo "3. $lang"

让我们将脚本保存到名为questions.sh的文件中并运行它:

$ chmod +x questions.sh
$ ./questions.sh
Hello, please introduce yourself.
Your name: Ziggy
Are you human?
y/n: y
What is your favorite programming language?
Your answer: Java
Answers:
1. Ziggy
2. y
3. Java

从上面的输出中我们可以看到,我们的脚本问了我们三个问题,然后打印了我们的答案。现在,让我们尝试自动回答脚本中的所有提示。

3.2. 将printf连接到我们的脚本

我们应该通过printf命令以正确的顺序输出我们的答案,它们之间带有换行符,并将它们传递到我们的脚本:

printf "bot\nn\nJava" | ./questions.sh

如果我们运行这个命令,我们会看到对问题的回答分别是“bot”、“n”和“Java”。请注意,我们在每个答案之间放置了换行符,以便将它们分别输入到三个不同的提示中。

4. 使用expect

有时,我们可能需要以更复杂的方式与程序交互。如果是这样,expect 是一个命令行应用程序,我们可以使用它来编写与程序交互的自定义逻辑脚本。e xpect充当脚本解释器,例如 bash,并具有自己的语法。

4.1. 编写一个简单的expect脚本

要等待我们的提示并回答它们,我们可以使用一个expect脚本:

#!/usr/bin/expect -f
set timeout -1
spawn ../4/questions.sh
expect "Your name: "
send -- "expect\n"
expect "Are you human?\r
y/n: "
send -- "n\r"
expect "What is your favorite programming language?\r
Your answer: "
send -- "Java\r"
expect eof

如果我们的系统上没有安装expect,我们可能需要通过包管理器 安装它才能工作。例如,在 Debian 或 Ubuntu 系统中,我们会使用:

apt install expect

让我们将此脚本保存到一个名为expect-script.exp的文件中,然后运行它:

chmod +x expect-script.exp
./expect-script.exp

我们将看到输入“expect”、“n”和“Java”按此顺序注册。

让我们看看在这种情况下, expect脚本做了什么:

  1. 由于第一行中定义的 shebang ( #! ),我们的 shell 尝试使用位于路径*/usr/bin/expect*的解释器运行脚本。
  2. 如果在这个位置安装并找到了expect ,它就会开始处理我们的脚本。
  3. 它按照指令在脚本文件中出现的顺序遍历指令,很像 bash 之类的脚本解释器。在这种情况下,指令是等待特定文本,并在收到这些特定文本后使用expectsend命令回答提示。

4.2. 使用autoexpect

**autoexpect 是自动生成expect脚本的好方法。**它通过运行原始应用程序记录我们的答案并将记录转换为expect脚本来做到这一点。要生成一个expect脚本来回答我们程序的提示,我们可以使用:

autoexpect questions.sh

这将运行questions.sh并开始记录我们的操作。在我们手动输入提示的答案后,我们的程序完成执行后会生成一个名为script.exp的脚本。

让我们检查生成的脚本:

#!/usr/bin/expect -f
#
# This Expect script was generated by autoexpect on Wed Aug 26 05:47:00 2020
# ...
set force_conservative 0  ;# set to 1 to force conservative mode even if
			  ;# script wasn't run conservatively originally
if {$force_conservative} {
    set send_slow {1 .1}
    proc send {ignore arg} {
	sleep .1
	exp_send -s -- $arg
    }
}
set timeout -1
spawn ../4/questions.sh
match_max 100000
expect -exact "Hello, please introduce yourself.\r
Your name: "
send -- "Ziggy\r"
expect -exact "Ziggy\r
Are you human?\r
y/n: "
send -- "y\r"
expect -exact "y\r
What is your favorite programming language?\r
Your answer: "
send -- "Java\r"
expect eof

正如我们所见,autoexpect生成期望命令并尝试检测在回答提示之前要等待哪些输出。