如何将 GUI 添加到 Linux Shell 脚本

您可以在 Bash 脚本中使用 GUI 窗口、滑块、单选按钮、进度条等。了解如何使用 zenity
工具包并让您的 Bash 脚本焕然一新。我们会告诉你怎么做。
Bash 脚本是一种功能强大的编程语言,因为它内置于 Bash shell 中,所以每个人都可以轻松使用。这是一种很容易开始编程的语言。因为它是解释性的,所以你不需要编译你的脚本。一旦您编辑了脚本文件并使其可执行,您就可以运行它。这使得编码、运行和调试周期非常高效。
人们对 Bash 脚本有两个主要的抱怨,第一个是速度。因为 Bash shell 解释脚本中的命令,所以它们的执行速度不如编译后的代码。然而,这就像抱怨拖拉机没有汽车快一样;它们适用于不同的事物。
但是,有两种速度。与使用编译语言(例如 C)开发解决方案相比,您通常可以快速编写一个脚本并使用它来执行任务,这要快得多。
人们对 Bash 脚本的第二个抱怨是用户界面——它是一个终端窗口。当然,有时界面并不重要。如果唯一会使用该脚本的人是它的作者,那么界面可能就没那么重要了。对于执行后台和批处理类型处理的脚本也无关紧要。通常,此类脚本不需要太多(如果有的话)用户交互。
有时您确实需要比终端窗口更直观、更现代的东西。大多数人都熟悉图形用户界面 (GUI)。为了给人们尽可能顺畅的体验,您必须从脚本中创建和使用 GUI 元素。
zenity 应用程序
zenity
允许您在 Bash 脚本中加入各种图形界面元素。它是一个功能强大的工具包,可为您的脚本带来现代感和现代、熟悉的外观。
zenity
预装在 Ubuntu、Fedora 和 Manjaro 发行版上。它是 GNOME 的一部分。如果您使用 KDE,尽管 zenity
确实可以在任何桌面环境中运行,但您可能需要查看 kdialog
。
本文中的示例向您展示了如何从命令行创建不同的对话窗口,如何在变量中捕获它们的返回值和用户选择,以及如何在脚本中使用对话窗口。
我们以一个使用所有三种类型的对话窗口的小应用程序结束。
日历对话框窗口
日历对话窗口允许某人选择日期。要使用 zenity
创建一个,需要一个包含两个词的命令:
zenity --calendar
出现日历对话窗口。这具有您期望从标准日期选择器获得的所有功能。您可以更改月份和年份,然后单击一天以选择该日期。默认情况下,当窗口出现时,今天的日期会突出显示。

单击“确定”关闭对话框窗口并选择突出显示的日期。双击日期做同样的事情。
如果您不想选择日期,请单击“取消”,按键盘上的“Esc”键,或关闭对话框窗口。

在上面的示例中,选择了 2019 年 8 月 19 日。如果用户单击“确定”,日历将关闭,所选日期将打印在终端窗口中。

你可以忽略这一行,“GTKDialog mapped without a transient parent.这是令人沮丧的。”
GTK全称是GIMP Tool Kit,是用来开发GNOME界面的工具包。它最初是由 GNU 图像处理程序 (GIMP) 的作者设计的。 GNU 代表 GNU’s Not Unix。
GTK 引擎警告 zenity
的作者他们以非标准方式使用了 GTK 组件。
捕获日期值
将日期打印到终端对我们来说没什么用。如果我们要从我们的脚本之一调用此日历,我们需要捕获选定的日期值,以便我们可以在我们的脚本中使用它做一些有用的事情。我们还将稍微自定义日历。
我们将在日历中使用以下选项。它们都必须与双破折号“-”标志一起使用:
- –text: 指定要在日历中显示的文本字符串。它取代了默认设置,“从下面选择一个日期。”
- –title:设置日历对话窗口的标题。
- –day:设置打开日历时选择的日期。
- –month:设置打开日历时选择的月份。
- –year:设置打开日历时选择的年份。
我们使用一个名为 ChosenDate
的变量来捕获从日历返回的日期。我们正在使用 echo $ChosenDate
将该日期打印到终端窗口。
是的,我们在前面的例子中取得了相同的结果,但在这里,我们将选定的日期存储在一个变量中。在前面的示例中,它被打印并被遗忘了。
ChosenDate=$(zenity -- calendar --text "Choose a date" --title "How-To Geek Rota" --day 1 -- month 9 --year 2019); echo $ChosenDate

现在,日历显示我们的提示和窗口标题。日期设置为我们选择的开始日期而不是今天的日期。

我们还可以自定义进行选择时返回的日期字符串的格式。 --date-format
选项后面必须跟一个格式说明符。这是一串标记,用于定义要包含在输出中的数据和格式。这些标记与用于 strftime()
C 语言函数的标记相同,并且有大量选择。
我们使用的代币是:
- %A:星期几的全名。
- %d:以数字表示的月份中的第几天。
- %m:以数字表示的月份。
- %y:两位数的年份(无世纪)。
ChosenDate=$(zenity -- calendar --text "Choose a date" --title "How-To Geek Rota" --date-format="%A %d/%m/%y" --day 1 -- month 9 --year 2019); echo $ChosenDate

有人选择了一个日期:

并使用我们的格式返回日期。它显示星期几的名称,后跟欧洲顺序中的日期:日、月、年。

文件选择对话框窗口:选择文件
文件选择对话框窗口非常复杂。人们可以浏览文件系统,突出显示一个或多个文件,然后单击“确定”以选择这些文件或完全取消选择。
zenity
提供了所有这些功能,甚至更多。而且它和日历对话框窗口一样易于使用。
我们将要使用的新选项是:
- –file-selection:告诉
zenity
我们想要使用文件选择对话框窗口。 - –multiple:允许某人选择多个文件。
- –file-filter:告诉文件对话窗口要显示哪些文件类型。
zenity --file-selection --tile "How-To Geek" --multiple --file-filter='*.mm *.png *.page *.sh *.txt'

文件选择对话框窗口的功能与任何其他文件选择窗口一样。

用户可以浏览文件系统并选择她选择的文件。

我们浏览到一个新目录并选择了一个名为“button_hybrid.png”的文件。
单击“确定”后,文件选择对话框窗口关闭,文件名和路径打印在终端窗口中。

如果您需要在任何进一步的处理中使用文件名,您可以将其捕获在一个变量中,就像您对日历中的日期所做的那样。
文件选择对话窗口:保存文件
如果我们添加一个选项,我们可以将文件选择对话框窗口变成文件保存对话框窗口。选项是 --save
。我们还将使用 --confirm-overwrite
选项。这会提示此人确认他要覆盖现有文件。
Response=$(zenity --file-selection --save --confirm-overwrite); echo $Response

出现文件保存对话窗口。请注意,有人可以在其中键入文件名的文本字段。

用户可以浏览到他在文件系统中选择的位置,为文件提供名称,或单击现有文件以覆盖它。

在上面的示例中,用户突出显示了一个现有文件。
当他单击“确定”时,会出现一个确认对话窗口,要求他确认是否要替换现有文件。注意文件名出现在警告对话框中。正是这种对细节的关注赋予了 zenity
专业的外观。
如果我们没有使用 --confirm-overwrite
选项,文件就会被静默覆盖。

文件名存储在变量 Response
中,它会打印到终端窗口。

通知对话框窗口
使用 zenity
,可以毫不费力地在您的脚本中包含流畅的通知对话框窗口。您可以调用常用对话窗口来为用户提供信息、警告、错误消息和问题。
要创建错误消息对话框窗口,请使用以下命令:
zenity --error --width 300 --text "Permission denied. Cannot write to the file."
我们使用的新选项是:
- –error:告诉
zenity
我们想要使用错误对话框窗口。 - –width:设置窗口的初始宽度。

错误对话框窗口出现在指定的宽度。它使用标准的 GTK 错误图标。

要创建信息对话窗口,请使用以下命令:
zenity --info --width 300 --text "Update complete. Click OK to continue."
我们使用的新选项是 --info
,它告诉 zenity
创建一个信息对话框窗口。

要创建问题对话窗口,请使用以下命令:
zenity --question --width 300 --text "Are you happy to proceed?"; echo $?
我们使用的新选项是 --question
,它告诉 zenity
创建一个问题对话框窗口。

$?
是一个特殊参数。它保存最近执行的前台管道的返回值。一般来说,这是最近关闭的进程的值。零值表示“确定”,一个或多个值表示“取消”。
这是一种通用技术,您可以将其应用于任何 zenity
对话框窗口。通过在脚本中检查此值,您可以确定是否应处理或忽略从对话窗口返回的数据。

我们单击“是”,因此返回码为零,表示“确定”。

要创建警告对话框窗口,请使用以下命令:
zenity --warning --title "Low Hard Drive Space" --width 300 --text "There may not be enough hard drive space to save the backup."
我们使用的新选项是 --warning
,它告诉 zenity
创建一个警告对话框窗口。

出现警告对话窗口。这不是一个问题,所以它只有一个按钮。

进度对话框窗口
您可以使用 zenity
进度对话框窗口来显示一个进度条,指示脚本完成的程度。
进度条根据从您的脚本输入的值前进。为了演示原理,请使用以下命令:
(for i in $(seq 0 10 100); do echo $i; sleep 1; done)

该命令分解如下:
seq
命令以 10 为步长执行从 0 到 100 的序列。- 在每一步中,值都存储在变量
i
中。这将打印到终端窗口。 - 由于
sleep 1
命令,命令暂停一秒钟。
我们可以将其与 zenity
进度对话框窗口一起使用来演示进度条。请注意,我们将上一个命令的输出传送到 zenity:
(for i in $(seq 0 10 100); do echo $i; sleep 1; done) | zenity --progress --title "How-To Geek" -- auto-close

我们使用的新选项是:
- –progress:告诉
zenity
我们想要使用进度对话框窗口。 - –auto-close:当进度条达到 100% 时关闭对话框。
出现进度对话框窗口,进度条向 100% 前进,每一步之间暂停一秒钟。

我们可以使用管道值到 zenity
的概念来将进度对话框窗口包含在脚本中。
在编辑器中输入此文本并将其保存为“progress.sh”。
!/bin/bash
function work-list () {
echo "# First work item"
echo "25"
sleep 1
echo "# Second work item"
echo "50"
sleep 1
echo "# Third work item"
echo "75"
sleep 1
echo "# Last work item"
echo "100"
sleep 1
}
work-list | zenity --progress --title "How-To Geek" --auto-close
exit 0
这是脚本的细分:
- 该脚本定义了一个名为
work-list
的函数。这是您放置命令和指令以执行实际工作的地方。将每个sleep 1
命令替换为您的真实命令。 zenity
接受echo \# ...\
行并将它们显示在进度对话框窗口中。更改这些行的文本,以便它们向用户传递信息性消息。- 包含数字的
echo
行,例如echo \25\
,也被zenity
接受并设置值进度条。 - 工作列表函数被调用并通过管道传输到
zenity
。
使用此命令使脚本可执行:
chmod +x progress.sh

使用此命令运行脚本:
./progress.sh

脚本运行,并且文本消息随着脚本的每个阶段执行而改变。进度条逐步向 100% 移动。

缩放对话框窗口
比例对话框窗口让人们可以移动滑块来选择一个数值。这意味着她不能输入太高或太低的值。
我们使用的新选项是:
- –scale:告诉
zenity
我们想要使用缩放对话窗口。 - –min-value:设置比例的最小值。
- –max-value:设置比例的最大值。
- –step::设置使用箭头键时滑块移动的量。如果有人使用鼠标,这不会影响滑块移动。
- –value:设置滑块的初始值和位置。
这是我们正在使用的命令:
Response=$(zenity --scale --title "How-To Geek" --text "Select magnification." --min-value=0 --max-value=30 --step=3 --value15); echo $Response

出现滑块对话框窗口,滑块设置为 15。

用户可以移动滑块来选择新值。

当她点击“确定”时,该值将传输到变量 Response
并打印到终端窗口。

输入对话框窗口
输入对话窗口允许某人输入文本。
我们使用的新选项是:
- –entry:告诉
zenity
我们想要使用一个输入对话框窗口。 - –entry-text:如果您想在文本输入字段中键入建议的值,则可以使用它。我们正在使用“”来强制一个空字段。这不是严格要求的,但我们想记录该选项。
完整命令如下所示:
Response=$(zenity --entry --text "Enter your search term" --title "Howe-To Geek" --entry-text=""); echo $Response

出现一个简单的对话窗口,其中包含一个文本输入字段。

有人可以键入和编辑文本。

当他单击“确定”时,他键入的值将分配给变量 Response。我们使用 echo 在终端窗口中打印变量的值。

把它们放在一起
让我们将这些技术放在一起并创建一个功能脚本。该脚本将执行硬件信息扫描并在滚动文本窗口中将结果呈现给用户。她可以选择长扫描类型或短扫描类型。
对于这个脚本,我们将使用三种类型的对话框窗口,其中两种对我们来说是新的:
- 第一个是列表对话框窗口。它允许某人做出选择。
- 第二个是进度对话框窗口,让用户知道正在发生的事情,她应该等待。
- 第三个是文本信息窗口,向用户显示结果。
在编辑器中输入此文本并将其保存为“hardware-info.sh”。
#!/bin/bash
# Display hardware listing for this computer
TempFile=$(mktemp)
ListType=`zenity --width=400 --height=275 --list --radiolist \
--title 'Hardware Scan' \
--text 'Select the scan type:' \
--column 'Select' \
--column 'Scan Type' TRUE "Short" FALSE "Long"`
if [[ $? -eq 1 ]]; then
# they pressed Cancel or closed the dialog window
zenity --error --title="Scan Declined" --width=200 \
--text="Hardware scan skipped"
exit 1
elif [ $ListType == "Short" ]; then
# they selected the short radio button
Flag="--short"
else
# they selected the long radio button
Flag=""
fi
# search for hardware info with the appropriate value in $Flag
hwinfo $Flag | tee >(zenity --width=200 --height=100 \
--title="Collating Information" --progress \
--pulsate --text="Checking hardware..." \
--auto-kill --auto-close) >${TempFile}
# Display the hardware info in a scrolling window
zenity --width=800 --height=600 \
--title "Hardware Details" \
--text-info --filename="${TempFile}"
exit 0
使用此命令使其可执行:
chmod +x hardware-info.sh

此脚本创建一个临时文件,文件名保存在 TempFile 变量中:
TempFile=$(mktemp)
该脚本使用 --list
选项创建一个名为列表对话框窗口的 zenity
对话框窗口。行尾的“\”字符告诉脚本将它们视为环绕的一长行。这是过程:
- 我们指定窗口的宽度和高度。
- 列表对话框窗口支持列。
--radiolist
选项使第一列成为一列单选按钮。 - 我们为窗口设置标题和文本提示。
- 我们将第一列的标题设置为“选择”。此列的内容将是单选按钮。
- 我们将第二列的标题设置为“选择”,并提供第二列的内容。此列包含两个文本标签:“短”和“长”。 TRUE 和 FALSE 指示符表示当对话框窗口出现时默认选择“短”选项。
- 我们将此对话窗口的结果存储在名为
ListType
的变量中。
ListType=`zenity --width=400 --height=275 --list --radiolist \
--title 'Hardware Scan' \
--text 'Select the scan type:' \
--column 'Select' \
--column 'Scan Type' TRUE "Short" FALSE "Long"`
如果用户按下“取消”,我们不需要检查 ListType
中的值,我们可以简单地退出。如果他按下“OK”,我们需要找出他选择的是“Short”还是“Long”单选按钮:
- 如果用户按下“确定”,则特殊参数
$?
等于零。如果他按下“取消”或关闭窗口,它等于 1。 - 如果等于 1,脚本将显示一个错误信息对话窗口并退出。如果他按下“确定”,我们将继续测试
ListType
变量中的值。 - 如果
ListType
变量的值为“Short”,脚本会将名为Flag
的变量设置为等于“–short”。 - 如果
ListType
变量不包含值“Short”,它必须包含值“Long”。该脚本将名为Flag
的变量设置为等于“”,这是一个空字符串。 - 该脚本在下一节中使用了
Flag
变量。
if [[ $? -eq 1 ]]; then
# they pressed Cancel or closed the dialog window
zenity --error --title="Scan Declined" --width=200 \ --text="Hardware scan skipped"
exit 1
elif [ $ListType == "Short" ]; then
# they selected the short radio button
Flag="--short"
else
# they selected the long radio button
Flag=""
fi
现在脚本知道用户想要哪种类型的扫描,我们可以执行硬件信息扫描:
- 脚本调用
hwinfo
命令并将Flag
变量中的值传递给它。 - 如果
Flag
包含“–short”,hwinfo
命令执行短扫描。如果Flag
的值为“”,则不会向hwinfo
传递任何内容,并执行默认的长扫描。 - 脚本将
hwinfo
的输出通过管道传输到tee
。tee
将输出发送到zenity
和TempFile
。 - 脚本创建一个进度条对话窗口。它设置对话框窗口的宽度和高度,以及标题和提示文本。
- 脚本无法事先知道
hwinfo
命令会产生多少信息,因此它无法将进度条设置为正确前进到 100%。--pulsate
选项使进度对话框显示移动指示器。这会通知用户发生了一些事情,他应该等待。 --auto-kill
选项会在有人点击“取消”时终止脚本。--auto-close
选项会导致进度对话框在其监视的进程完成时自动关闭。
# search for hardware info with the appropriate value in $Flag
hwinfo $Flag | tee >(zenity --width=200 --height=100 \
--title="Collating Information" --progress \
--pulsate --text="Checking hardware..." \
--auto-kill --auto-close) >${TempFile}
hwinfo
扫描完成后,脚本会调用 zenity
以使用 --text-info
选项创建一个文本信息对话窗口。文本信息对话框窗口显示 TempFile
文件的内容:
- 脚本设置对话框窗口的宽度和高度以及标题文本。
--flename
选项用于读取保存在TempFIle
变量中的文件内容。
# Display the hardware info in a scrolling window
zenity --width=800 --height=600 \
--title "Hardware Details" \
--text-info --filename="${TempFile}"
当用户关闭文本信息对话框窗口时,脚本退出。
exit 0
让我们启动它看看。
./hardware-info.sh

出现列表框。默认选择“短”选项。

让我们选择“长”,然后单击“确定”。

进度窗口出现,带有滑动指示器。它会保留在屏幕上,直到硬件扫描完成。

硬件扫描完成后,会出现包含扫描详细信息的文本信息对话框窗口。

单击“确定”。
即使是顽固的命令行骑师也不得不承认几个 GUI 对话框窗口可以让一个不起眼的 Bash 脚本具有专业的感觉。