在 Bash 脚本中加入图形用户界面元素,例如窗口、滑块、单选按钮和进度条,是完全可行的。本文将向您展示如何利用 zenity
工具包,让您的 Bash 脚本焕然一新,展现现代化的风采。
Bash 脚本 是一种强大的编程语言,因其内置于 Bash shell 中而易于使用。它是一种解释型语言,这意味着您无需编译即可直接运行脚本。只需编辑脚本文件并使其可执行,即可立即执行,这使得开发和调试过程高效便捷。
尽管 Bash 脚本功能强大,但用户通常会提出两个主要抱怨。首先是执行速度。由于 Bash shell 需要解释脚本中的命令,因此其执行速度不如编译后的代码。但这就像抱怨拖拉机不如汽车快一样,它们各自适用于不同的场景。
然而,在实际应用中,您通常可以快速编写一个 Bash 脚本来完成任务,这比使用 C 语言等编译型语言 要快得多。
第二个抱怨是 Bash 脚本的用户界面,通常只是一个终端窗口。当然,在某些情况下,界面并不重要,例如,如果脚本只由作者本人使用,或者用于后台和批处理任务时,用户交互的需求较少,这时界面可能就无关紧要了。
但有时,您确实需要更直观和现代的用户界面。大多数用户都熟悉 图形用户界面。为了提供更流畅的用户体验,您需要在脚本中创建和使用 GUI 元素。
zenity 应用
zenity
允许您在 Bash 脚本中嵌入各种图形界面元素。它是一个功能强大的工具包,可以使您的脚本拥有现代且熟悉的外观。
zenity
预装在 Ubuntu、Fedora 和 Manjaro 等发行版中,它是 GNOME 的一部分。如果您使用 KDE,您可能需要考虑使用 kdialog
,尽管 zenity
也可以在任何桌面环境中使用。
本文的示例将展示如何从命令行创建不同的对话框窗口,如何在变量中捕获它们的返回值和用户选择,以及如何在脚本中使用这些对话框窗口。
我们将演示一个使用三种不同类型对话窗口的小应用。
日历对话框窗口
日历对话窗口允许用户选择日期。使用 zenity
创建一个日历窗口非常简单,只需执行以下命令:
zenity --calendar
这将弹出一个日历对话窗口,它具备标准日期选择器的所有功能。您可以切换月份和年份,然后单击某一天来选择该日期。默认情况下,窗口打开时,今天的日期会被高亮显示。
单击“确定”将关闭对话窗口并选择高亮显示的日期。双击日期也可以达到相同的效果。
如果您不想选择日期,可以单击“取消”,按下键盘上的“Esc”键,或直接关闭对话窗口。
在上例中,选择了 2019 年 8 月 19 日。如果用户单击“确定”,日历将关闭,所选日期将被打印到终端窗口。
您可以忽略这一行消息:“GTKDialog mapped without a transient parent. This is discouraged.”
GTK 代表 GIMP 工具包,它是用于开发 GNOME 界面的工具包。最初由 GNU Image Manipulation Program ( 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
创建一个问题对话窗口。
$?
是一个 特殊参数。它保存最近执行的前台管道的返回值,通常是最近关闭的进程返回的值。值为零表示“OK”,一个或多个值表示“取消”。
这是一种通用技术,适用于任何 zenity
对话窗口。通过在脚本中检查此值,您可以判断对话窗口返回的数据是否应该被处理或忽略。
我们单击“是”,因此返回代码为零,表示“OK”。
要创建警告对话框窗口,请使用以下命令:
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 "25"
)也会被zenity
接受,并用于设置进度条的值。 work-list
函数被调用,并通过管道传递给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)
脚本使用