点(.)命令
下面的例子在命令行中使用点命令,但你完全可以把它用在一个脚本程序中。
假设你有两个包含环境设置的文件,它们分别针对两个不同的开发环境。为了设置老的、经典命令的环境,你可以使用文件classic_set,它的内容如下:
#!/bin/sh
version=classic
PATH=/usr/local/old_bin/usr/bin:/bin:.
PS1="classic> "
对于新命令,使用文件latest_set:
#!/bin/sh
version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin.
PS1=" latest version> "
可以通过将这些脚本程序和点灵敏结合来设置环境,就像下面的实例那样:
$. ./classic_set
classic> echo $version
classic
classic> . /latest_set
latest version> echo $version
latest
latest version>
这个脚本程序使用点命令执行,所以每个脚本程序都是在当前shell中执行。这使得脚本程序可以改变当前shell中的环境设置,即使脚本程序执行结束后,这些改变仍然有效。
eval命令
eval命令允许你对参数进行求值。它是shell的内置命令,通常不会以单独命令的形式存在,使用X/Open规范中的一个小例子来演示它的用法:
foo=10
x=foo
y='$'$x
echo $y
输出$foo。
而
foo=10
x=foo
eval y='$'$x
echo $y
输出10,因此eval有点像一个额外的$,它给出一个变量的值的值。
eval命令十分有用,它允许代码被随时生成和运行。虽然它的确增加了脚本调试的复杂度,但它可以让你完成使用其他方法难以或者根本无法完成的事情。
exec命令
exec命令有两种不同的用法。它的典型用法是将当前shell替换为一个不同的程序。
例如:
exec wall "Hello"
脚本中这个命令会用wall命令替换当前的shelll。脚本程序中exec命令后面的代码都不会执行,因为执行这个脚本的shell已经不存在了。
exec的第二种用法是修改当前文件描述符:
exec 3< afile
这使得文件描述符3被打开以便从文件afile中读取数据。这种用法非常少见。
trap命令
此处略。
dialog工具
严格来说dialog并不是shell的一部分,但是在通常情况下,它仅仅在shell程序设计中有用。
如果你知道你的脚本程序只需要运行在linux控制台上,就可以使用dialog工具命令。
这个命令使用文本模式的图形和色彩,提供了友好的面向图形的解决方案。
一些linux发行版中默认没有dialog工具,例如ubuntu,可能必须添加公开维护的套件库来找到一个现成的版本。在其他linux发行版中,可能会找到一个已安装的替代工机具gdialog。它和dialog工具非常相似,但它依赖GNOME用户接口来显示其对话框。然而,你得到的回报是你获得了一个真正的图形化界面。一般来说,你可以将任何使用dialog工具的程序中对dialog工具的调用替换为对gdialog工具的调用,从而获得程序的一个图形化版本。
简单示例:
dialog --msgbox "Hello world" 9 18
执行后会在屏幕显示一个图形化的消息框。
下面对dialog的各种可能性进行详细的介绍:
类型 | 用于创建类型的选项 | 含义 |
---|---|---|
复选框 | –checklist | 允许用户显示一个选项列表,每个选项都可以被单独选择 |
信息框 | –infobox | 在显示消息后,对话框将立刻返回,但不清除屏幕 |
输入框 | –inputbox | 允许用户输入文本 |
菜单框 | –menu | 允许用户选择列表中的一项 |
消息框 | –msgbox | 向用户显示一条消息,同时显示一个OK按钮,用户可以通过选择按钮继续操作 |
单选框 | –radiolist | 允许用户选择列表中的一个选项 |
文本框 | –textbox | 允许用户在带有滚动条的文本框中显示一个文件的内容 |
是/否框 | –yesno | 允许用户提问,用户可以选择yes或no |
如果想获得任何类型的允许文本输入或进行选择的对话框的输出,你必须捕获标准错误流。通常是把它指向某个临时文件以便后续处理。
要想获得Yes/No对话框的输出结果,只需查看它的退出码,返回0表示成功,1表示失败。
所有的对话框类型都有各种各样的用于控制的参数(见下表),比如控制显示的对话框的大小和形状。我们首先列出每种类型所需的参数,然后在命令行上演示其中一部分参数的用法。最后,你将看到一个简单的将几种对话框结合起来的程序。
对话框类型 | 参数 |
---|---|
–checklist | text height width list-height [tag text status]… |
–infobox | text height width |
–inputbox | text height width [initial string] |
–menu | text height width menu-height [tag item] … |
–msgbox | text height width |
–radiolist | text height width list-height [tag text status]… |
–textbox | filename height width |
–yesno | text height width |
除此之外,所有的对话框类型都有几个相同的参数选项。在此只介绍两个,–title和–clear。前者用于指定对话框的标题,后者用来完成清屏操作。
示例
dialog --title "Check me" --checklist "Pick Numbers" 15 25 3 1 "one" "off" 2 "two" "on" 3 "three" "off"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5c3gkQeU-1662206492329)(vx_images/71555790826696.png)]
为了能够将这些放在一个程序中,你需要能够访问用户输入的结果。这一点很容易实现,对于文本输入,只需要重定向标准错误流或检查环境变量$?的内容,$?的值实际上就是前一个命令的退出状态。
示例2
-
首先,这个程序通过一个简单的对话框来告诉用户发生的事情。你不需要获得返回值或任何用户的输入,所以这看起来非常简单和友好。
#!/bin/bash # ASK some questions and collect the answer dialog --title "Questionnaire" --msgbox "Welcome to my simple survey" 9 18
-
然后用一个简单的yes/no对话框来询问用户是否要继续操作。我们用环境变量$?来检查用户是否选择了yes(返回码是0)。如果用户不想继续操作,就使用一个简单的信息框显示信息,信息框在退出之前不需要用户的输入。
dialog --title "Confirm" --yesno "Are you willing to take part?" 9 18 if [ $? != 0 ];then dialog --infobox "Thank you anyway" 5 20 sleep 2 dialog --clear exit 0 fi
-
我们使用一个输入框来询问用户的姓名。重定向标准错误流2到临时文件_1.txt,然后再将它放到变量Q_NAME中:
dialog --title "Questionnaire" --inputbox "Please enter your name" 9 30 2>_1.txt Q_NAME=$(cat _1.txt)
-
现在显示一个菜单,它有4个不同的选项。你再次重定向标准错误流并且将它装载到一个变量中:
dialog --menu "$Q_NAME, what music do you like best?" 15 30 4 1 "Classical" 2 "Jazz" 3 "Country" 4 "Other" 2>_1.txt Q_MUSIC=$(cat _1.txt)
-
用户选择的菜单项编号将被保存到临时文件_1.txt中,同时这个结果被放入变量Q_MUSIC中,以便你对结果进行测试。
if [ "$Q_MUSIC" = "1" ];then dialog --title "Likes Classical" --msgbox "Good choice!" 12 25 else dialog --title "Doesn't like Classical" --msgbox "Shame" 12 25 fi
-
最后,清除对话框并退出程序
dialog --clear exit 0
这里给出上面示例的完整代码,你可以选择dialog或者gdialog:
#!/bin/bash
gui=dialog
#gui=gdialog
#1.告诉用户发生的事情
$gui --title "Questionnaire" --msgbox "Welcome to my simple survey" 9 18
#2.询问是否继续操作
$gui --title "Confirm" --yesno "Are you willing to take part?" 9 18
if [ $? != 0 ];then
$gui --infobox "Thank you anyway" 5 20
sleep 2
$gui --clear
exit 0
fi
#3.询问用户姓名
$gui --title "Questionnaire" --inputbox "Please enter your name" 9 30 2>_1.txt
Q_NAME=$(cat _1.txt)
#4.让用户选择最爱的音乐
$gui --menu "$Q_NAME, what music do you like best?" 15 30 4 \
1 "Classical" \
2 "Jazz" \
3 "Country" \
4 "Other" \
2>_1.txt
Q_MUSIC=$(cat _1.txt)
#5.对选择的结果测试
if [ "$Q_MUSIC" = "1" ];then
$gui --title "Likes Classical" --msgbox "Good choice!" 12 25
else
$gui --title "Doesn't like Classical" --msgbox "Shame" 12 25
fi
$gui --clear
rm -rf _1.txt
exit 0
shell实战综合应用
需求
有许多CD唱片,你将设计和实现一个管理CD唱片的程序。
设计
由于所有需要存储的数据全部都是文本,而且假设唱片不是很多,因此没有必要使用一个复杂的数据库,使用一些简单的文本文件即可。将资料保存在文本文件中比较简单,而且如果你的需求发生了变化,操纵文本文件总是要比操纵其他类型的文件更加容易。
如果对曲目数量没有限制,就有以下3中选择。
- 只使用一个文件,用一行来保存“标题”信息,再用n行来保存该CD唱片上的曲目信息。
- 将每张CD唱片的所有信息都放置在一行上,允许该行一直延续知道没有曲目信息需要保存为止。
- 把标题信息和曲目分开,用不同的文件来分别保存它们。
只有第三种做法能够让你灵活地修改文件的格式,如果今后你想要把数据库转换为关系数据库格式的话,你就需要修改文件格式,因此选择第三种方法。
下一个决策时要在文件里放入哪些信息、
我们决定对每张CD保存以下信息:
- CD唱片里的目录编号
- 标题
- 曲目类型
- 作曲家
对曲目,只保存两条信息:
- 曲目编号
- 曲名
为了把这两个文件结合起来,必须把曲目信息和CD唱片上的其他信息关联起来。为此,你需要使用CD唱片的目录编号。因为它对每张CD唱片都是唯一的,所以它在标题文件中只出现一次,在曲目文件中对每首曲目只出现一次。
让我们来看一个示例标题文件:
目录编号 | 标题 | 曲目类型 | 作曲家 |
---|---|---|---|
CD123 | Cool sax | 爵士 | Bix |
CD214 | Classic violin | 古典 | Bach |
CD345 | Hits99 | 流行 | Various |
它对应的曲目文件:
目录编号 | 曲目编号 | 曲名 |
---|---|---|
CD123 | 1 | Some jazz |
CD123 | 2 | More jazz |
CD234 | 1 | Sonata in D minor |
CD345 | 1 | Dizzy |
这两个文件通过目录编号结合在一起。标题文件中的一个数据项一般都对应曲目文件中的多行数据。
你需要决定的最后一件事情是如何分隔数据项。在关系数据库里,长度固定的数据字段比较常见,但他并非总是最方便的。另一种常见的方法是使用逗号,这个例子就选择了这个方法(即用逗号分隔变量,或CSV文件)。
这里先列出下面“实验”部分要用到的函数:
get_return()
get_confirm()
set_menu_choice()
insert_title()
insert_track()
add_record_tracks()
add_records()
find_cd()
update_cd()
count_cds()
remove_records()
list_tracks()
-
首先要设置好脚本要使用的一些全局变量,包括标题文件、曲目文件和一个临时文件。我们还设置Ctrl+C组合键中断处理,以确保在用户在中断脚本程序时删除临时文件:
menu_choice="" current_cd="" title_file="title.cdb" tracks_file="tracks.cdb" temp_file=/tmp/cdb.$$ trap 'rm -f $temp_file' EXIT
-
现在开始定义函数。因为脚本程序是从文件的第一行开始执行,所以这样做可以确保在调用任何一个函数之前都能够找到它的定义。为了避免在几个地方反复编写同样代码,最开始的两个函数是简单的工具型函数。
get_return(){
echo -e "Press return \c"
read x
return 0
}
get_confirm(){
echo -e "Are you sure?"
while true
do
read x
case "$x" in
y | yes | Y | Yes | YES)
return 0;;
n | no | N | No | no)
echo
echo "Cancelled"
return 1;;
*) echo "Please enter yes or no";;
esac
done
}
-
接下来是主菜单函数set_menu_choice。菜单的内容是动态变化的,当用户选择了某张CD唱片后,主菜单会多出几个选项。
注意:echo -e命令可能不能移植到某些shell’中。set_menu_choice(){ clear echo "Options :-" echo echo " a) Add new CD" echo " f) Find cd" echo " c) Count the CDs and tracks in the catalog" if [ "$cdcatnum" != "" ];then echo " l) List tracks on $cdtitle" echo " r) Remove $cdtitle" echo " u) Update track information for $cdtitle" fi echo " q) Quit" echo echo -e "Please enter choice then press return \c" read menu_choice return }
-
接下来是两个很短小的函数insert_title和insert_track,它们用于向数据库文件里添加数据,虽然有的人不喜欢这种长度只有一行的函数,但它们有助于让其他函数的含义更加清晰易懂。
紧跟着是一个比较大的函数add_record_trakcs,他会用到上述两个短小的函数。这两个函数使用模式匹配来确保用户未输入逗号(因为我们把逗号用作数据字段之间的分隔符),使用算术操作在用户输入曲目时递增当前曲目的编号: -
add_records函数用于输入新CD唱片的标题信息:
add_records(){
#Prompt for the initial information
echo -e "Enter catalog name \c"
read tmp
cdcatnum=${tmp%%,*}
echo -e "Enter title \c"
read tmp
cdtitle=${tmp%%,*}
echo -e "Enter type \c"
read tmp
cdtype=${tmp%%,*}
echo -e "Enter artist/composer\c"
read tmp
cdac=${tmp%%,*}
#Check that they want to enter the information
echo "About to add new entry"
echo "$cdcatnum then append it to the title file"
#if confirmed then append it to the titles file
if get_confirm; then
insert_title $cdcatnum, $cdtitle, $cdtype, $cdac
add_record_tracks
else
remove_records
fi
}
- find_cd函数的作用是使用grep命令在唱片标题文件中查找CD唱片的有关资料。你需要知道查询字符串在标题文件里出现的次数,但grep命令的返回值只会告诉我们匹配了0次或者多次。我们将grep命令的输出保存到一个临时文件中,文件中的每行对应一次匹配,然后再统计该文件的行数。
- 待办