第 18 章 图形化桌面环境中的脚本编程

本章内容

  • 创建文本菜单
  • 创建文本窗口部件
  • 添加X Window图形

多年来,shell脚本一直都被认为是枯燥乏味的。但如果你准备在图形化环境中运行脚 本时,就未必如此了。有很多与脚本用户交互的方式并不依赖read和echo语句。
本章将会深入介绍一些可以让交互式脚本更友好的方法,这样它们看起来就不那么古板了。

18.1 创建文本菜单

创建交互式shell脚本最常用的方法是使用菜单。提供各种选项可以帮助脚本用户了解脚本能做什么和不能做什么。
通常菜单脚本会清空显示区域,然后显示可用的选项列表。用户可以按下与每个选项关联的字母或数字来选择选项。图18-1显示了一个示例菜单的布局。
shell脚本菜单的核心是case命令(参见第12章)。case命令会根据用户在菜单上的选择来执行特定命令。
后面几节将会带你逐步了解创建基于菜单的shell脚本的步骤。

第 18 章 图形化桌面环境中的脚本编程 - 图1
图18-1 在shell脚本中显示菜单
18

18.1.1 创建菜单布局

创建菜单的第一步显然是决定在菜单上显示哪些元素以及想要显示的布局方式。
在创建菜单前,通常要先清空显示器上已有的内容。这样就能在干净的、没有干扰的环境中显示菜单了。
clear命令用当前终端会话的terminfo数据(参见第2章)来清理出现在屏幕上的文本。运行clear命令之后,可以用echo命令来显示菜单元素。
默认情况下,echo命令只显示可打印文本字符。在创建菜单项时,非可打印字符通常也很 有用,比如制表符和换行符。要在echo命令中包含这些字符,必须用-e选项。因此,命令如下:

  1. echo -e "1.\tDisplay disk space"

会生成如下输出行:

1. Display disk space

这极大地方便了菜单项布局的格式化。只需要几个echo命令,就能创建一个看上去还行的菜单。

clear
echo
echo -e "\t\t\tSys Admin Menu\n"
echo -e "\t1. Display disk space"
echo -e "\t2. Display logged on users"
echo -e "\t3. Display memory usage"
echo -e "\t0. Exit menu\n\n"
echo -en "\t\tEnter option: "

最后一行的-en选项会去掉末尾的换行符。这让菜单看上去更专业一些,光标会一直在行尾等待用户的输入。

创建菜单的最后一步是获取用户输入。这步用read命令(参见第14章)。因为我们期望只有单字符输入,所以在read命令中用了-n选项来限制只读取一个字符。这样用户只需要输入一个数字,也不用按回车键:

read -n 1 option

接下来,你需要创建自己的菜单函数。

18.1.2 创建菜单函数

shell脚本菜单选项作为一组独立的函数实现起来更为容易。这样你就能创建出简洁、准确、容易理解的case命令。
要做到这一点,你要为每个菜单选项创建独立的shell函数。创建shell菜单脚本的第一步是决定你希望脚本执行哪些功能,然后将这些功能以函数的形式放在代码中。
通常我们会为还没有实现的函数先创建一个桩函数(stub function)。桩函数是一个空函数,或者只有一个echo语句,说明最终这里里需要什么内容。

function diskspace {
  clear
  echo "This is where the diskspace commands will go"
}

这允许你的菜单在你实现某个函数时仍然能正常操作。你不需要写出所有函数之后才能让菜单投入使用。函数从clear命令开始。这样你就能在一个干净的屏幕上执行该函数,不会受到原先菜单的干扰。
还有一点有助于制作shell脚本菜单,那就是将菜单布局本身作为一个函数来创建。

function menu {
  clear
  echo
  echo -e "\t\t\tSys Admin Menu\n"
  echo -e "\t1. Display disk space"
  echo -e "\t2. Display logged on users"
  echo -e "\t3. Display memory usage"
  echo -e "\t0. Exit program\n\n"
  echo -en "\t\tEnter option: "
  read -n 1 option
}

这样一来,任何时候你都能调用menu函数来重现菜单。

18.1.3 添加菜单逻辑

现在你已经建好了菜单布局和函数,只需要创建程序逻辑将二者结合起来就行了。前面提到过,这需要用到case命令。
case命令应该根据菜单中输入的字符来调用相应的函数。用默认的case命令字符(星号)来处理所有不正确的菜单项是种不错的做法。
下面的代码展示了典型菜单中case命令的用法。

menu
case $option in
0)
  break ;;
1)
  diskspace ;;
2)
  whoseon ;;
3)
  memusage ;;
*)
  clear
  echo "Sorry, wrong selection";;
esac

这段代码首先用menu函数清空屏幕并显示菜单。menu函数中的read命令会一直等待,直到用户在键盘上键入了字符。然后,case命令就会接管余下的处理过程。case命令会基于返回的字符调用相应的函数。在函数运行结束后,case命令退出。

18.1.4 整合 shell 脚本菜单

现在你已经看到了构成shell脚本菜单的各个部分,让我们将它们组合在一起,看看彼此之间是如何协作的。这里是一个完整的菜单脚本的例子。

$ cat menu1
#!/bin/bash
# simple script menu
function diskspace {
  clear
  df -k
}
function whoseon {
  clear
  who
}
function memusage {
  clear
  cat /proc/meminfo
}
function menu {
  clear
  echo
  echo -e "\t\t\tSys Admin Menu\n"
  echo -e "\t1. Display disk space"
  echo -e "\t2. Display logged on users"
  echo -e "\t3. Display memory usage"
  echo -e "\t0. Exit program\n\n"
  echo -en "\t\tEnter option: "
  read -n 1 option
}
while [ 1 ]
  do
  menu
  case $option in
  0)
    break ;;
  1)
    diskspace ;;
  2)
    whoseon ;;
  3)
    memusage ;;
  *)
    clear
    echo "Sorry, wrong selection";;
  esac
  echo -en "\n\n\t\t\tHit any key to continue"
  read -n 1 line
done
clear
$

这个菜单创建了三个函数,利用常见的命令提取Linux系统的管理信息。它使用while循环来一直菜单,除非用户选择了选项0,这时,它会用break命令来跳出while循环。
可以用这个模板创建任何shell脚本菜单界面。它提供了一种跟用户交互的简单途径。

18.1.5 使用 select 命令

你可能已经注意到,创建文本菜单的一半工夫都花在了建立菜单布局和获取用户输入。bash shell提供了一个很容易上手的小工具,帮助我们自动完成这些工作。
select命令只需要一条命令就可以创建出菜单,然后获取输入的答案并自动处理。select命令的格式如下。

select variable in list
do
  commands
done

list参数是由空格分隔的文本选项列表,这些列表构成了整个菜单。select命令会将每个列表项显示成一个带编号的选项,然后为选项显示一个由PS3环境变量定义的特殊提示符。
这里有一个select命令的简单示例。

$ cat smenu1
#!/bin/bash
# using select in the menu
function diskspace {
  clear
  df -k
}
function whoseon {
  clear
  who
}
function memusage {
  clear
  cat /proc/meminfo
}
PS3="Enter option: "
select option in "Display disk space" "Display logged on users"
"Display memory usage" "Exit program"
do
  case $option in
  "Exit program")
    break ;;
  "Display disk space")
    diskspace ;;
  "Display logged on users")
    whoseon ;;
  "Display memory usage")
    memusage ;;
  *)
    clear
    echo "Sorry, wrong selection";;
  esac
done
clear
$

select语句中的所有内容必须作为一行出现。这可以从行接续字符中看出。运行这个程序时,它会自动生成如下菜单。

$ ./smenu1
1) Display disk space 3) Display memory usage
2) Display logged on users 4) Exit program
Enter option:

在使用select命令时,记住,存储在变量中的结果值是整个文本字符串而不是跟菜单选项相关联的数字。文本字符串值才是你要在case语句中进行比较的内容。

18.2 制作窗口

使用文本菜单没错,但在我们的交互脚本中仍然欠缺很多东西,尤其是相比图形化窗口而言。幸运的是,开源界有些足智多谋的人已经帮我们做好了。
dialog包最早是由Savio Lam创建的一个小巧的工具,现在由Thomas E. Dickey维护。该包能够用ANSI转义控制字符在文本环境中创建标准的窗口对话框。你可以轻而易举地将这些对话框融入自己的shell脚本中,借此与用户进行交互。本节将会介绍dialog包并演示如何在shell脚本中使用它。

说明 并非在所有的Linux发行版中都会默认安装dialog包。即使未安装,鉴于它的流行程度,你也几乎总能在软件库中找到它。参考你的Linux发行版的文档来了解如何下载dialog包。在Ubuntu Linux发行版中,下面的命令行命令用来安装它:

sudo apt-get install dialog
这条命令将会为你的系统安装dialog包以及需要的库。

18.2.1 dialog 包

dialog命令使用命令行参数来决定生成哪种窗口部件(widget)。部件是dialog包中窗口元素类型的术语。dialog包现在支持表18-1中的部件类型。
表18-1 dialog部件

部 件 描 述
calendar 提供选择日期的日历
checklist 显示多个选项(其中每个选项都能打开或关闭)
form 构建一个带有标签以及文本字段(可以填写内容)的表单
fselect 提供一个文件选择窗口来浏览选择文件
gauge 显示完成的百分比进度条
infobox 显示一条消息,但不用等待回应
inputbox 提供一个输入文本用的文本表单
inputmenu 提供一个可编辑的菜单
menu 显示可选择的一系列选项
msgbox 显示一条消息,并要求用户选择OK按钮
pause 显示一个进度条来显示暂定期间的状态
passwordbox 显示一个文本框,但会隐藏输入的文本
passwordform 显示一个带标签和隐藏文本字段的表单
radiolist 提供一组菜单选项,但只能选择其中一个
tailbox 用tail命令在滚动窗口中显示文件的内容
tailboxbg 跟tailbox一样,但是在后台模式中运行
textbox 在滚动窗口中显示文件的内容
timebox 提供一个选择小时、分钟和秒数的窗口
yesno 提供一条带有Yes和No按钮的简单消息

正如在表18-1中看到的,我们可以选择很多不同的部件。只用多花一点工夫,就可以让脚本看起来更专业。
18
要在命令行上指定某个特定的部件,需使用双破折线格式。

dialog --widget parameters

其中widget是表18-1中的部件名,parameters定义了部件窗口的大小以及部件需要的文本。 每个dialog部件都提供了两种形式的输出:

  • 使用STDERR
  • 使用退出状态码

可以通过dialog命令的退出状态码来确定用户选择的按钮。如果选择了Yes或OK按钮, dialog命令会返回退出状态码0。如果选择了Cancel或No按钮,dialog命令会返回退出状态码1。可以用标准的$?变量来确定dialog部件中具体选择了哪个按钮。
如果部件返回了数据,比如菜单选择,那么dialog命令会将数据发送到STDERR。可以用标准的bash shell方法来将STDERR输出重定向到另一个文件或文件描述符中。

dialog --inputbox "Enter your age:" 10 20 2>age.txt

这个命令会将文本框中输入的文本重定向到age.txt文件中。后面几节将会看到一些shell脚本中频繁用到的dialog部件。

  1. msgbox部件

msgbox部件是对话框中最常见的类型。它会在窗口中显示一条简单的消息,直到用户单击OK按钮后才消失。使用msgbox部件时要用下面的格式。

dialog --msgbox text height width

text参数是你想在窗口中显示的字符串。dialog命令会根据由heightwidth参数创建的窗口的大小来自动换行。如果想在窗口顶部放一个标题,也可以用—title参数,后接作为标题的文本。这里有个使用msgbox部件的例子。

$ dialog --title Testing --msgbox "This is a test" 10 20

在输入这条命令后,消息框会显示在你所用的终端仿真器会话的屏幕上,如图18-2所示。

第 18 章 图形化桌面环境中的脚本编程 - 图2

图18-2 在dialog命令中使用msgbox
如果你的终端仿真器支持鼠标,可以单击OK按钮来关闭对话框。也可以用键盘命令来模拟单击动作——按下回车键。

  1. yesno部件

yesno部件进一步扩展了msgbox部件的功能,允许用户对窗口中显示的问题选择yes或no。 它会在窗口底部生成两个按钮:一个是Yes,一个是No。用户可以用鼠标、制表符键或者键盘方向键来切换按钮。要选择按钮的话,用户可以按下空格键或者回车键。
这里有个使用yesno部件的例子。

$ dialog --title "Please answer" --yesno "Is this thing on?" 10 20
$ echo $?
1
$

这会产生如图18-3所示的部件。
第 18 章 图形化桌面环境中的脚本编程 - 图3
图18-3 在dialog命令中使用yesno部件

dialog命令的退出状态码会根据用户选择的按钮来设置。如果用户选择了No按钮,退出状态码是1;如果选择了Yes按钮,退出状态码就是0。

  1. inputbox部件

inputbox部件为用户提供了一个简单的文本框区域来输入文本字符串。dialog命令会将文本字符串的值发给STDERR。你必须重定向STDERR来获取用户输入。图18-4显示了inputbox部件的外形。
18
第 18 章 图形化桌面环境中的脚本编程 - 图4
图18-4 inputbux部件
如图18-4所示,inputbox提供了两个按钮:OK和Cancel。如果选择了OK按钮,命令的退出状态码就是0;反之,退出状态码就会是1。

$ dialog --inputbox "Enter your age:" 10 20 2>age.txt
$ echo $?
0
$ cat age.txt
12$

你会注意到,在使用cat命令显示文本文件的内容时,该值后面并没有换行符。这让你能够轻松地将文件内容重定向到shell脚本中的变量里,以提取用户输入的字符串。

  1. textbox部件

textbox部件是在窗口中显示大量信息的极佳办法。它会生成一个滚动窗口来显示由参数所指定的文件中的文本。

$ dialog --textbox /etc/passwd 15 45

/etc/passwd文件的内容会显示在可滚动的文本窗口中,如图18-5所示。

第 18 章 图形化桌面环境中的脚本编程 - 图5

图18-5 textbox部件
可以用方向键来左右或上下滚动显示文件的内容。窗口底部的行会显示当前查看的文本处于文件中的哪个位置(百分比)。文本框只包含一个用来选择退出部件的Exit按钮。

  1. menu部件

menu部件允许你来创建我们之前所制作的文本菜单的窗口版本。只要为每个选项提供一个选择标号和文本就行了。

$ dialog --menu "Sys Admin Menu" 20 30 10 1 "Display disk space"
2 "Display users" 3 "Display memory usage" 4 "Exit" 2> test.txt

第一个参数定义了菜单的标题,之后的两个参数定义了菜单窗口的高和宽,而第四个参数则定义了在窗口中一次显示的菜单项总数。如果有更多的选项,可以用方向键来滚动显示它们。
在这些参数后面,你必须添加菜单项对。第一个元素是用来选择菜单项的标号。每个标号对每个菜单项都应该是唯一的,可以通过在键盘上按下对应的键来选择。第二个元素是菜单中使用的文本。图18-6展示了由示例命令生成的菜单。
第 18 章 图形化桌面环境中的脚本编程 - 图6
图18-6 带有菜单项的menu部件

如果用户通过按下标号对应的键选择了某个菜单项,该菜单项会高亮显示但不会被选定。直到用户用鼠标或回车键选择了OK按钮时,选项才会最终选定。dialog命令会将选定的菜单项文本发送到STDERR。可以根据需要重定向STDERR。

  1. fselect部件

dialog命令提供了几个非常炫的内置部件。fselect部件在处理文件名时非常方便。不 用强制用户键入文件名,你就可以用fselect部件来浏览文件的位置并选择文件,如图18-7 所示。
18
第 18 章 图形化桌面环境中的脚本编程 - 图7

图18-7 fselect部件
fselect部件的格式如下。

$ dialog --title "Select a file" --fselect $HOME/ 10 50 2>file.txt

fselect选项后的第一个参数是窗口中使用的起始目录位置。fselect部件窗口由左侧的目录列表、右侧的文件列表(显示了选定目录下的所有文件)和含有当前选定的文件或目录的简单文本框组成。可以手动在文本框键入文件名,或者用目录和文件列表来选定(使用空格键选择文件,将其加入文本框中)。

18.2.2 dialog 选项

除了标准部件,还可以在dialog命令中定制很多不同的选项。你已经看过了—title选项的用法。它允许你设置出现在窗口顶部的部件标题。
另外还有许多其他的选项可以让你全面定制窗口外观和操作。表18-2显示了dialog命令中可用的选项。

表18-2 dialog命令选项

选 项 描 述
—add-widget 继续下个对话框,直到按下Esc或Cancel按钮
—aspect ratio 指定窗口宽度和高度的宽高比
—backtitle title 指定显示在屏幕顶部背景上的标题
—begin x y 指定窗口左上角的起始位置
—cancel-label label 指定Cancel按钮的替代标签
—clear 用默认的对话背景色来清空屏幕内容
—colors 在对话文本中嵌入ANSI色彩编码
—cr-wrap 在对话文本中允许使用换行符并强制换行
—create-rc file 将示例配置文件的内容复制到指定的file文件中①
—defaultno 将yes/no对话框的默认答案设为No
—default-item string 设定复选列表、表单或菜单对话中的默认项
—exit-label label 指定Exit按钮的替代标签
—extra-button 在OK按钮和Cancel按钮之间显示一个额外按钮
—extra-label label 指定额外按钮的替代标签
—help 显示dialog命令的帮助信息
—help-button 在OK按钮和Cancel按钮后显示一个Help按钮
—help-label label 指定Help按钮的替代标签
—help-status 当选定Help按钮后,在帮助信息后写入多选列表、单选列表或表单信息
—ignore 忽略dialog不能识别的选项
—input-fd fd 指定STDIN之外的另一个文件描述符
—insecure 在password部件中键入内容时显示星号
—item-help 为多选列表、单选列表或菜单中的每个标号在屏幕的底部添加一个帮助栏
—keep-window 不要清除屏幕上显示过的部件
—max-input size 指定输入的最大字符串长度。默认为2048
—nocancel 隐藏Cancel按钮
—no-collapse 不要将对话文本中的制表符转换成空格
—no-kill 将tailboxbg对话放到后台,并禁止该进程的SIGHUP信号
—no-label label 为No按钮指定替代标签
—no-shadow 不要显示对话窗口的阴影效果
—ok-label label 指定OK按钮的替代标签
—output-fd fd 指定除STDERR之外的另一个输出文件描述符
—print-maxsize 将对话窗口的最大尺寸打印到输出中
—print-size 将每个对话窗口的大小打印到输出中
—print-version 将dialog的版本号打印到输出中
—separate-output 一次一行地输出checklist部件的结果,不使用引号
—separator string 指定用于分隔部件输出的字符串
—separate-widget string 指定用于分隔部件输出的字符串
—shadow 在每个窗口的右下角绘制阴影
—single-quoted 需要时对多选列表的输出采用单引号
—sleep sec 在处理完对话窗口之后延迟指定的秒数
—stderr 将输出发送到STDERR(默认行为)
—stdout 将输出发送到STDOUT
—tab-correct 将制表符转换成空格
—tab-len n 指定一个制表符占用的空格数(默认为8)
—timeout sec 指定无用户输入时,sec秒后退出并返回错误代码
—title title 指定对话窗口的标题
—trim 从对话文本中删除前导空格和换行符
—visit-items 修改对话窗口中制表符的停留位置,使其包括选项列表
—yes-label label 为Yes按钮指定替代标签

—————————— ① dialog命令支持运行时配置。该命令会根据配置文件模板创建一份配置文件。dialog启动时会先去检查是否设置了DIALOGRC环境变量,该变量会保存配置文件名信息。如果未设置该变量或未找到该文件,它会将 $HOME/.dialogrc作为配置文件。如果这个文件还不存在的话,就尝试查找编译时指定的GLOBALRC文件,也就是/etc/dialogrc。如果这个文件也不存在的话,就用编译时的默认值。

—backtitle选项是为脚本中的菜单创建公共标题的简便办法。如果你为每个对话窗口都指定了该选项,那么它在你的应用中就会保持一致,这样会让脚本看起来更专业。
由表18-2可知,可以重写对话窗口中的任意按钮标签。该特性允许你创建任何需要的窗口。

18.2.3 在脚本中使用 dialog 命令

在脚本中使用dialog命令不过就是动动手的事。你必须记住两件事:

  • 如果有Cancel或No按钮,检查dialog命令的退出状态码;
  • 重定向STDERR来获得输出值。

如果遵循了这两个规则,立刻就能够拥有具备专业范儿的交互式脚本。这里有一个例子,它使用dialog部件来生成我们之前所创建的系统管理菜单。

$ cat menu3
#!/bin/bash
# using dialog to create a menu
temp=$(mktemp -t test.XXXXXX)
temp2=$(mktemp -t test2.XXXXXX)
function diskspace {
    df -k >$temp
    dialog --textbox $temp 20 60
}
function whoseon {
    who >$temp
    dialog --textbox $temp 20 50
}
function memusage {
    cat /proc/meminfo >$temp
    dialog --textbox $temp 20 50
}
while [ 1 ]; do
    dialog --menu "Sys Admin Menu" 20 30 10 1 "Display disk space" 2
    "Display users" 3 "Display memory usage" 0 "Exit" 2>$temp2
    if [ $? -eq 1 ]; then
        break
    fi
    selection=$(cat $temp2)
    case $selection in
    1)
        diskspace
        ;;
    2)
        whoseon
        ;;
    3)
        memusage
        ;;
    0)
        break
        ;;
    *)
        dialog --msgbox "Sorry, invalid selection" 10 30
        ;;
    esac
done
rm -f $temp 2>/dev/null
rm -f $temp2 2>/dev/null
$

这段脚本用while循环和一个真值常量创建了个无限循环来显示菜单对话。这意味着,执行完每个函数之后,脚本都会返回继续显示菜单。
由于menu对话包含了一个Cancel按钮,脚本会检查dialog命令的退出状态码,以防用户按下Cancel按钮退出。因为它是在while循环中,所以退出该菜单就跟用break命令跳出while循环一样简单。
脚本用mktemp命令创建两个临时文件来保存dialog命令的数据。第一个临时文件$temp用

来保存df和meminfo命令的输出,这样就能在textbox对话中显示它们了(如图18-8所示)。第二个临时文件$temp2用来保存在主菜单对话中选定的值。
18
第 18 章 图形化桌面环境中的脚本编程 - 图8
图18-8 用textbox对话选项显示的meminfo命令输出
现在,这看起来像是可以给别人展示的真正的应用程序了。

18.3 使用图形

如果想给交互脚本加入更多的图形元素,你可以再进一步。KDE和GNOME桌面环境(参见第1章)都扩展了dialog命令的思路,包含了可以在各自环境下生成X Window图形化部件的命令。
本节将描述kdialog和zenity包,它们各自为KDE和GNOME桌面提供了图形化窗口部件。

18.3.1 KDE 环境

KDE图形化环境默认包含kdialog包。kdialog包使用kdialog命令在KDE桌面上生成类似于dialog式部件的标准窗口。生成的窗口能跟其他KDE应用窗口很好地融合,不会造成不协调的感觉。这样你就可以直接在shell脚本中创建能够和Windows相媲美的用户界面了。

说明 你的Linux发行版使用KDE桌面并不代表它就默认安装了kdialog包。你可能需要从发行版的软件仓库中手动安装。

  1. kdialog部件

就像dialog命令,kdialog命令使用命令行选项来指定具体使用哪种类型的窗口部件。下面是kdialog命令的格式。

kdialog display-options window-options arguments

window-options选项允许指定使用哪种类型的窗口部件。可用的选项如表18-3所示。
表18-3 kdialog窗口选项

选 项 描 述
—checklist title [tag item status] 带有状态的多选列表菜单,可以表明选项是否被选定
—error text 错误消息框
—inputbox text [init] 输入文本框。可以用init值来指定默认值
—menu title [tag item] 带有标题的菜单选择框,以及用tag标识的选项列表
—msgbox text 显示指定文本的简单消息框
—password text 隐藏用户输入的密码输入文本框
—radiolist title [tag item status] 带有状态的单选列表菜单,可以表明选项是否被选定
—separate-output 为多选列表和单选列表菜单返回按行分开的选项
—sorry text “对不起”消息框
—textbox file [width] [height] 显示file的内容的文本框,可以指定width和height
—title title 为对话窗口的TitleBar区域指定一个标题
—warningyesno text 带有Yes和No按钮的警告消息框
—warningcontinuecancel text 带有Continue和Cancel按钮的警告消息框
—warningyesnocancel text 带有Yes、No和Cancel按钮的警告消息框
—yesno text 带有Yes和No按钮的提问框
—yesnocancel text 带有Yes、No和Cancel按钮的提问框

表18-3中列出了所有的标准窗口对话框类型。但在使用kdialog窗口部件时,它看起来更像是KDE桌面上的一个独立窗口,而不是在终端仿真器会话中的。
checklist和radiolist部件允许你在列表中定义单独的选项以及它们默认是否选定。

$kdialog --checklist "Items I need" 1 "Toothbrush" on 2 "Toothpaste"
off 3 "Hair brush" on 4 "Deodorant" off 5 "Slippers" off

最终的多选列表窗口如图18-9所示。
第 18 章 图形化桌面环境中的脚本编程 - 图9
图18-9 kdialog多选列表对话窗口

指定为on的选项会在多选列表中高亮显示。要选择或取消选择多选列表中的某个选项,只要单击它就行了。如果选择了OK按钮,kdialog就会将标号值发到STDOUT上。

"1" "3"
$

当按下回车键时,kdialog窗口就和选定选项一起出现了。当单击OK或Cancel按钮时, kdialog命令会将每个标号作为一个字符串值返回到STDOUT(这些就是你在输出中看到的”1” 和”3”)。脚本必须能解析结果值并将它们和原始值匹配起来。

  1. 使用kdialog

18
可以在shell脚本中使用kdialog窗口部件,方法类似于dialog部件。最大的不同是kdialog 窗口部件用STDOUT来输出值,而不是STDERR。
下面这个脚本将之前创建的系统管理菜单转换成KDE应用。

$ cat menu4
#!/bin/bash
# using kdialog to create a menu
temp=$(mktemp -t temp.XXXXXX)
temp2=$(mktemp -t temp2.XXXXXX)
function diskspace {
    df -k >$temp
    kdialog --textbox $temp 1000 10
}
function whoseon {
    who >$temp
    kdialog --textbox $temp 500 10
}
function memusage {
    cat /proc/meminfo >$temp
    kdialog --textbox $temp 300 500
}
while [ 1 ]; do
    kdialog --menu "Sys Admin Menu" "1" "Display diskspace" "2" "Display
users" "3" "Display memory usage" "0" "Exit" >$temp2
    if [ $? -eq 1 ]; then
        break
    fi
    selection=$(cat $temp2)
    case $selection in
    1)
        diskspace
        ;;
    2)
        whoseon
        ;;
    3)
        memusage
        ;;
    0)
        break
        ;;
    *)
        kdialog --msgbox "Sorry, invalid selection"
        ;;
    esac
done
$

使用kdialog命令和dialog命令在脚本中并无太大区别。生成的主菜单如图18-10所示。
第 18 章 图形化桌面环境中的脚本编程 - 图10
图18-10 采用kdialog的系统管理菜单脚本
这个简单shell脚本看起来挺像真正的KDE应用!你的交互式脚本已经没有什么操作局限了。

18.3.2 GNOME 环境

GNOME图形化环境支持两种流行的可生成标准窗口的包:

  • gdialog
  • zenity

到目前为止,zenity是大多数GNOME桌面Linux发行版上最常见的包(在Ubuntu和Fedora上默认安装)。本节将会介绍zenity的功能并演示如何在脚本中使用它。

  1. zenity部件

如你所期望的,zenity允许用命令行选项创建不同的窗口部件。表18-4列出了zenity能够生成的不同部件。
表18-4 zenity窗口部件

选 项 描 述
—calendar 显示一整月日历
—entry 显示文本输入对话窗口
—error 显示错误消息对话窗口
—file-selection 显示完整的路径名和文件名对话窗口
—info 显示信息对话窗口
—list 显示多选列表或单选列表对话窗口
—notification 显示通知图标
—progress 显示进度条对话窗口
—question 显示yes/no对话窗口
—scale 显示可调整大小的窗口
—text-info 显示含有文本的文本框
—warning 显示警告对话窗口

zenity命令行程序与kdialog和dialog程序的工作方式有些不同。许多部件类型都用另外的命令行选项定义,而不是作为某个选项的参数。
zenity命令能够提供一些非常酷的高级对话窗口。calendar选项会产生一个整月的日历, 如图18-11所示。

第 18 章 图形化桌面环境中的脚本编程 - 图11

图18-11 zenity日历对话窗口
当在日历中选择了日期时,zenity命令会将值返回到STDOUT中,就和kdialog一样。

$  zenity    --calendar 12/25/2011
$

zenity中另一个很酷的窗口是文件选择选项,如图18-12所示。

第 18 章 图形化桌面环境中的脚本编程 - 图12
图18-12 zenity文件选择对话窗口
可以用对话窗口来浏览系统上任意一个目录位置(只要你有查看该目录的权限),并选择文件。当你选定文件时,zenity命令会返回完整的文件路径名。

$  zenity  --file-selection
/home/ubuntu/menu5
$

有了这种可以任意发挥的工具,创建shell脚本就没什么限制了。

  1. 在脚本中使用zenity

如你所期望的,zenity在shell脚本中表现良好。但是,zenity没有沿袭dialog和kdialog中所采用的选项惯例,因此,将已有的交互式脚本迁移到zenity上要花点工夫。
在将系统管理菜单从kdialog迁移到zenity的过程中,需要对部件定义做大量的工作。

$cat menu5
#!/bin/bash
# using zenity to create a menu
temp=$(mktemp -t temp.XXXXXX)
temp2=$(mktemp -t temp2.XXXXXX)
function diskspace {
    df -k > $temp
    zenity --text-info --title "Disk space" --filename=$temp
    --width 750 --height 10
}
function whoseon {
    who > $temp
    zenity --text-info --title "Logged in users" --filename=$temp
    --width 500 --height 10
}
function memusage {
    cat /proc/meminfo > $temp
    zenity --text-info --title "Memory usage" --filename=$temp
    --width 300 --height 500
}
while [ 1 ]; do
    zenity --list --radiolist --title "Sys Admin Menu" --column "Select"
    --column "Menu Item" FALSE "Display diskspace" FALSE "Display users"
    FALSE "Display memory usage" FALSE "Exit" > $temp2
    if [ $? -eq 1 ]; then
        break
    fi
    selection=$(cat $temp2)
    case $selection in
        "Display disk space")
            diskspace
            ;;
        "Display users")
            whoseon
            ;;
        "Display memory usage")
            memusage
            ;;
        Exit)
            break
            ;;
        *)
            zenity --info "Sorry, invalid selection"
            ;;
    esac
done
$

由于zenity并不支持菜单对话窗口,我们改用单选列表窗口来作为主菜单,如图18-13所示。该单选列表用了两列,每列都有一个标题:第一列包含用于选择的单选按钮,第二列是选
项文本。单选列表也不用选项里的标号。当选定一个选项时,该选项的所有文本都会返回到STDOUT。这会让case命令的内容丰富一些。必须在case中使用选项的全文本。如果文本中有 任何空格,你需要给文本加上引号。
使用zenity包,你可以给GNOME桌面上的交互式shell脚本带来一种Windows式的体验。

第 18 章 图形化桌面环境中的脚本编程 - 图13
图18-13 采用zenity的系统管理菜单

18.4 小结

交互式shell脚本因枯燥乏味而声名狼藉。在多数Linux系统中,可以通过一些技术手段和工具改变这种状况。首先,可以用case命令和shell脚本函数为你的交互式脚本创建菜单系统。
case命令允许你用标准的echo命令来绘制菜单,然后用read命令来读取用户输入。之后case命令会选择根据输入值来选择对应的shell脚本函数。
dialog程序提供了一些预建的文本部件,可以在基于文本的终端仿真器上生成类窗口对象。你可以用dialog程序创建对话框来显示文本、输入文本以及选择文件和日期。这会让你的脚本生动许多。
如果是在图形化X Window环境中运行shell脚本,你可以在交互脚本中采用更多的工具。对KDE桌面来说,有kdialog程序。该程序提供了简单命令来为所有基本窗口功能创建窗口部件。对GNOME桌面来说,有gdialog和zenity程序。每个程序都提供了能像真正的窗口应用一样融入GNOME桌面的窗口部件。
下一章将深入讲解文本数据文件的编辑和处理。通常shell脚本最大的用途就在于解析和显示文本文件中的数据,比如日志文件和错误文件。Linux环境包含了两个非常有用的工具:sed和gawk,两者都能够在shell脚本中处理文本数据。下一章将介绍这些工具并演示它们的基本用法。