高级Bash脚本编程指南中文版(原著Revision10) Advanced Bash Scripting Guide In Chinese

Advanced-Bash-Scripting-Guide-in-Chinese

Advanced-Bash-Scripting-Guide-in-Chinese

Advanced-Bash-Scripting-Guide-in-Chinese

User Manual: Pdf

Open the PDF directly: View PDF PDF.
Page Count: 325 [warning: Documents this large are best viewed by clicking the View PDF Link!]

1.1
1.2
1.2.1
1.2.2
1.2.2.1
1.2.2.2
1.3
1.3.1
1.3.2
1.3.2.1
1.3.2.2
1.3.2.3
1.3.2.4
1.3.3
1.3.3.1
1.3.3.2
1.3.4
1.3.5
1.3.5.1
1.3.5.2
1.3.5.3
1.3.5.4
1.3.5.5
1.3.6
1.3.6.1
1.3.6.2
1.3.6.3
目錄
Introduction
第一部分初见shell
1.为什么使用shell编程
2.Sha-Bang(#!)一起出发
2.1调用一个脚本
2.2牛刀小试
第二部分shell基础
3.特殊字符
4.变量与参数
4.1变量替换
4.2变量赋值
4.3Bash弱类型变量
4.4特殊变量类型
5.引用
5.1引用变量
5.2转义
6.退出与退出状态
7.测试
7.1测试结构
7.2文件测试操作
7.3其他比较操作
7.4嵌套if/then条件测试
7.5牛刀小试
8.运算符相关话题
8.1运算符
8.2数字常量
8.3双圆括号结构
1
1.3.6.4
1.4
1.4.1
1.4.1.1
1.4.1.2
1.4.1.3
1.4.2
1.4.2.1
1.4.2.1.1
1.4.2.1.2
1.4.2.2
1.4.3
1.4.3.1
1.4.3.2
1.4.3.3
1.4.3.4
1.4.4
1.4.5
1.4.6
1.5
1.5.1
1.5.2
1.5.2.1
1.5.2.2
1.5.2.3
1.5.2.4
1.5.2.5
1.5.2.6
1.5.3
8.4运算符优先级
第三部分shell进阶
9.换个角度看变量
9.1内部变量
9.2指定变量属性:
9.3:随机产生整数
10.变量处理
10.1字符串处理
10.1.1使用awk处理字符串
10.1.2参考资料
10.2参数替换
11.循环与分支
11.1循环
11.2嵌套循环
11.3循环控制
11.4测试与分支
12.命令替换
13.算术扩展
14.休息时间
第五部分进阶话题
19.嵌入文档
20.I/O重定向
20.1使用exec
20.2重定向代码块
20.3应用程序
22.限制模式的Shell
23.进程替换
26.列表结构
25.别名
2
3
AdvancedBash-ScriptingGuidein
Chinese
《高级Bash脚本编程指南》Revision10中文版
联系/加入我们
邮箱:absguide#linuxstory.org(#替换为@)
QQ群:535442421
原著及早期翻译作品
原著
原著链接:http://tldp.org/LDP/abs/html/
原作:MendelCooper
原著版本:Revision10,10Mar2014
译著
早期译著连接:http://www.linuxsir.org/bbs/thread256887.html
译者:杨春敏黄毅
译著版本:Revision3.7,23Oct2005
最新Revision10LinuxStory社区的imcmy同学发起并组织翻译
LinuxStory通告地址http://www.linuxstory.org/asdvanced-bash-scripting-
guide-in-chinese/
翻译作品
翻译作品放在GitBook上,欢迎阅读!
翻译进度
Introduction
4
第一部分初见Shell[@imcmy][@zihengcat]
1.为什么使用shell编程[@imcmy][@zihengcat]
2.Sha-Bang#!)一起出发[@imcmy][@zihengcat]
第二部分Shell基础[@imcmy][@zihengcat]
3.特殊字符[@imcmy][@zihengcat]
4.变量与参数[@imcmy][@zihengcat]
5.引用[@mr253727942][@zihengcat]
6.退出与退出状态[@samita2030][@zihengcat]
7.测试[@imcmy][@zihengcat]
8.运算符相关话题[@samita2030][@zihengcat]
第三部分Shell进阶[@imcmy]
9.AnotherLookatVariables[@Ninestd]
10.变量处理[@imcmy]
11.循环与分支[@imcmy]
12.命令替换[@imcmy]
13.算术扩展[@imcmy]
14.休息时间[@imcmy]
第四部分.命令[@zhaozq]
15.内建命令[@zhaozq]
16.外部过滤器,程序与命令[@zhaozq]
17.系统与高级命令[@zhaozq]
第五章.AdvancedTopics
18.正则表达式[@Zjie]
18.1正则表达式简介[@Zjie]
18.2文件名替换[@Zjie]
19.嵌入文档[@mingmings]
20.I/O重定向[@mingmings]
21.Subshells[@mingmings]
22.RestrictedShells[@panblack]
23.ProcessSubstitution[@panblack]
24.Functions[@zy416548283]
25.别名[@mingmings]
26.ListConstructs[@panblack]
27.Arrays[@zy416548283]
28.IndirectReferences[@panblack]
29./devand/proc[@panblack]
Introduction
5
30.NetworkProgramming[@Zjie]
31.OfZerosandNulls[@panblack]
32.Debugging[@wuqichao]
33.Options[@zy416548283]
34.Gotchas[@liuburn]
35.ScriptingWithStyle[@chuchingkai]
36.Miscellany[@richard-ma]
37.Bash,versions2,3,and4
38.Endnotes[@zy416548283]
38.1Author'sNote
38.2AbouttheAuthor
38.3WheretoGoForHelp
38.4ToolsUsedtoProduceThisBook
38.5Credits
38.6Disclaimer
Bibliography
Appendix
A.ContributedScripts
B.ReferenceCards
C.ASedandAwkMicro-Primer[@wuqichao]
C.1Sed[@wuqichao]
C.2Awk[@wuqichao]
D.ParsingandManagingPathnames
E.ExitCodesWithSpecialMeanings
F.ADetailedIntroductiontoI/OandI/ORedirection
G.Command-LineOptions
G.1StandardCommand-LineOptions
G.2BashCommand-LineOptions
H.ImportantFiles
I.ImportantSystemDirectories
J.AnIntroductiontoProgrammableCompletion
K.Localization
L.HistoryCommands
M.Sample.bashrcand.bash_profileFiles
N.ConvertingDOSBatchFilestoShellScripts
O.Exercises
Introduction
6
O.1AnalyzingScripts
O.2WritingScripts
P.RevisionHistory
Q.DownloadandMirrorSites
R.ToDoList
S.Copyright
T.ASCIITable
Index
ListofTables
ListofExamples
翻译校审流程
初始化
1. 首先fork项目
2. fork过去的项目clone到本地
3. 命令行下运行gitcheckout-bdev创建一个新分支
4. 运行gitremoteaddupstream
https://github.com/LinuxStory/Advanced-Bash-Scripting-Guide-in-
Chinese.git添加远端库
5. 运行gitremoteupdate更新
6. 运行gitfetchupstreammaster拉取更新到本地
7. 运行gitrebaseupstream/master将更新合并到你的分支
初始化只需要做一遍,之后请在dev分支进行修改。
如果修改过程中项目有更新,请重复567步。
翻译校审流程
1. 保证在dev分支中
2. 打开README.md,在翻译进度后加上你自己的github
1.ShellProgramming![@翻译人][@校审人]
3. 本地提交修改,写明提交信息
4. push到你fork的项目中,然后登录GitHub
Introduction
7
5. 在你fork的项目的首页可以看到一个pullrequest按钮,点击它,填写说
明信息,然后提交即可
为了不重复工作,请等待我们确认了你的pullrequest(即你的名字出现在项
目中时),再进行翻译校审工作
6. 进行翻译校审,重复3-5步提交翻译校审的作品
新手可以参阅针对github小白的《翻译流程详解》,妹子写的呦~
翻译校审建议
1. 使用markdown进行翻译校审,文件名必须使用英文
2. 翻译校审后的文档请放到source文件夹下的对应章节中,然后pullrequest即可
3. 有任何问题随时欢迎发issue
4. 术语尽量保证和已翻译的一致,也可以查询微软术语搜索Linux中国术语词典
5. 你可以将你认为是术语的词汇加入术语表 TERM.md
关于版权
根据原著作者的要求,翻译成果属于公有领域(CC0),翻译参与人员及原著作者
MendelCooper享有署名权
Introduction
8
第一部分初见Shell
脚本:文章;书面文档
——韦伯斯特字典1913年版
Shell是一种命令解释器,它不仅分离了用户层与操作系统内核,更是一门强大的编
程语言。我们称为shell编写的程序为脚本(script)。脚本是一种易于使用的工
具,它能够将系统调用、工具软件、实用程序(utility)和已编译的二进制文件联系
在一起构建程序。实际上,shell脚本可以调用所有的UNIX命令、实用程序以及工
具软件。如果你觉得这还不够,使用像 test命令和循环结构这样的shell内建命令
能够让脚本更加灵活强大。Shell脚本特别适合完成系统管理任务和那些不需要复杂
结构性语言实现的重复工作。
内容目录
1.为什么使用shell编程
2.Sha-Bang#!)一起出发
2.1调用一个脚本
2.2牛刀小试
第一部分初见shell
9
第一章为什么使用shell编程
没有任何一种程序设计语言是完美的,甚至没有一个最好的语言。只有在特定
环境下适合的语言。
——HerbertMayer
无论你是否打算真正编写shell脚本,只要你想要在一定程度上熟悉系统管理,了解
掌握shell脚本的相关知识都是非常有必要的。例如Linux系统在启动的时候会执
/etc/rc.d目录下的shell脚本来恢复系统配置和准备服务。详细了解这些启动
脚本对分析系统行为大有益处,何况,你很有可能会去修改它们呢。
编写shell脚本并不困难,shell脚本由许多小的部分组成,而其中只有数量相当少的
shell本身特性,操作和选项 有关的部分才需要去学习。Shell语法非常简单朴
素,很像是在命令行中调用和连接工具,你只需遵循很少一部分的"规则"就可以
了。大部分短小的脚本通常在第一次就可以正常工作,即使是一个稍长一些的脚
本,调试起来也十分简单。
在个人计算机发展的早期,BASIC语言让计算机专业人士能够在早期的微机上
编写程序。几十年后,Bash脚本可以让所有仅对LinuxUNIX系统有初步了解
的用户在现代计算机上做同样的事。
我们现在已经可以做出一些又小又快的单板机,比如树莓派Bash脚本提供了
一种发掘这些有趣设备潜力的方式。
使用shell脚本构建一个复杂应用原型(prototype),不失为是一种虽有缺陷但非常
快速的方式。在项目开发初期,使用脚本实现部分功能往往显得十分有用。在使用
C/C++JavaPerlPython编写最终代码前,可以使用shell脚本测试,修补应用
结构,提前发现重大缺陷。
Shell脚本与经典的UINX哲学相似,将复杂的任务划分为简单的子任务,将组件与
工具连接起来。许多人认为比起新一代功能强大、高度集成的语言,例如Perl
shell脚本至少是一种在美学上更加令人愉悦的解决问题的方式,Perl试图做到面面
俱到,但你必须强迫自己改变思维方式适应它。
HerbertMayer曾说:有用的语言需要数组、指针以及构建数据结构的通用机制
如果依据这些标准,那shell脚本距有用还差得很远,甚至是无用的。
1
1.为什么使用shell编程
10
什么时候不应该使用shell脚本
资源密集型的任务,尤其是对速度有要求(如排序、散列、递归 等)
需要做大量的数学运算,例如浮点数运算,高精度运算或者复数运算(使用
C++FORTRAN代替)
有跨平台需求(使用C或者Java代替)
必须使用结构化编程的复杂应用(如变量类型检查、函数原型等)
影响系统全局的关键性任务
对安全性有高要求,需要保证系统的完整性以及阻止入侵、破解、恶意破坏
项目包含有连锁依赖关系的组件
需要大量的文件操作(Bash只能访问连续的文件,并且是以一种非常笨拙且低
效的逐行访问的方式进行的)
需要使用多维数组
需要使用如链表、树等数据结构
需要产生或操作图像和图形用户接口(GUI
需要直接访问系统硬件或外部设备
需要使用端口或套接字输入输出端口(SocketI/O
需要使用库或旧程序的接口
私有或闭源的项目(Shell脚本直接将源代码公开,所有人都可以看到)
如果你的应用满足上述任意一条,你可以考虑使用更加强大的脚本语言,如Perl
TclPythonRuby等,或考虑使用编译型语言,如CC++Java等。即使如此,
在开发阶段使用shell脚本建立应用原型也是十分有用的。
我们接下来将使用BashBash"Bourne-Againshell"的首字母缩略词 ,Bash
源于StephenBourne开发的Bourneshellsh)。如今Bash已成为了大部分UNIX
衍生版中shell脚本事实上的标准。本书所涉及的大部分概念在其他shell中也是适用
的,例如KornShellBash从它当中继承了一部分的特性 ;又如CShell及其变体
(需要注意的是,199310TomChristiansenUsenet帖子中指出,因CShell
内部固有的问题,不推荐使用CShell编程)
接下来的部分将是一些编写shell脚本的指导。这些指导很大程度上依赖于实例来阐
shell的特性。本书所有的例子都能够正常工作,并在尽可能的范围内进行过测
试,其中的一部分已经运用在实际生产生活中。读者们可以使用这些在存档中的例
子(文件名为 scriptname.shscriptname.bash) ,赋予它们可执行权限
chmodu+rxscriptname),然后执行它们看看会发生什么。如果存档不可
2
3
4
5
1.为什么使用shell编程
11
用,读者朋友也可以从本书的HTML或者PDF版本中复制粘贴代码出来。需要注意
的是,在部分例子中使用了一些暂时还未被解释的特性,这需要读者暂时跳过它
们。
除特别说明,本书所有例子均由本书作者编写。
Hiscountenancewasboldandbashednot.
——EdmundSpenser
.这些操作和选项被称为内建命令(builtin),是shell的内部特征。
.虽然递归可以在shell脚本中实现,但是它的效率很低且实现起来很复杂、不
具有美感。
.首字母缩略词是由每一个单词的首字母拼接而成的易读的代替短语。这不是
一个好习惯,通常会引起一些不必要的麻烦。
.ksh88中的许多特性,甚至一些ksh93的特性都被合并到Bash中了。
.按照惯例,用户编写的Bourneshell脚本应该在文件名后加上 .sh的扩展
名。而那些系统脚本,比如在 /etc/rc.d中的脚本通常不遵循这种规范。
1
2
3
4
5
1.为什么使用shell编程
12
第二章Sha-Bang#!)一起出发
Shell编程声名显赫
——LarryWall
本章目录
2.1调用一个脚本
2.2牛刀小试
一个最简单的脚本其实就是将一连串系统命令存储在一个文件中。最起码,它能帮
你省下重复输入这一连串命令的功夫。
样例2-1.cleanup:清理 /var/log目录下的日志文件
#Cleanup
#请使用root权限执行
cd/var/log
cat/dev/null>messages
cat/dev/null>wtmp
echo"Logfilescleanedup."
这支脚本仅仅是一些可以很容易从终端或控制台输入的命令的集合罢了,没什么特
殊的地方。将命令放在脚本中的好处是,你不用再一遍遍重复输入这些命令啦。脚
本成了一支程序、一款工具,它可以很容易的被修改或为特殊需求定制。
样例2-2.cleanup:改进的清理脚本
2.Sha-Bang(#!)一起出发
13
#!/bin/bash
#Bash脚本标准起始行。
#Cleanup,version2
#请使用root权限执行。
#这里可以插入代码来打印错误信息,并在未使用root权限时退出。
LOG_DIR=/var/log
#使用变量比硬编码(hard-coded)更合适
cd$LOG_DIR
cat/dev/null>messages
cat/dev/null>wtmp
echo"Logscleanedup."
exit#正确终止脚本的方式。
#不带参数的exit返回上一条指令的运行结果。
现在我们看到了一个真正意义上的脚本!让我们继续前进...
样例2-3.cleanup:改良、通用版
#!/bin/bash
#Cleanup,version3
#注意:
#--------
#此脚本涉及到许多后边才会解释的特性。
#当你阅读完整本书的一半以后,理解它们就没有任何困难了。
LOG_DIR=/var/log
ROOT_UID=0#UID0的用户才拥有root权限。
LINES=50#默认保存messages日志文件行数。
E_XCD=86#无法切换工作目录的错误码。
E_NOTROOT=87#root权限用户执行的错误码。
2.Sha-Bang(#!)一起出发
14
#请使用root权限运行。
if["$UID"-ne"$ROOT_UID"]
then
echo"Mustberoottorunthisscript."
exit$E_NOTROOT
fi
if[-n"$1"]
#测试命令行参数(保存行数)是否为空
then
lines=$1
else
lines=$LINES#如果为空则使用默认设置
fi
#StephaneChazelas建议使用如下方法检查命令行参数,
#但是这已经超出了此阶段教程的范围。
#
#E_WRONGARGS=85#Non-numericalargument(badargumentform
at).
#case"$1"in
#"")lines=50;;
#*[!0-9]*)echo"Usage:`basename$0`lines-to-cleanup";
#exit$E_WRONGARGS;;
#*)lines=$1;;
#esac
#
#*在第十一章循环与分支中会对此作详细的阐述。
cd$LOG_DIR
if[`pwd`!="$LOG_DIR"]#也可以这样写if["$PWD"!="$LOG_DI
R"]
#检查工作目录是否为/var/log?
then
2.Sha-Bang(#!)一起出发
15
echo"Can'tchangeto$LOG_DIR"
exit$E_XCD
fi#在清理日志前,二次确认是否在正确的工作目录下。
#更高效的写法:
#
#cd/var/log||{
#echo"Cannotchangetonecessarydirectory.">&2
#exit$E_XCD;
#}
tail-n$linesmessages>mesg.temp#保存messages日志文件最后一部分
mvmesg.tempmessages#替换系统日志文件以达到清理目的
#cat/dev/null>messages
#*我们不需要使用这个方法了,上面的方法更安全
cat/dev/null>wtmp#':>wtmp''>wtmp'有同样的效果
echo"Logfilescleanedup."
#注意在/var/log目录下的其他日志文件不会被这个脚本清除
exit0
#返回0表示脚本运行成功
也许你并不希望清空全部的系统日志,这个脚本保留了messages日志的最后一部
分。随着学习的深入,你将明白更多提高脚本运行效率的方法。
脚本起始行sha-bang#!) 告诉系统这个脚本文件需要使用指定的命令解释器来
执行。#!实际上是一个占两字节 的幻数(magicnumber,幻数可以用来标识特殊
的文件类型,在这里则是标记可执行shell脚本(你可以在终端中输入 man
magic了解更多信息)。紧随#!的是一个路径名。此路径指向用来解释此脚本的程
序,它可以是shell,可以是程序设计语言,也可以是实用程序。这个解释器从头
#!的下一行)开始执行整个脚本的命令,同时忽略注释。
1
2
3
2.Sha-Bang(#!)一起出发
16
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed-f
#!/bin/awk-f
上面每一条脚本起始行都调用了不同的解释器,比如 /bin/sh调用了系统默认
shellLinux系统中默认是bash) 。大部分UNIX商业发行版中默认的是Bourne
shell,即 #!/bin/sh。你可以以牺牲Bash特性为代价,在非Linux的机器上运行
sh脚本。当然,脚本得遵循POSIX sh标准。
需要注意的是 #!后的路径必须正确,否则当你运行脚本时只会得到一条错误信
息,通常是"Commandnotfound."
当脚本仅包含一些通用的系统命令而不使用shell内部指令时,可以省略 #!。第三
个例子需要 #!是因为当对变量赋值时,例如 lines=50,使用了与shell特性相
关的结构 。再重复一次, #!/bin/sh调用的是系统默认shell解释器,在Linux
统中默认为 /bin/bash
这个例子鼓励读者使用模块化的方式编写脚本,并在平时记录和收集一些在以后可
能会用到的代码模板。最终你将拥有一个相当丰富易用的代码库。以下的代码可以
用来测试脚本被调用时的参数数量是否正确。
E_WRONG_ARGS=85
script_parameters="-a-h-m-z"
#-a=all,-h=help等等
if[$#-ne$Number_of_expected_args]
then
echo"Usage:`basename$0`$script_parameters"
#`basename$0`是脚本的文件名
exit$E_WRONG_ARGS
fi
大多数情况下,你会针对特定的任务编写脚本。本章的第一个脚本就是这样。然后
你也许会泛化(generalize)脚本使其能够适应更多相似的任务,比如用变量代替
硬编码,用函数代替重复代码。
4
5
6
7
1
2.Sha-Bang(#!)一起出发
17
.在文献中更常见的形式是she-bang或者sh-bang。它们都来源于词汇
sharp(#)bang(!)的连接。
.一些UNIX的衍生版(基于4.2BSD)声称他们使用四字节的幻数,在#!后增
加一个空格,即 #!/bin/sh。而SvenMascheck指出这是虚构的。
.
命令解释器首先将会解释#!这一行,而因为#!#打头,因此解释器将其视作注
释。起始行作为调用解释器的作用已经完成了。
事实上即使脚本中含有不止一个#!,bash也会将除第一个`#!`以外的解释为注
释。
#!/bin/bash
echo"Part1ofscript."
a=1
#!/bin/bash
#这并不会启动新的脚本
echo"Part2ofscript."
echo$a#$a的值仍旧为1
1
2
3
4
2.Sha-Bang(#!)一起出发
18
.
这里允许使用一些技巧。
#!/bin/rm
#自我删除的脚本
#当你运行这个脚本,除了这个脚本本身消失以外并不会发生什么。
WHATEVER=85
echo"Thislinewillneverprint(betcha!)."
exit$WHATEVER#这没有任何关系。脚本将不会从这里退出。
#尝试在脚本终止后打印echo$a
#得到的值将会是0而不是85.
当然你也可以建立一个起始行是 #!/bin/moreREADME文件,并且使它可
以执行。结果就是这个文件成为了一个可以打印本身的文件。(查看样例19-
3,使用 cat命令的heredocument也许是一个更好的选择)
.可移植操作系统接口(POSIX)尝试标准化类UNIX操作系统。POSIX规范
可以在OpenGroupsite中查看。
.为了避免这种情况的发生,可以使用 #!/bin/envbash作为起始行。这在
bash不在 /binUNIX系统中会有效果。
.如果bash是系统默认shell,那么脚本并不一定需要#!作为起始行。但是当你
在其他的shell中运行脚本,例如tcsh,则需要使用#!
4
5
6
7
2.Sha-Bang(#!)一起出发
19
2.1调用一个脚本
写完一个脚本以后,你可以通过 shscriptname bashscriptname来调用
它(不推荐使用 sh<scriptname调用脚本,因为这会禁用脚本从标准输入
stdin)读入数据)。更方便的方式是使用 chmod命令使脚本可以被直接执行。
执行命令:
chmod555scriptname(给予所有用户读取/执行的权限)
chmod+rxscriptname(给予所有用户读取/执行的权限)
chmodu+rxscriptname(仅给予脚本所有者读取/执行的权限)
当脚本的权限被设置好后,你就可以直接使用 ./scriptname 进行调用测试了。
如果脚本文件以sha-bang开头,那么它将自动调用指定的命令解释器运行脚本。
完成调试与测试后,你可能会将脚本文件移至 /usr/local/bin(使用root权限)
中,使脚本可以被所有用户调用。这时你可以直接在命令行中输入 scriptname
[ENTER]执行脚本。
.注意,当你使用 shscriptname调用Bash脚本时,将会禁用与Bash特性
相关的功能,脚本有可能会执行失败。
.脚本需要同时具有读取和执行的权限,因为shell需要读取脚本执行。
.为什么不直接使用 scriptname来调用脚本?为什么当工作目录($PWD
正好是 scriptname所在目录时也不起作用?因为一些安全原因,当前目录
./)并不会被默认添加到用户的$PATH路径中。因此需要用户显式使
./scriptname在当前目录下调用脚本。
1
2
3
1
2
3
2.1调用一个脚本
20
2.2牛刀小试
1. 系统管理员通常会写一些脚本来完成自动化工作。试举例说明使用脚本的便利
之处。
2. 请尝试写一个脚本。调用脚本,会打印当前系统时间和日期,所有已登录的用
户和系统运行时间。并将这些信息保存到一个日志文件中。
2.2牛刀小试
21
第二部分shell基础
目录
3.特殊字符
4.变量与参数
4.1变量替换
4.2变量赋值
4.3Bash弱类型变量
4.4特殊变量类型
5.引用
5.1引用变量
5.2转义
6.退出与退出状态
7.测试
7.1测试结构
7.2文件测试操作
7.3其他比较操作
7.4嵌套if/then条件测试
7.5牛刀小试
8.运算符和相关话题
8.1运算符
8.2数字常量
8.3双圆括号结构
8.4运算符优先级
第二部分shell基础
22
第三章特殊字符
是什么让一个字符变得特殊呢?如果一个字符不仅具有字面意义,而且具有元意
meta-meaning),我们就称它为特殊字符。特殊字符同命令和关键词
keywords)一样,是bash脚本的组成部分。
你在脚本或其他地方都能够找到特殊字符。
#
注释符。如果一行脚本的开头是#(除了#!),那么代表这一行是注释,不会被执
行。
#这是一行注释
注释也可能会在一行命令结束之后出现。
echo"Acommentwillfollow."#这儿可以写注释
#^注意在#之前有空格
注释也可以出现在一行开头的空白符(whitespace)之后。
#这个注释前面存在着一个制表符(tab
注释甚至可以嵌入到管道命令(pipe)之中。
initial=(`cat"$startfile"|sed-e'/#/d'|tr-d'\n'|\
#删除所有带'#'注释符号的行
sed-e's/\./\./g'-e's/_/_/g'`)
#摘录自脚本life.sh
3.特殊字符
23
命令不能写在同一行注释之后。因为没有任何方法可以结束注释(仅支持单
行注释),为了让新命令正常执行,另起一行写吧。
当然,在 echo语句中被引用或被转义的#不会被认为是注释。同样,在某
些参数替换式或常量表达式中的#也不会被认为是注释。
echo"The#heredoesnotbeginacomment."
echo'The#heredoesnotbeginacomment.'
echoThe\#heredoesnotbeginacomment.
echoThe#herebeginsacomment.
echo${PATH#*:}#参数替换而非注释
echo$((2#101011))#进制转换而非注释
#感谢S.C.
因为引用符和转义符("'\)转义了#
一些模式匹配操作同样使用了#
;
命令分隔符[分号]。允许在同一行内放置两条或更多的命令。
echohello;echothere
if[-x"$filename"];then#注意在分号以后有一个空格
#+^^
echo"File$filenameexists.";cp$filename$filename.bak
else#^^
echo"File$filenamenotfound.";touch$filename
fi;echo"Filetestcomplete."
注意有时候";"需要被转义才能正常工作。
;;
3.特殊字符
24
case条件语句终止符[双分号]
case"$variable"in
abc)echo"\$variable=abc";;
xyz)echo"\$variable=xyz";;
esac
;;&,;&
case条件语句终止符(Bash4+版本)。
.
句点命令[句点]。等价于 source命令(查看样例15-22)。这是一个bash的内建
命令。
.
句点可以作为文件名的一部分。如果它在文件名开头,那说明此文件是隐藏文件。
使用不带参数的 ls命令不会显示隐藏文件。
3.特殊字符
25
bash$touch.hidden-file
bash$ls-l
total10
-rw-r--r--1bozo4034Jul1822:04data1.addressbook
-rw-r--r--1bozo4602May2513:58data1.addressbook.b
ak
-rw-r--r--1bozo877Dec172000employment.addressb
ook
bash$ls-al
total14
drwxrwxr-x2bozobozo1024Aug2920:54./
drwx------52bozobozo3072Aug2920:51../
-rw-r--r--1bozobozo4034Jul1822:04data1.address
book
-rw-r--r--1bozobozo4602May2513:58data1.address
book.bak
-rw-r--r--1bozobozo877Dec172000employment.ad
dressbook
-rw-rw-r--1bozobozo0Aug2920:54.hidden-file
当句点出现在目录中时,单个句点代表当前工作目录,两个句点代表上级目录。
bash$pwd
/home/bozo/projects
bash$cd.
bash$pwd
/home/bozo/projects
bash$cd..
bash$pwd
/home/bozo/
句点通常代表文件移动的目的地(目录),下式代表的是当前目录。
3.特殊字符
26
bash$cp/home/bozo/current_work/junk/*.
复制所有的垃圾文件当前目录
.
句点匹配符。在正则表达式中,点号意味着匹配任意单个字符。
"
部分引用[双引号]。在字符串中保留大部分特殊字符。详细内容将在第五章介绍。
'
全引用[单引号]。在字符串中保留所有的特殊字符。是部分引用的强化版。详细内
容将在第五章介绍。
,
逗号运算符。逗号运算符 将一系列的算术表达式串联在一起。算术表达式依次被
执行,但只返回最后一个表达式的值。
let"t2=((a=9,15/3))"
#a被赋值为9t2被赋值为15/3
逗号运算符也可以用来连接字符串。
1
3.特殊字符
27
forfilein/{,usr/}bin/*calc
#^/bin/usr/bin目录中
#+找到所有的以"calc"结尾的可执行文件
do
if[-x"$file"]
then
echo$file
fi
done
#/bin/ipcalc
#/usr/bin/kcalc
#/usr/bin/oidcalc
#/usr/bin/oocalc
#感谢RoryWinston提供的执行结果
,,,
在参数替换中进行小写字母转换(Bash4新增)。
\
转义符[反斜杠]。转义某字符的标志。
\x转义了字符x。双引号""内的X与单引号内的X具有同样的效果。转义符也可以
用来转义"',使它们表达其字面含义。
第五章将更加深入的解释转义字符。
/
文件路径分隔符[正斜杠]。起分割路径的作用。(比如
/home/bozo/projects/Makefile
它也在算术运算中充当除法运算符。
3.特殊字符
28
`
命令替换符。 `command`结构可以使得命令的输出结果赋值给一个变量。通常
也被称作后引号(backquotes)或反引号(backticks)。
:
空命令[冒号]。它在shell中等价于"NOP"(即noop,空操作)与shell内建命令true
有同样的效果。它本身也是Bash的内建命令之一,返回值是true0)。
:
echo$?#返回0
在无限循环中的应用:
while:
do
operation-1
operation-2
...
operation-n
done
#等价于
#whiletrue
#do
#...
#done
可在if/then中充当占位符:
ifcondition
then:#什么都不做,跳出判断执行下一条语句
else
take-some-action
fi
3.特殊字符
29
在二元操作中作占位符:查看样例8-2或默认参数部分。
:${username=`whoami`}
#${username=`whoami`}如果没有:就会报错
#除非"username"是系统命令或内建命令
:${1?"Usage:$0ARGUMENT"}#摘自样例脚本"usage-message.sh"
查看样例19-10了解空命令在heredocument中作为占位符的情况。
使用参数替换为字符串变量赋值(查看样例10-7)。
:${HOSTNAME?}${USER?}${MAIL?}
#如果其中一个或多个必要的环境变量没有被设置
#将会打印错误
查看变量扩展或字符串替换章节了解空命令在其中的作用。
>重定向操作符结合,可以在不改变文件权限的情况下清空文件。如果文件不
存在,那么将创建这个文件。
:>data.xxx#文件"data.xxx"已被清空
#cat/dev/null>data.xxx作用相同
#但是此操作不会产生一个新进程,因为":"shell内建命令。
也可查看样例16-15
>>重定向操作符结合,将不会清空任何已存在的文件( :>>
target_file)。如果文件不存在,将创建这个文件。
以上操作仅适用于普通文件,不适用于管道、符号链接和特殊文件。
空命令可以用来作为一行注释的开头,尽管我们并不推荐这么做。使用#可以使解
释器关闭该行的错误检测,所以几乎所有的内容都可以出现在注释#中。使用空命
令却不是这样的:
:这一行注释将会产生一个错误,(if[$x-eq3])
3.特殊字符
30
:也可以作为一个域分隔符,比如在 /etc/passwd$PATH变量中。
bash$echo$PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr
/games
将冒号作为函数名也是可以的。
:()
{
echo"Thenameofthisfunctionis"$FUNCNAME""
#为什么要使用冒号作函数名?
#这是一种混淆代码的方法......
}
:
#函数名是:
这种写法并不具有可移植性,也不推荐使用。事实上,在Bash的最近的版本更新中
已经禁用了这种用法。但我们还可以使用下划线_来替代。
冒号也可以作为非空函数的占位符。
not_empty()
{
:
}#含有空指令,这并不是一个空函数。
!
取反(或否定)操作符[感叹号]!操作符反转已执行的命令的返回状态(查看样例
6-2)。它同时可以反转测试操作符的意义,例如可以将相等(=)反转成不等
!=)。它是一个Bash关键词。
在一些特殊场景下,它也会出现在间接变量引用中。
3.特殊字符
31
在另外一些特殊场景下,即在命令行下可以使用!调用Bash的历史记录(附录
L)。需要注意的是,在脚本中,这个机制是被禁用的。
*
通配符[星号]。在文件匹配(globbing)操作时扩展文件名。如果它独立出现,则匹
配该目录下的所有文件。
bash$echo*
abs-book.sgmladd-drive.shagram.shalias.sh
在正则表达式中表示匹配任意多个(包括0)前个字符。
*
算术运算符。在进行算术运算时,表示乘法运算。
**双星号可以表示乘方运算或扩展文件匹配。
?
测试操作符[问号]。在一些特定的语句中,?表示一个条件测试。
在一个双圆括号结构中,?可以表示一个类似C语言风格的三元(trinary)运算符的
一个组成部分。
condition?result-if-true:result-if-false
2
3.特殊字符
32
((var0=var1<98?9:21))
#不要加空格,紧挨着写
#等价于
#if["$var1"-lt98]
#then
#var0=9
#else
#var0=21
#fi
在参数替换表达式中,?用来测试一个变量是否已经被赋值。
?
通配符。它在进行文件匹配(globbing)时以单字符通配符扩展文件名。在扩展正
则表达式中匹配一个单字符。
$
取值符号[钱字符],用来进行变量替换(即取出变量的内容)。
var1=5
var2=23skidoo
echo$var1#5
echo$var2#23skidoo
如果在变量名前有$,则表示此变量的值。
$
行结束符[EOF]在正则表达式中,$匹配行尾字符串。
${}
3.特殊字符
33
参数替换。
$'...'
引用字符串扩展。这个结构将转义八进制或十六进制的值转换成ASCII Unicode
字符。
$*,$@
位置参数。
$?
返回状态变量。此变量保存一个命令、一个函数或该脚本自身的返回状态。
$$
进程ID变量。此变量保存该运行脚本的进程ID
()
命令组。
(a=hello;echo$a)
通过括号执行一系列命令会产生一个子shellsubshell)。括号中的变量,即
在子shell中的变量,在脚本的其他部分是不可见的。父进程脚本不能访问子进程
(子shell)所创建的变量。
a=123
(a=321;)
echo"a=$a"#a=123
#在括号中的"a"就像个局部变量。
数组初始化。
3
4
3.特殊字符
34
Array=(element1element2element3)
{xxx,yyy,zzz,...}
花括号扩展结构。
echo\"{These,words,are,quoted}\"#"将作为单词的前缀和后缀
#"These""words""are""quoted"
cat{file1,file2,file3}>combined_file
#file1,file2file3拼接在一起后写入combined_file中。
cpfile22.{txt,backup}
#"file22.txt"拷贝为"file22.backup"
这个命令可以作用于花括号内由逗号分隔的文件描述列表。 文件名扩展(匹配)
作用于大括号间的各个文件。
除非被引用或被转义,否则空白符不应在花括号中出现。
echo{file1,file2}\:{\A,"B",'C'}
file1:Afile1:Bfile1:Cfile2:Afile2:Bfile2:C
{a..z}
扩展的花括号扩展结构。
5
3.特殊字符
35
echo{a..z}#abcdefghijklmnopqrstuvwxy
z
#输出az之间所有的字母。
echo{0..3}#0123
#输出03之间所有的数字。
base64_charset=({A..Z}{a..z}{0..9}+/=)
#使用扩展花括号初始化一个数组。
#摘自vladz编写的样例脚本"base64.sh"
Bash第三版中引入了{a..z}扩展的花括号扩展结构。
{}
代码块[花括号],又被称作内联组(inlinegroup)。它实际上创建了一个匿名函数
anonymousfunction),即没有名字的函数。但是,不同于那些标准函数,代
码块内的变量在脚本的其他部分仍旧是可见的。
bash${locala;
a=123;}
bash:local:canonlybeusedina
function
a=123
{a=321;}
echo"a=$a"#a=321(代码块内赋值)
#感谢S.C.
代码块可以经由I/O重定向进行输入或输出。
样例3-1.代码块及I/O重定向
3.特殊字符
36
#!/bin/bash
#读取文件/etc/fstab
File=/etc/fstab
{
readline1
readline2
}<$File
echo"Firstlinein$Fileis:"
echo"$line1"
echo
echo"Secondlinein$Fileis:"
echo"$line2"
exit0
#你知道如何解析剩下的行吗?
#提示:使用awk...
#Hans-JoergDiers建议:使用Bash的内建命令set
样例3-2.将代码块的输出保存至文件中
#!/bin/bash
#rpm-check.sh
#查询一个rpm文件的文件描述、包含文件列表,以及是否可以被安装。
#将输出保存至文件。
#
#这个脚本使用代码块来描述。
SUCCESS=0
E_NOARGS=65
if[-z"$1"]
then
echo"Usage:`basename$0`rpm-file"
exit$E_NOARGS
3.特殊字符
37
fi
{#代码块起始
echo
echo"ArchiveDescription:"
rpm-qpi$1#查询文件描述。
echo
echo"ArchiveListing:"
rpm-qpl$1#查询文件列表。
echo
rpm-i--test$1#查询是否可以被安装。
if["$?"-eq$SUCCESS]
then
echo"$1canbeinstalled."
else
echo"$1cannotbeinstalled."
fi
echo#代码块结束。
}>"$1.test"#输出重定向至文件。
echo"Resultsofrpmtestinfile$1.test"
#rpm各项参数的具体含义可查看man文档
exit0
与由圆括号包裹起来的命令组不同,由花括号包裹起来的代码块不产生子进
程。
也可以使用非标准的for循环语句来遍历代码块。
{}
文本占位符。在xargs-i后作为输出的占位符使用。
6
3.特殊字符
38
ls.|xargs-i-tcp./{}$1
#^^^^
#摘自"ex42.sh"(copydir.sh)
{}\;
路径名。通常在find命令中使用,但这不是shell的内建命令。
定义:路径名是包含完整路径的文件名,例
/home/bozo/Notes/Thursday/schedule.txt。我们通常又称之为绝对路
径。
在执行 find-exec时最后需要加上 ;,但是分号需要被转义以保证其不会
shell解释。
[]
测试。在[]之间填写测试表达式。值得注意的是,[shell内建命令test的一
个组成部分,而不是外部命令/usr/bin/test的链接。
[[]]
测试。在[[]]之间填写测试表达式。相比起单括号测试[]),它更加的灵活。它
是一个shell的关键字。
详情查看关于[[]]结构的讨论。
[]
数组元素。在数组中,可以使用中括号的偏移量来用来访问数组中的每一个元素。
Array[1]=slot_1
echo${Array[1]}
[]
3.特殊字符
39
字符集、字符范围。在正则表达式中,中括号用来匹配指定字符集或字符范围内的
任意字符。
$[...]
整数扩展符。在$[]中可以计算整数的算术表达式。
a=3
b=7
echo$[$a+$b]#10
echo$[$a*$b]#21
(())
整数扩展符。在(())中可以计算整数的算术表达式。
详情查看关于((...))结构的讨论。
>&>>&>><<>
重定向。
scriptname>filename将脚本scriptname的输出重定向到filename中。如果
文件存在,那么覆盖掉文件内容。
command&>filename将命令command的标准输出(stdout)和标准错误输出
(stderr)重定向到filename
重定向在用于清除测试条件的输出时特别有效。例如测试一个特定的命令是否
存在。
bash$typebogus_command&>/dev/null
bash$echo$?
1
3.特殊字符
40
或写在脚本中:
command_test(){type"$1"&>/dev/null;}
#^
cmd=rmdir#存在的命令。
command_test$cmd;echo$?#返回0
cmd=bogus_command#不存在的命令。
command_test$cmd;echo$?#返回1
command>&2将命令的标准输出重定向至标准错误输出。
scriptname>>filename将脚本scriptname的输出追加到filename文件末尾。
如果文件不存在,那么将创建这个文件。
[i]<>filename打开文件filename用来读写,并且分配一个文件描述符i指向
它。如果文件不存在,那么将创建这个文件。
进程替换:(command)><(command)
在某些情况下,"<"">"将用作字符串比较。
在另外一些情况下,"<"">"将用作数字比较。详情查看样例16-9
<<
heredocument中进行重定向。
<<<
herestring中进行重定向。
<,>
ASCII码比较。
3.特殊字符
41
veg1=carrots
veg2=tomatoes
if[["veg1"<"veg2"]]
then
echo"Although$veg1precede$veg2inthedictionary,"
echo-n"thisdoesnotnecessarilyimplyanything"
echo"aboutmyculinarypreferences."
else
echo"Whatkindofdictionaryareyouusing,anyhow?"
fi
\<,>
正则表达式中的单词边界(wordboundary)。
bash$grep'\<the\>'textfile
|
管道(pipe)。管道可以将上一个命令的输出作为下一个命令的输入,或者直接输
出到shell中。管道是一种可以将一系列命令连接在一起的绝妙方式。
echols-l|sh
#"echols-l"的结果输出到shell中,
#与直接输入"ls-l"的结果相同。
cat*.lst|sort|uniq
#将所有后缀名为lst的文件合并后排序,接着删掉所有重复行。
3.特殊字符
42
管道是一种在进程间通信的典型方法。它将一个进程的输出作为另一个进程的
输入。举一个经典的例子,像cat或者echo这样的命令,可以通过管道
将它们产生的数据流导入到过滤器(filter)中。过滤器是可以用来处理输入流
的命令。
cat$filename1$filename2|grep$search_word
查看UNIXFAQ第三章获取更多关于使用UNIX管道的信息。
命令的输出同样可以通过管道输入到脚本中。
#!/bin/bash
#uppercase.sh:将所有输入变成大写
tr'a-z''A-Z'
#为了防止产生单字符文件名,
#必须使用单引号引用字符范围。
exit0
现在,让我们将ls-l的输出通过管道导入到脚本中。
bash$ls-l|./uppercase.sh
-RW-RW-R--1BOZOBOZO109APR719:491.TXT
-RW-RW-R--1BOZOBOZO109APR1416:482.TXT
-RW-R--R--1BOZOBOZO725APR2020:56DATA-FILE
在管道中,每一个进程的输出必须作为下个进程的输入被正确读入,如果不这
样,数据流会被阻塞(block),管道就无法按照预期正常工作。
catfile1file2|ls-l|sort
#"catfile1file2"的输出会消失。
管道是在一个子进程中运行的,因此它并不能修改父进程脚本中的变量。
7
3.特殊字符
43
variable="initial_value"
echo"new_value"|readvariable
echo"variable=$variable"#variable=initial_value
如果管道中的任意一个命令意外中止了,管道将会提前中断,我们称其为管道破裂
(BrokenPipe)。出现这种情况,系统将发送一个SIGPIPE信号。
>|
强制重定向。即使在noclobber选项被设置的情况下,重定向也会覆盖已存在
的文件。
||
或(OR)逻辑运算符。在测试结构中,任意一个测试条件为真,整个表达式为
真。返回0(成功标志位)。
&
后台运行操作符。如果命令后带&,那么此命令将转至后台运行。
bash$sleep10&
[1]850
[1]+Donesleep10
在脚本中,命令甚至循环都可以在后台运行。
样例3-3.在后台运行的循环
#!/bin/bash
#background-loop.sh
foriin12345678910#第一个循环
do
echo-n"$i"
done&#这个循环在后台运行。
3.特殊字符
44
#有时会在第二个循环结之后才执行此后台循环。
echo#'echo'有时不显示
foriin11121314151617181920#第二个循环
do
echo-n"$i"
done
echo#'echo'有时不显示
#======================================================
#脚本期望输出结果:
#12345678910
#11121314151617181920
#一些情况下可能会输出:
#11121314151617181920
#12345678910bozo$
#第二个'echo'没有被执行,为什么?
#另外一些情况下可能会输出:
#1234567891011121314151617181920
#第一个'echo'没有被执行,为什么?
#非常罕见的情况下,可能会输出:
#1112131234567891014151617181920
#前台循环抢占(preempt)了后台循环。
exit0
#NasimuddinAnsari建议:在第6行和第14行的
#echo-n"$i"后增加sleep1
#会得到许多有趣的结果。
脚本在后台执行命令时可能因为等待键盘事件被挂起。幸运的是,有一套方案
可以解决这个问题。
3.特殊字符
45
&&
与(AND)逻辑操作符。在测试结构中,所有测试条件都为真,表达式才为真,返
0(成功标志位)。
-
选项与前缀。它可以作为命令的选项标志,也可以作为一个操作符的前缀,也可以
作为在参数代换中作为默认参数的前缀。
COMMAND-[Option1][Option2][..]
ls-al
sort-dfu$filename
if[$file1-ot$file2]
then#^
echo"File$file1isolderthan$file2."
fi
if["$a"-eq"$b"]
then#^
echo"$aisequalto$b."
fi
if["$c"-eq24-a"$d"-eq47]
then#^^
echo"$cequals24and$dequals47."
fi
param2=${param1:-$DEFAULTVAL}
#^
--
双横线一般作为命令长选项的前缀。
3.特殊字符
46
sort--ignore-leading-blanks
双横线与Bash内建命令一起使用时,意味着该命令选项的结束。
下面提供了一种删除文件名以横线开头文件的简单方法。
bash$ls-l
-rw-r--r--1bozobozo0Nov2512:29-badname
bash$rm---badname
bash$ls-l
total0
双横线通常也和set连用。
set--$variable(查看样例15-18)。
-
重定向输入输出[短横线]
bash$cat-
abc
abc
...
Ctl-D
在这个例子中, cat-输出由键盘读入的标准输入(stdin)标准输出(stdout)
但是在真实应用的I/O重定向中是否有使用'-'
(cd/source/directory&&tarcf-.)|(cd/dest/directory&&t
arxpvf-)
#将整个文件树从一个目录移动到另一个目录。
3.特殊字符
47
#感谢AlanCox<a.cox@swansea.ac.uk>所作出的部分改动
#1)cd/source/directory
#工作目录定位到文件所属的源目录
#2)&&
#"与链":如果'cd'命令操作成功,那么执行下一条命令
#3)tarcf-.
#'tarc'(create创建)创建一份新的档案
#'tarf-'(file指定文件)'-'后指定一个目标文件作为输出
#'.'代表当前目录
#4)|
#通过管道进行重定向
#5)(...)
#在建立的子进程中执行命令
#6)cd/dest/directory
#工作目录定位到目标目录
#7)&&
#2)相同
#8)tarxpvf-
#'tarx'解压档案
#'tarp'(preserve保留)保留档案内文件的所有权及文件权限
#'tarv'(verbose冗余)发送全部信息到标准输出
#'tarf-'(file指定文件)'-'后指定一个目标文件作为输入
#
#注意'x'是一个命令,而'p','v','f'是选项。
#干的漂亮!
#更加优雅的写法是:
#cdsource/directory
#tarcf-.|(cd../dest/directory;tarxpvf-)
#
#同样可以写成:
#cp-a/source/directory/*/dest/directory
#:
#cp-a/source/directory/*/source/directory/.[^.]*/dest/dir
ectory
#可以在源目录中有隐藏文件时使用
3.特殊字符
48
bunzip2-clinux-2.6.16.tar.bz2|tarxvf-
#--未解压的tar文件--|--将解压出的tar传递给"tar"--
#如果不使用管道让"tar"处理"bunzip2"得到的文件,
#那么就需要使用单独的两步来完成。
#目的是为了解压"bzipped"压缩的内核源代码。
下面的例子中,"-"并不是一个Bash的操作符,它仅仅是tar, cat等一些特定
UNIX命令中将结果输出到标准输出的选项。
bash$echo"whatever"|cat-
whatever
当需要文件名的时候,-可以用来代替某个文件而重定向到标准输出(通常出现在
tarcf中)或从stdin中接受数据。这是一种在管道中使用面向文件(file-
oriented)工具作为过滤器的方法。
bash$file
Usage:file[-bciknvzL][-fnamefile][-mmagicfiles]file...
单独执行file命令,将会得到一条错误信息。
在命令后增加一个"-"可以得到一个更加有用的结果。它会使得shell暂停等待用户
输入。
bash$file-
abc
standardinput:ASCIItext
bash$file-
#!/bin/bash
standardinput:Bourne-Againshellscripttextexec
utable
现在命令能够接受标准输入并且处理它们了。
3.特殊字符
49
"-"能够通过管道将标准输出重定向到其他命令中。这就可以做到像在某个文件前添
加几行这样的事情。
使用diff比较两个文件的部分内容:
grepLinuxfile1|difffile2-
最后介绍一个使用-tar命令的实际案例。
样例3-4.备份最近一天修改过的所有文件
3.特殊字符
50
#!/bin/bash
#将当前目录下24小时之内修改过的所有文件备份成一个
#"tarball"(tar打包`gzip压缩)文件
BACKUPFILE=backup-$(date+%m-%d-%Y)
#在备份文件中嵌入时间
#感谢JoshuaTschida提供的建议
archive=${1:-$BACKUPFILE}
#如果没有在命令行中特别制定备份格式,
#那么将会默认设置为"backup-MM-DD-YYYY.tar.gz"
tarcvf-`find.-mtime-1-typef-print`>$archive.tar
gzip$archive.tar
echo"Directory$PWDbackedupinarchivefile\"$archive.tar.gz
\"."
#StephaneChazeles指出如果目录中有非常多的文件,
#或文件名中包含空白符时,上面的代码会运行失败。
#他建议使用以下的任意一种方法:
#--------------------------------------------------------------
-----
#find.-mtime-1-typef-print0|xargs-0tarrvf"$archiv
e.tar"
#使用了GNU版本的"find"命令。
#find.-mtime-1-typef-exectarrvf"$archive.tar"'{}'\;
#兼容其他的UNIX发行版,但是速度会比较慢
#--------------------------------------------------------------
-----
exit0
3.特殊字符
51
"-"开头的文件在和"-"重定向操作符一起使用时可能会导致一些问题。因此
合格的脚本必须首先检查这种情况。如果遇到,就需要给文件名加一个合适的前
缀,比如./-FILENAME,$PWD/-FILENAME或者 $PATHNAME/-FILENAME
如果变量的值以'-'开头,也可能会造成类似问题。
var='-n'
echo$var
#等同于"echo-n",不会输出任何东西。
-
先前的工作目录。使用cd-命令可以返回先前的工作目录。它实际上是使用了
$OLDPWD环境变量。
不要将这里的"-"与先前的"-"重定位操作符混淆。"-"的具体含义需要根据上下
文来解释。
-
减号。算术运算符中的减法标志。
=
等号。赋值操作符。
a=28
echo$a#28
在一些情况下,"="可以作为字符串比较操作符。
+
加号。加法算术运算。
在一些情况下,+是作为正则表达式中的一个操作符。
3.特殊字符
52
+
选项操作符。作为一个命令或过滤器的选项标记。
特定的一些指令和内建命令使用+启用特定的选项,使用-禁用特定的选项。在参
数代换中,+是作为变量扩展的备用值(alternatevalue)的前缀。
%
取模。取模操作运算符。
let"z=5%3"
echo$z#2
在另外一些情况下,%是一种模式匹配的操作符。
~
主目录[波浪号]。它相当于内部变量$HOME~bozobozo的主目录,执行
ls~bozo将会列出他的主目录中内容。 ~/是当前用户的主目录,执行ls
~/将会列出其中所有的内容。
bash$echo~bozo
/home/bozo
bash$echo~
/home/bozo
bash$echo~/
/home/bozo/
bash$echo~:
/home/bozo:
bash$echo~nonexistent-user
~nonexistent-user
3.特殊字符
53
~+
当前工作目录。它等同于内部变量$PWD
~-
先前的工作目录。它等同于内部变量$OLDPWD
=~
正则表达式匹配。将在Bashversion3章节中介绍。
^
行起始符。在正则表达式中,"^"代表一行文本的开始。
^,^^
参数替换中的大写转换符(在Bash4版新增)。
控制字符
改变终端或文件显示的一些行为。一个控制符是由CONTRL+key组成的(同时按
下)。控制字符同样可以通过转义以八进制或十六进制的方式显示。
控制符不能在脚本中使用。
Ctrl-A
移动光标至行首。
Ctrl-B
非破坏性退格(即不删除字符)。
Ctrl-C
3.特殊字符
54
中断指令。终止当前运行的任务。
Ctrl-D
登出shell(类似exit
键入EOFend-of-file,文件终止标记),中断stdin的输入。
当你在终端或xterm窗口中输入字符时, Ctl-D将会删除光标上的字符。当没有
字符时, Crl-D将会登出shell。在xterm中,将会关闭整个窗口。
Ctrl-E
移动光标至行末。
Ctrl-F
光标向前移动一个字符。
Ctrl-G
响铃 BEL。在一些老式打字机终端上,将会响铃。而在xterm中,将会产
声。
Ctrl-H
抹除(破坏性退格)。退格删除前面的字符。
3.特殊字符
55
#!/bin/bash
#在字符串中嵌入Ctrl-H
a="^H^H"#两个退格符Ctrl-H
#vi/vim中使用Ctrl-VCtrl-H来键入
echo"abcdef"#abcdef
echo
echo-n"abcdef$a"#abcdf
#^^末尾有空格退格两次的结果
echo
echo-n"abcdef$a"#abcdef
#^末尾没有空格时为什么退格无效了?
#并不是我们期望的结果。
echo;echo
#ConstantinHagemeier建议尝试一下:
#a=$'\010\010'
#a=$'\b\b'
#a=$'\x08\x08'
#但是这些并不会改变结果。
########################################
#现在来试试这个。
rubout="^H^H^H^H^H"#5Ctrl-H
echo-n"12345678"
sleep2
echo-n"$rubout"
sleep2
Ctrl-I
水平制表符。
Ctrl-J
3.特殊字符
56
另起一行(换行)。在脚本中,你也可使用八进制'\012'或者十六进制'\x0a'来表
示。
Ctrl-K
垂直制表符。
当你在终端或xterm窗口中输入字符时, Ctrl-K将会删除光标上及其后的所有
字符。而在脚本中, Ctrl-K的作用有些不同。具体查看下方LeeLee
Maschmeyer写的样例。
Ctrl-L
清屏、走纸。在终端中等同于clear命令。在打印时, Ctrl-L将会使纸张移
动到底部。
Ctrl-M
回车(CR)。
3.特殊字符
57
#!/bin/bash
#感谢LeeMaschmeyer提供的样例。
read-n1-s-p\
$'Control-Mleavescursoratbeginningofthisline.PressEnter
.\x0d'
#'0d'Control-M的十六进制的值
echo>&2#'-s'参数禁用了回显,所以需要显式的另起一行。
read-n1-s-p$'Control-Jleavescursoronnextline.\x0a'
#'0a'Control-J换行符的十六进制的值
echo>&2
###
read-n1-s-p$'AndControl-K\x0bgoesstraightdown.'
echo>&2#Control-K是垂直制表符。
#一个更好的垂直制表符例子是:
var=$'\x0aThisisthebottomline\x0bThisisthetopline\x0a'
echo"$var"
#这将会产生与上面的例子类似的结果。但是
echo"$var"|col
#这却会使得右侧行高于左侧行。
#这也解释了为什么我们需要在行首和行尾加上换行符
#来避免显示的混乱。
#LeeMaschmeyer的解释:
#--------------------------
#在第一个垂直制表符的例子中,垂直制表符使其
#在没有回车的情况下向下打印。
#这在那些不能回退的设备上,例如Linux的终端才可以。
#而垂直制表符的真正目的是向上而非向下。
#它可以用来在打印机中用来打印上标。
#col工具可以用来模拟真实的垂直制表符行为。
exit0
3.特殊字符
58
Ctrl-N
在命令行历史记录中调用下一条历史命令 。
Ctrl-O
在命令行中另起一行。
Ctrl-P
在命令行历史记录中调用上一条历史命令。
Ctrl-Q
恢复(XON)。
终端恢复读入stdin
Ctrl-R
在命令行历史记录中进行搜索。
Ctrl-S
挂起(XOFF)。
终端冻结stdin。(可以使用Ctrl-Q恢复)
Ctrl-T
交换光标所在字符与其前一个字符。
Ctrl-U
删除光标所在字符之前的所有字符。在一些情况下,不管光标在哪个位
置, Ctrl-U都会删除整行文字。
Ctrl-V
输入时,使用Ctrl-V允许插入控制字符。例如,下面两条语句是等价的:
8
3.特殊字符
59
echo-e'\x0a'
echo<Ctl-V><Ctl-J>
Ctrl-V在文本编辑器中特别有用。
Ctrl-W
当你在终端或xterm窗口中输入字符时, Ctrl-W将会删除光标所在字符之前到
其最近的空白符之间的所有字符。在一些情况下, Ctrl-W会删除到之前最近的
非字母或数字的字符。
Ctrl-X
在一些特定的文本处理程序中,剪切高亮文本并复制到剪贴板(clipboard)。
Ctrl-Y
粘贴之前使用Ctrl-UCtrl-W删除的文字。
Ctrl-Z
暂停当前运行的任务。
在一些特定的文本处理程序中是替代操作。
MSDOS文件系统中作为EOFend-of-file,文件终止标记)。
空白符
作为命令或变量之间的分隔符。空白符包含空格、制表符、换行符或它们的任意组
合。 在一些地方,比如变量赋值时,空白符不应该出现,否则会造成语法错误。
空白行在脚本中不会有任何实际作用,但是可以划分代码,使代码更具可读性。
特殊变量$IFS是作为一些特定命令的输入域(field)分隔符,默认值为空白
符。
9
3.特殊字符
60
定义:域是字符串中离散的数据块。使用空白符或者指定的字符(通常由
$IFS决定)来分隔临近域。在一些情况下,域也可以被称作记录
record)。
如果想在字符串或者变量中保留空白符,请引用。
UNIX过滤器可以使用POSIX字符类[:space:]来寻找和操作空白符。
.操作符(operator)用来执行表达式(operation)。最常见的例子就是算术
运算符+-*/。在Bash中,操作符和关键字的概念有一些重叠。
.它更被人熟知的名字是三元(ternary)操作符。但是读起来不清晰,而且容
易令人混淆。trinary是一种更加优雅的写法。
.美国信息交换标准代码(AmericanStandardCodeforInformation
Interchange)。这是一套可以由计算机存储和处理的7位(bit)字符(包含字
母、数字和一系列有限的符号)编码系统。
.进程标识符(PID),是分配给正在运行进程的唯一数字标识。可以使用
ps命令查看进程的PID
定义:进程是正在执行的命令或程序,通常也称作任务。
.shell来执行大括号扩展操作。命令本身是在扩展的基础上进行操作的。
.例外:作为管道的一部分的大括号中的代码块可能会运行在子进程中。
ls|{readfirstline;readsecondline;}
#错误。大括号中的代码块在子进程中运行,
#+因此"ls"命令输出的结果不能传递到代码块中。
echo"Firstlineis$firstline;secondlineis
$secondline"#无效。
#感谢S.C.
.正如在古代催情剂(philtre)被认为是一种能引发神奇变化的药剂一样,
UNIX中的过滤器(filter)也是有类似的作用的。
(如果一个程序员做出了一个能够在Linux设备上运行的"lovephiltre",那么
他将会获得巨大的荣誉。)
1
2
3
4
5
6
7
8
3.特殊字符
61
.Bash将之前在命令行中执行过的命令存储在缓存(buffer)中,或者一块内
存区域里。可以使用内建命令history来查看。
.换行符本身也是一个空白符。因此这就是为什么仅仅包含一个换行符的空行
也被认为是空白符。
8
9
3.特殊字符
62
第四章变量与参数
本章目录
4.1变量替换
4.2变量赋值
4.3Bash变量弱类型
4.4特殊变量类型
变量(variable)在编程语言中用来表示数据。它本身只是一个标记,指向数据在
计算机内存中的一个或一组地址。
变量通常出现在算术运算,数量操作及字符串解析中。
4.变量与参数
63
4.1变量替换
变量名是其所指向值的一个占位符(placeholder)。引用变量值的过程我们称之为
变量替换(variablesubstitution)。
$
接下来我们仔细区分一下变量名与变量值。如果变量名是variable1那么
$variable1就是对变量值的引用。
bash$variable1=23
bash$echovariable1
variable1
bash$echo$variable1
23
变量仅仅在声明时、赋值时、被删除时( unset)、被导出时( export),算
术运算中使用双括号结构((...))时或在代表信号时(signal,查看样例32-5)才不需
要有$前缀。赋值可以是使用=(比如var1=27),可以是在read语句中,
也可以是在循环的头部( forvar2in123)。
在双引号 ""字符串中可以使用变量替换。我们称之为部分引用,有时候也称弱引
用。而使用单引号 ''引用时,变量只会作为字符串显示,变量替换不会发生。我
们称之为全引用,有时也称强引用。更多细节将在第五章讲解。
实际上, $variable这种写法是${variable}的简化形式。在某些特殊情况
下,使用$variable写法会造成语法错误,使用完整形式会更好(查看章节
10.2)。
样例4-1.变量赋值与替换
#!/bin/bash
#ex9.sh
1
4.1变量替换
64
#变量赋值与替换
a=375
hello=$a
#^^
#----------------------------------------------------
#初始化变量时,赋值号=的两侧绝不允许有空格出现。
#如果有空格会发生什么?
#"VARIABLE=value"
#^
#%脚本将会尝试运行带参数"=value""VARIABLE"命令。
#"VARIABLE=value"
#^
#%脚本将会尝试运行"value"命令,
#+同时设置环境变量"VARIABLE"""
#----------------------------------------------------
echohello#hello
#没有引用变量,"hello"只是一个字符串...
echo$hello#375
#^这是变量引用。
echo${hello}#375
#与上面的类似,变量引用。
#字符串内引用变量
echo"$hello"#375
echo"${hello}"#375
echo
hello="ABCD"
echo$hello#ABCD
echo"$hello"#ABCD
4.1变量替换
65
#正如我们所见,echo$helloecho"$hello"的结果不同。
#====================================
#字符串内引用变量将会保留变量的空白符。
#====================================
echo
echo'$hello'#$hello
#^^
#单引号会禁用掉(转义)变量引用,这导致"$"将以普通字符形式被解析。
#注意单双引号字符串引用效果的不同。
hello=#将其设置为空值
echo"\$hello(nullvalue)=$hello"#$hello(nullvalue)=
#注意
#将一个变量设置为空与删除(unset)它不同,尽管它们的表现形式相同。
#-----------------------------------------------
#使用空白符分隔,可以在一行内对多个变量进行赋值。
#但是这会降低程序的可读性,并且可能会导致部分程序不兼容的问题。
var1=21var2=22var3=$V3
echo
echo"var1=$var1var2=$var2var3=$var3"
#在一些老版本的shell中这样写可能会有问题。
#-----------------------------------------------
echo;echo
numbers="onetwothree"
#^^
other_numbers="123"
#^^
#如果变量中有空白符号,那么必须用引号进行引用。
#other_numbers=123#出错
4.1变量替换
66
echo"numbers=$numbers"
echo"other_numbers=$other_numbers"#other_numbers=123
#也可以转义空白符。
mixed_bag=2\---\Whatever
#^^使用\转义空格
echo"$mixed_bag"#2---Whatever
echo;echo
echo"uninitialized_variable=$uninitialized_variable"
#未初始化的变量是空值(null表示不含有任何值)
uninitialized_variable=#只声明而不初始化,等同于设为空值。
echo"uninitialized_variable=$uninitialized_variable"#仍旧为空
uninitialized_variable=23#设置变量
unsetuninitialized_variable#删除变量
echo"uninitialized_variable=$uninitialized_variable"
#uninitialized_variable=
#变量值为空
echo
exit0
4.1变量替换
67
一个未被赋值或未初始化的变量拥有空值(nullvalue)。注意:null值不等
同于0
if[-z"$unassigned"]
then
echo"\$unassignedisNULL."
fi#$unassignedisNULL.
在赋值前使用变量可能会导致错误。但在算术运算中使用未赋值变量是可行
的。
echo"$uninitialized"#空行
let"uninitialized+=5"#5
echo"$uninitialized"#5
#结论:
#一个未初始化的变量不含值(null),但在算术运算中会被作为0处理。
也可参考样例15-23
.实际上,变量名是被称作左值(lvalue),意思是出现在赋值表达式的左侧
的值,比如VARIABLE=23。变量值被称作右值(rvalue),意思是出现在赋
值表达式右侧的值,比如VAR2=$VARIABLE
事实上,变量名只是一个引用,一枚指针,指向实际存储数据内存地址的指
针。
1
4.1变量替换
68
4.2变量赋值
=
赋值操作符(在其前后没有空白符)。
不要混淆=-eq,后者用来进行比较而非赋值。
同时也要注意=根据使用场景既可作赋值操作符,也可作比较操作符。
样例4-2.变量赋值
4.2变量赋值
69
#!/bin/bash
#非引用形式变量
echo
#什么时候变量是非引用形式,即变量名前没有'$'符号的呢?
#当变量在被赋值而不是被引用时。
#赋值
a=879
echo"Thevalueof\"a\"is$a."
#使用'let'进行赋值
leta=16+5
echo"Thevalueof\"a\"isnow$a."
echo
#'for'循环中赋值(隐式赋值)
echo-n"Valuesof\"a\"intheloopare:"
forain78911
do
echo-n"$a"
done
echo
echo
#'read'表达式中(另一种赋值形式)
echo-n"Enter\"a\""
reada
echo"Thevalueof\"a\"isnow$a."
echo
exit0
样例4-3.奇妙的变量赋值
4.2变量赋值
70
#!/bin/bash
a=23#简单形式
echo$a
b=$a
echo$b
#来我们玩点炫的(命令替换)。
a=`echoHello!`#'echo'命令的结果赋值给'a'
echo$a
#注意在命令替换结构中包含感叹号(!)在命令行中使用将会失效,
#+因为它将会触发Bash的历史(history)机制。
#shell脚本内,Bash的历史机制默认关闭。
a=`ls-l`#'ls-l'命令的结果赋值给'a'
echo$a#不带引号引用,将会移除所有的制表符与分行符
echo
echo"$a"#引号引用变量将会保留空白符
#查看"引用"章节。
exit0
使用$(...)形式进行赋值(与反引号不同的新形式),与命令替换形式相似。
#摘自/etc/rc.d/rc.local
R=$(cat/etc/redhat-release)
arch=$(uname-m)
4.2变量赋值
71
4.3Bash变量是弱类型的
不同于许多其他编程语言,Bash并不区分变量的类型。本质上说,Bash变量是字
符串,但在某些情况下,Bash允许对变量进行算术运算和比较。决定因素则是变
量值是否只含有数字。
样例4-4.整数还是字符串?
#!/bin/bash
#int-or-string.sh
a=2334#整数。
let"a+=1"
echo"a=$a"#a=2335
echo#依旧是整数。
b=${a/23/BB}#"23"替换为"BB"
#$b变成了字符串。
echo"b=$b"#b=BB35
declare-ib#将其声明为整数并没有什么卵用。
echo"b=$b"#b=BB35
let"b+=1"#BB35+1
echo"b=$b"#b=1
echo#Bash认为字符串的"整数值"0
c=BB34
echo"c=$c"#c=BB34
d=${c/BB/23}#"BB"替换为"23"
#$d变为了一个整数。
echo"d=$d"#d=2334
let"d+=1"#2334+1
echo"d=$d"#d=2335
echo
#如果是空值会怎样呢?
4.3Bash弱类型变量
72
e=''#...也可以是e=""e=
echo"e=$e"#e=
let"e+=1"#空值是否允许进行算术运算?
echo"e=$e"#e=1
echo#空值变为了一个整数。
#如果时未声明的变量呢?
echo"f=$f"#f=
let"f+=1"#是否允许进行算术运算?
echo"f=$f"#f=1
echo#未声明变量变为了一个整数。
#
#然而……
let"f/=$undecl_var"#可以除以0么?
#let:f/=:syntaxerror:operandexpected(errortokenis"
")
#语法错误!在这里$undecl_var并没有被设置为0
#
#但是,仍旧……
let"f/=0"
#let:f/=0:divisionby0(errortokenis"0")
#预期之中。
#在执行算术运算时,Bash通常将其空值的整数值设为0
#但是不要做这种事情!
#因为这可能会导致一些意外的后果。
#结论:上面的结果都表明Bash中的变量是弱类型的。
exit$?
弱类型变量有利有弊。它可以使编程更加灵活、更加容易(给与你足够的想象空
间)。但它也同样的容易造成一些小错误,容易养成粗心大意的编程习惯。
为了减轻脚本持续跟踪变量类型的负担,Bash不允许变量声明。
4.3Bash弱类型变量
73
4.3Bash弱类型变量
74
4.4特殊的变量类型
局部变量
仅在代码块或函数中才可见的变量(参考函数章节的局部变量部分)。
环境变量
会影响用户及shell行为的变量。
一般情况下,每一个进程都有自己的环境environment),也就是一组
该进程可以访问到的变量。从这个意义上来说,shell表现出与其他进程一样的
行为。
每当shell启动时,都会创建出与其环境对应的shell环境变量。改变或增加shell
环境变量会使shell更新其自身的环境。子进程(由父进程执行产生)会继承父
进程的环境变量。
分配给环境变量的空间是有限的。创建过多环境变量或占用空间过大的环
境变量有可能会造成问题。
bash$eval"`seq10000|sed-e's/.*/exportvar&=ZZZZZZZZZZ
ZZZZ/'`"
bash$du
bash:/usr/bin/du:Argumentlisttoolong
注意,上面的"错误"已经在Linux内核版本号为2.6.23的系统中修复了。
(感谢StéphaneChazelas对此问题的解释并提供了上面的例子。)
如果在脚本中设置了环境变量,那么这些环境变量需要被导出,也就是通知脚本
所在的环境做出相应的更新。这个导出操作就是export命令。
4.4特殊变量类型
75
脚本只能将变量导出到子进程,即在这个脚本中所调用的命令或程序。在
命令行中调用的脚本不能够将变量回传给命令行环境,即子进程不能将变量回
传给父进程。
定义:子进程(childprocess)是由另一个进程,即其父进程(parent
process)所启动的子程序。
位置参数
从命令行中传递给脚本的参数 $0,$1,$2,$3...即命令行参数。
$0代表脚本名称, $1代表第一个参数, $2代表第二个, $3代表第三
个,以此类推 。在$9之后的参数必须被包含在大括号中,如${10},${11},
${12}
特殊变量$*$@代表所有位置参数。
样例4-5.位置参数
#!/bin/bash
#调用脚本时使用至少10个参数,例如
#./scriptname12345678910
MINPARAMS=10
echo
echo"Thenameofthisscriptis\"$0\"."
#附带./代表当前目录
echo"Thenameofthisscriptis\"`basename$0`\"."
#除去路径信息(查看'basename'
echo
if[-n"$1"]#测试变量是否存在
then
echo"Parameter#1is$1"#使用引号转义#
fi
if[-n"$2"]
1
2
4.4特殊变量类型
76
then
echo"Parameter#2is$2"
fi
if[-n"$3"]
then
echo"Parameter#3is$3"
fi
#...
if[-n"${10}"]#大于$9的参数必须被放在大括号中
then
echo"Parameter#10is${10}"
fi
echo"-----------------------------------"
echo"Allthecommand-lineparametersare:"$*""
if[$#-lt"$MINPARAMS"]
then
echo
echo"Thisscriptneedsatleast$MINPARAMScommand-lineargum
ents!"
fi
echo
exit0
在位置参数中使用大括号助记符提供了一种非常简单的方式来访问传入脚本的最后
一个参数。在其中会使用到间接引用。
4.4特殊变量类型
77
args=$##传入参数的个数
lastarg=${!args}
#这是$args的一种间接引用方式
#也可以使用:lastarg=${!#}(感谢ChrisMonson.)
#这是$#的一种间接引用方式。
#注意lastarg=${!$#}是无效的。
一些脚本能够根据调用时文件名的不同来执行不同的操作。要达到这样的效果,脚
本需要检测$0,也就是调用时的文件名 。同时,也必须存在指向这个脚本所有
别名的符号链接文件(symboliclinks)。详情查看样例16-2
如果一个脚本需要一个命令行参数但是在调用的时候却没有传入,那么这
将会造成一个空变量赋值。这通常不是我们想要的。一种避免的方法是,在使
用期望的位置参数时候,在赋值语句两侧添加一个额外的字符。
3
4.4特殊变量类型
78
variable1_=$1_#而不是variable1=$1
#使用这种方法可以在没有位置参数的情况下避免产生错误。
critical_argument01=$variable1_
#多余的字符可以被去掉,就像下面这样:
variable1=${variable1_/_/}
#仅仅当$variable1_是以下划线开头时候才会有一些副作用。
#这里使用了我们稍后会介绍的参数替换模板中的一种。
#(将替换模式设为空等价于删除。)
#更直接的处理方法就是先检测预期的位置参数是否被传入。
if[-z$1]
then
exit$E_MISSING_POS_PARAM
fi
#但是,正如FabinKreutz指出的,
#+上面的方法会有一些意想不到的副作用。
#更好的方法是使用参数替换:
#${1:-$DefaultVal}
#详情查看第十章操作变量的第二节变量替换
样例4-6.wh,whois域名查询
4.4特殊变量类型
79
#!/bin/bash
#ex18.sh
#在下面三个可选的服务器中进行whois域名查询:
#ripe.net,cw.net,radb.net
#将这个脚本重命名为'wh'后放在/usr/local/bin目录下
#这个脚本需要进行符号链接:
#ln-s/usr/local/bin/wh/usr/local/bin/wh-ripe
#ln-s/usr/local/bin/wh/usr/local/bin/wh-apnic
#ln-s/usr/local/bin/wh/usr/local/bin/wh-tucows
E_NOARGS=75
if[-z"$1"]
then
echo"Usage:`basename$0`[domain-name]"
exit$E_NOARGS
fi
#检查脚本名,访问对应服务器进行查询。
case`basename$0`in#也可以写:case${0##*/}in
"wh")whois$1@whois.tucows.com;;
"wh-ripe")whois$1@whois.ripe.net;;
"wh-apnic")whois$1@whois.apnic.net;;
"wh-cw")whois$1@whois.cw.net;;
*)echo"Usage:`basename$0`[domain-name]";;
esac
exit$?
使用shift命令可以将全体位置参数向左移一位,重新赋值。
$1<---$2, $2<---$3, $3<---$4,以此类推。
4.4特殊变量类型
80
原先的$1将会消失,而$0(脚本名称)不会有任何改变。如果你在脚本中使
用了大量的位置参数, shift可以让你不使用{大括号}助记法也可以访问超过10
个的位置参数。
样例4-7.使用shift命令
#!/bin/bash
#shft.sh:使用`shift`命令步进访问所有的位置参数。
#将这个脚本命名为shft.sh,然后在调用时跟上一些参数。
#例如:
#shshft.shabcdef83barndoor
until[-z"$1"]#直到访问完所有的参数
do
echo-n"$1"
shift
done
echo#换行。
#那些被访问完的参数又会怎样呢?
echo"$2"
#什么都不会被打印出来。
#$2被移动到$1且没有$3时,$2将会保持空。
#因此shift是移动参数而非复制参数。
exit
#可以参考echo-params.sh脚本,在不使用shift命令的情况下,
#+步进访问所有位置参数。
shift命令也可以带一个参数来指明一次移动多少位。
4.4特殊变量类型
81
#!/bin/bash
#shift-past.sh
shift3#移动3位。
#n=3;shift$n效果相同。
echo"$1"
exit0
#========================#
$shshift-past.sh12345
4
#但是就像EleniFragkiadaki指出的那样,
#如果尝试将位置参数($#)传给'shift'
#将会导致脚本错误的结束,同时位置参数也不会发送改变。
#这也许是因为陷入了一个死循环...
#比如:
#until[-z"$1"]
#do
#echo-n"$1"
#shift20#如果少于20个位置参数,
#done#+那么循环将永远不会结束。
#
#当你不确定是否有这么多的参数时,你可以加入一个测试:
#shift20||break
#^^^^^^^^
使用shift命令同给函数传参相类似。详情查看样例36-18
.函数同样也可以接受与使用位置参数。
1
2
4.4特殊变量类型
82
.是调用脚本的进程设置了$0参数。就是脚本的文件名。详情可以查看
execv的使用手册。
在命令行中,$0shell的名称。
bash$echo$0
bash
tcsh%echo$0
tcsh
.如果脚本被引用(sourced)执行或者被链接(symlinked)执行时会失效。
安全的方法是检测变量$BASH_Source
2
3
4.4特殊变量类型
83
第五章引用
本章目录
5.1引用变量
5.2转义
引用就是将一个字符串用引号括起来。这样做是为了保护Shell/Shell脚本中被重新
解释过或带扩展功能的特殊字符(如果一个字符带有其特殊意义而不仅仅是字面量
的话,这个字符就能称为特殊字符。比如星号“*”就能表示正则表达式中的一个
配符)。
bash$ls-l[Vv]*
-rw-rw-r--1bozobozo324Apr215:05VIEWDATA.BAT
-rw-rw-r--1bozobozo507May414:25vartrace.sh
-rw-rw-r--1bozobozo539Apr1417:11viewdata.sh
bash$ls-l'[Vv]*'
ls:[Vv]*:Nosuchfileordirectory
可以看到,提示不存在该文件。这里的 '[Vv]*被当成了文件名。在日常沟
通和写作中,当我们引用一个短语的时候,我们会将它单独隔开并赋予它特殊
的意义,而在bash脚本中,当我们引用一个字符串,意味着保留它的字面量。
很多程序和公用代码会展开被引用字符串中的特殊字符。引用的一个重用用途是保
Shell中的命令行参数,但仍然允许调用的程序扩展它。
bash$grep'[Ff]irst'*.txt
file1.txt:Thisisthefirstlineoffile1.txt.
file2.txt:ThisistheFirstlineoffile2.txt.
在所有.txt文件中找出包含first或者First字符串的行
注意,不加引号的grep[Ff]irst*.txtBash下也同样有效。1
5.引用
84
引用也可以控制echo命令的断行符。
bash$echo$(ls-l)
total8-rw-rw-r--1bobo13Aug2112:57t.sh-rw-rw-r--1bo
bo78Aug2112:57u.sh
bash$echo"$(ls-l)"
total8
-rw-rw-r--1bobo13Aug2112:57t.sh
-rw-rw-r--1bobo78Aug2112:57u.sh
.前提是当前目录下有文件名为Firstfirst的文件。这也是使用引用的另一个
原因。(感谢HaraldKoenig指出了这一点)
1
5.引用
85
5.1引用变量
引用变量时,通常建议将变量包含在双引号中。因为这样可以防止除$`
(反引号)和 \(转义符)之外的其他特殊字符被重新解释。 在双引号中仍然
可以使用 $引用变量( "$variable"),也就是将变量名替换为变量值(详情
查看样例4-1)。
使用双引号可以防止字符串被分割。 即使参数中拥有很多空白分隔符,被包在双
引号中后依旧是算作单一字符。
List="onetwothree"
forain$List#空白符将变量分成几个部分。
do
echo"$a"
done
#one
#two
#three
echo"---"
forain"$List"#在单一变量中保留所有空格。
do#^^
echo"$a"
done
#onetwothree
下面是一个更加复杂的例子:
1
2
5.1引用变量
86
variable1="avariablecontainingfivewords"
COMMANDThisis$variable1#带上7个参数执行COMMAND命令:
#"This""is""a""variable""containing""five""words"
COMMAND"Thisis$variable1"#带上1个参数执行COMMAND命令:
#"Thisisavariablecontainingfivewords"
variable2=""#空值。
COMMAND$variable2$variable2$variable2
#不带参数执行COMMAND命令。
COMMAND"$variable2""$variable2""$variable2"
#带上3个参数执行COMMAND命令。
COMMAND"$variable2$variable2$variable2"
#带上1个参数执行COMMAND命令(2空格)。
#感谢StéphaneChazelas
当字符分割或者保留空白符出现问题时,才需要在 echo语句中用双引号
包住参数。
样例5-1.输出一些奇怪的变量
#!/bin/bash
#weirdvars.sh:输出一些奇怪的变量
echo
var="'(]\\{}\$\""
echo$var#'(]\{}$"
echo"$var"#'(]\{}$"没有任何区别。
echo
IFS='\'
echo$var#'(]{}$"\被转换成了空格,为什么?
echo"$var"#'(]\{}$"
5.1引用变量
87
#上面的例子由StephaneChazelas提供。
echo
var2="\\\\\""
echo$var2#"
echo"$var2"#\\"
echo
#但是...var2="\\\\""不是合法的语句,为什么?
var3='\\\\'
echo"$var3"#\\\\
#强引用是可以的。
#************************************************************#
#就像第一个例子展示的那样,嵌套引用是允许的。
echo"$(echo'"')"#"
#^^
#在有些时候这种方法非常有用。
var1="Twobits"
echo"\$var1="$var1""#$var1=Twobits
#^^
#或者,可以像ChrisHiestand指出的那样:
if[["$(du"$My_File1")"-gt"$(du"$My_File2")"]]
#^^^^^^^^
then
...
fi
#************************************************************#
单引号('')与双引号类似,但是在单引号中不能引用变量,因为$不再具有特
殊含义。在单引号中,除 '之外的所有特殊字符都将会被直接按照字面意思解
释。可以认为单引号(全引用)是双引号(部分引用)的一种更严格的形式。
5.1引用变量
88
因为在单引号中转义符(\)都已经按照字面意思解释了,因此尝试在单引
号中包含单引号将不会产生你所预期的结果。
echo"Whycan'tIwrite'sbetweensinglequotes"
echo
#可以采取迂回的方式。
echo'Whycan'\''tIwrite'"'"'sbetweensinglequotes'
#|-------||----------||-----------------------|
#由三个单引号引用的字符串,再加上转义以及双引号包住的单引号组成。
#感谢StéphaneChazelas提供的例子。
1
5.1引用变量
89
.在命令行里,如果双引号包含了"!"将会产生错误。这是因为shell将其解释
为查看历史命令。而在脚本中,因为历史机制已经被关闭,所以不会产生这个
问题。
我们更加需要注意的是在双引号中\的反常行为,尤其是在使用echo-e
命令时。
bash$echohello\\!
hello!
bash$echo"hello\\!"
hello\\!
bash$echo\\
>
bash$echo"\\"
>
bash$echo\a
a
bash$echo"\a"
\a
bash$echox\ty
xty
bash$echo"x\ty"
x\ty
bash$echo-ex\ty
xty
bash$echo-e"x\ty"
xy
echo后的双引号中一般会转义\。并且echo-e会将"\t"解释
成制表符。
(感谢WaynePollock提出这些;感谢GeoffLeeDanielBarclay对此做出
的解释。)
1
2
5.1引用变量
90
.字符分割(wordsplitting)在本文中的意思是指将一个字符串分割成独立
的、离散的变量。
2
5.1引用变量
91
5.2转义
转义是一种引用单字符的方法。通过在特殊字符前加上转义符\来告诉shell按照
字面意思去解释这个字符。
需要注意的是,在一些特定的命令和工具,比如echosed中,转
义字符通常会起到相反的效果,即可能会使得那些字符产生特殊含义。
echosed命令中,转义字符的特殊含义
\n
换行(linefeed)。
\r
回车(carriagereturn)。
\t
水平制表符。
\v
垂直制表符。
\b
退格。
\a
警报、响铃或闪烁。
5.2转义
92
\0xx
ASCII码的八进制形式,等价于0nn,其中nn是数字。
$'...'字符串扩展结构中可以通过转义八进制或十六进制的ASCII
码形式给变量赋值,比如quote=$'\042'
样例5-2.转义字符
#!/bin/bash
#escaped.sh:转义字符
##############################################
###首先让我们先看一下转义字符的基本用法。###
##############################################
#转义新的一行。
#------------
echo""
echo"Thiswillprint
astwolines."
#Thiswillprint
#astwolines.
echo"Thiswillprint\
asoneline."
#Thiswillprintasoneline.
echo;echo
echo"============="
echo"\v\v\v\v"#按字面意思打印\v\v\v\v
#使用echo命令的-e选项来打印转义字符。
echo"============="
echo"VERTICALTABS"
echo-e"\v\v\v\v"#打印四个垂直制表符。
5.2转义
93
echo"=============="
echo"QUOTATIONMARK"
echo-e"\042"#打印"(引号,八进制ASCII码为42)。
echo"=============="
#使用$'\X'这样的形式后可以不需要加-e选项。
echo;echo"NEWLINEand(maybe)BEEP"
echo$'\n'#新的一行。
echo$'\a'#警报(响铃)。
#根据不同的终端版本,也可能是闪屏。
#我们之前介绍了$'\nnn'字符串扩展,而现在我们要看到的是...
#============================================#
#Bash第二个版本开始的$'\nnn'字符串扩展结构。
#============================================#
echo"Introducingthe\$\'...\'string-expansionconstruct..
."
echo"...featuringmorequotationmarks."
echo$'\t\042\t'#在制表符之间的引号。
#需要注意的是'\nnn'是一个八进制的值。
#字符串扩展同样适用于十六进制的值,格式是$'\xhhh'
echo$'\t\x22\t'#在制表符之间的引号。
#感谢GregKeraunen指出这些。
#在早期的Bash版本中允许使用'\x022'这样的形式。
echo
#ASCII码字符赋值给变量。
#-----------------------
quote=$'\042'#"赋值给变量。
echo"$quoteQuotedstring$quoteandthisliesoutsidethequot
5.2转义
94
es."
echo
#连接多个ASCII码字符给变量。
triple_underline=$'\137\137\137'#137'_'ASCII码的八进制形式
echo"$triple_underlineUNDERLINE$triple_underline"
echo
ABC=$'\101\102\103\010'#101102103A,B,C
#ASCII码的八进制形式。
echo$ABC
echo
escape=$'\033'#033ESC的八进制形式
echo"\"escape\"echoesan$escape"
#没有可见输出
echo
exit0
下面是一个更加复杂的例子:
样例5-3.检测键盘输入
#!/bin/bash
#作者:SigurdSolaas,作于2011420
#授权在《高级Bash脚本编程指南》中使用。
#需要Bash版本高于4.2
key="novalueyet"
whiletrue;do
clear
echo"BashExtraKeysDemo.Keystotry:"
echo
echo"*Insert,Delete,Home,End,Page_UpandPage_Down"
echo"*Thefourarrowkeys"
5.2转义
95
echo"*Tab,enter,escape,andspacekey"
echo"*Theletterandnumberkeys,etc."
echo
echo"d=showdate/time"
echo"q=quit"
echo"================================"
echo
#将独立的Home键值转换为数字7上的Home键值:
if["$key"=$'\x1b\x4f\x48'];then
key=$'\x1b\x5b\x31\x7e'
#引用字符扩展结构。
fi
#将独立的End键值转换为数字1上的End键值:
if["$key"=$'\x1b\x4f\x46'];then
key=$'\x1b\x5b\x34\x7e'
fi
case"$key"in
$'\x1b\x5b\x32\x7e')#插入
echoInsertKey
;;
$'\x1b\x5b\x33\x7e')#删除
echoDeleteKey
;;
$'\x1b\x5b\x31\x7e')#数字7上的Home
echoHomeKey
;;
$'\x1b\x5b\x34\x7e')#数字1上的End
echoEndKey
;;
$'\x1b\x5b\x35\x7e')#上翻页
echoPage_Up
;;
$'\x1b\x5b\x36\x7e')#下翻页
echoPage_Down
;;
$'\x1b\x5b\x41')#上箭头
echoUparrow
5.2转义
96
;;
$'\x1b\x5b\x42')#下箭头
echoDownarrow
;;
$'\x1b\x5b\x43')#右箭头
echoRightarrow
;;
$'\x1b\x5b\x44')#左箭头
echoLeftarrow
;;
$'\x09')#制表符
echoTabKey
;;
$'\x0a')#回车
echoEnterKey
;;
$'\x1b')#ESC
echoEscapeKey
;;
$'\x20')#空格
echoSpaceKey
;;
d)
date
;;
q)
echoTimetoquit...
echo
exit0
;;
*)
echoYourpressed:\'"$key"\'
;;
esac
echo
echo"================================"
unsetK1K2K3
read-s-N1-p"Pressakey:"
5.2转义
97
K1="$REPLY"
read-s-N2-t0.001
K2="$REPLY"
read-s-N1-t0.001
K3="$REPLY"
key="$K1$K2$K3"
done
exit$?
还可以查看样例37-1
\"
转义引号,指代自身。
echo"Hello"#Hello
echo"\"Hello\"...hesaid."#"Hello"...hesaid.
\$
转义美元符号(跟在\\$后的变量名将不会被引用)。
echo"\$variable01"#$variable01
echo"Thebookcost\$7.98."#Thebookcost$7.98.
\\
转义反斜杠,指代自身。
5.2转义
98
echo"\\"#结果是\
#然而...
echo"\"#在命令行中会出现第二行并提示输入。
#在脚本中会出错。
#但是...
echo'\'#结果是\
根据转义符所在的上下文(强引用、弱引用,命令替换或者在here
document)的不同,它的行为也会有所不同。
5.2转义
99
#简单转义与引用
echo\z#z
echo\\z#\z
echo'\z'#\z
ehco'\\z'#\\z
echo"\z"#\z
echo"\\z"#\z
#命令替换
echo`echo\z`#z
echo`echo\\z`#z
echo`echo\\\z`#\z
echo`echo\\\\z`#\z
echo`echo\\\\\\z`#\z
echo`echo\\\\\\\z`#\\z
echo`echo"\z"`#\z
echo`echo"\\z"`#\z
#HereDocument
cat<<EOF
\z
EOF#\z
cat<<EOF
\\z
EOF#\z
#以上例子由StéphaneChazelas提供。
含有转义字符的字符串可以赋值给变量,但是仅仅将单一的转义符赋值给变量
是不可行的。
5.2转义
100
variable=\
echo"$variable"
#这样做会报如下错误:
#tesh.sh::commandnotfound
#单独的转义符不能够赋值给变量。
#
#事实上,"\"转义了换行,实际效果是:
#+variable=echo"$variable"
#+这是一个非法的赋值方式。
variable=\
23skidoo
echo"$variable"#23skidoo
#因为第二行是一个合法的赋值,因此不会报错。
variable=\
#\^转义符后有一个空格
echo"$variable"#空格
variable=\\
echo"$variable"#\
variable=\\\
echo"$variable"
#这样做会报如下错误:
#tesh.sh:\:commandnotfound
#
#第一个转义符转转义了第二个,但是第三个转义符仍旧转义的是换行,
#+跟开始的那个例子一样,因此会报错。
variable=\\\\
echo"$variable"#\\
#第二个和第四个转义符被转义了,因此可行。
转义空格能够避免在命令参数列表中的字符分割问题。
5.2转义
101
file_list="/bin/cat/bin/gzip/bin/more/usr/bin/less/usr/bin/e
macs-20.7"
#将一系列文件作为命令的参数。
#增加两个文件到列表中,并且列出整个表。
ls-l/usr/X11R6/bin/xsetroot/sbin/dump$file_list
echo"----------------------------------------------------------
---------------"
#如果我们转义了这些空格会怎样?
ls-l/usr/X11R6/bin/xsetroot\/sbin/dump\$file_list
#错误:因为转义了两个空格,因此前三个文件被连接成了一个参数传递给了'ls-l'
转义符也提供一种可以撰写多行命令的方式。通常,每一行是一个命令,但是转义
换行后命令就可以在下一行继续撰写。
(cd/source/directory&&tarcf-.)|\
(cd/dest/directory&&tarxpvf-)
#回顾AlanCox的目录树拷贝命令,但是把它拆成了两行。
#或者你也可以:
tarcf--C/source/directory.|
tarxpvf--C/dest/directory
#可以看下方的注释。
#(感谢StéphaneChazelas。)
在脚本中,如果以"|"管道作为一行的结束字符,那么不需要加转义符\
可以写多行命令。但是一个好的编程习惯就是在写多行命令的事后,无论什么
情况都要在行尾加上转义符\
5.2转义
102
echo"foo
bar"
#foo
#bar
echo
echo'foo
bar'#没有区别。
#foo
#bar
echo
echofoo\
bar#转义换行。
#foobar
echo
echo"foo\
bar"#没有区别,在弱引用中,\转义符仍旧转义了换行。
#foobar
echo
echo'foo\
bar'#在强引用中,\就按照字面意思来解释了。
#foo\
#bar
#StéphaneChazelas提供的例子。
5.2转义
103
第六章退出与退出状态
Bourneshell里存在不明确之处,但人们也会使用它们。
——ChatRamey
C程序类似, exit命令被用来结束脚本。同时,它也会返回一个值,返回值可
以被交给父进程。
每个命令都会返回一个退出状态(exitstatus),有时也叫做返回状态(return
status)或退出码(exitcode)。命令执行成功返回0,如果返回一个非0值,通常
情况下会被认为是一个错误代码。一个运行状态良好的UNIX命令、程序和工具在正
常执行退出后都会返回一个0的退出码,当然也有例外。
同样地,脚本中的函数和脚本本身也会返回一个退出状态。在脚本或者脚本函数中
执行的最后的命令会决定它们的退出状态。在脚本中, exitnnn命令将会把nnn
退出状态码传递给shellnnn必须是0-255之间的整型数)。
当一个脚本以不带参数的exit来结束时,脚本的退出状态由脚本最后执
行命令决定( exit命令之前)。
#!/bin/bash
COMMAND_1
...
COMMAND_LAST
#将以最后的命令来决定退出状态
exit
exitexit$?以及省略exit效果等同。
6.退出与退出状态
104
#!/bin/bash
COMMAND_1
...
COMMAND_LAST
#将以最后的命令来决定退出状态
exit$?
#!/bin/bash
COMMAND_1
...
COMMAND_LAST
#将以最后的命令来决定退出状态
$?读取上一个执行命令的退出状态。在一个函数返回后, $?给出函数最后执
行的那条命令的退出状态。这就是Bash函数的"返回值"
管道执行后, $?给出最后执行的那条命令的退出状态。
在脚本终止后,命令行下键入 $?会给出脚本的退出状态,即在脚本中最后一条命
令执行后的退出状态。一般情况下,0为成功,1-255为失败。
样例6-1.退出与退出状态
1
6.退出与退出状态
105
#!/bin/bash
echohello
echo$?#返回值为0,因为执行成功。
lskdf#不认识的命令。
echo$?#返回非0值,因为失败了。
echo
exit113#将返回113shell
#为了验证这些,在脚本结束的地方使用“echo$?”
#按照惯例,'exit0'意味着执行成功,
#+0意味着错误或者异常情况。
#查看附录章节退出码的特殊含义
$?对于测试脚本中的命令的执行结果特别有用(查看样例16-35和样例16-
20)。
逻辑非操作符!将会反转测试或命令的结果,并且这将会影响退出状态。
样例6-2.否定一个条件使用!
6.退出与退出状态
106
true#trueshell内建命令。
echo"exitstatusof\"true\"=$?"#0
!true
echo"exitstatusof\"!true\"=$?"#1
#注意在命令之间的"!"需要一个空格。
#!true将导致一个"commandnotfound"错误。
#
#如果一个命令以'!'开头,那么将调用Bash的历史机制,显示这个命令被使用的历
史。
true
!true
#这次就没有错误了,但是同样也没有反转。
#它不过是重复之前的命令(true)。
#============================================================#
#_pipe_前使用!将改变返回的退出状态。
ls|bogus_command#bash:bogus_command:commandnotfound
echo$?#127
>
!ls|bogus_command#bash:bogus_command:commandnotfound
echo$?#0
#注意!不会改变管道的执行。
#只改变退出状态。
#============================================================#
>
#感谢StéphaneChazelasKristopherNewsome
某些特定的退出码具有一些特定的保留含义,用户不应该在自己的脚本中
重新定义它们。
.在函数没有用return来结束这个函数的情况下。
1
6.退出与退出状态
107
第七章测试
本章目录
7.1测试结构
7.2文件测试操作
7.3其他比较操作
7.4嵌套if/then条件测试
7.5牛刀小试
每一个完备的程序设计语言都可以对一个条件进行判断,然后根据判断结果执行相
应的指令。Bash拥有test命令,双方括号双圆括号测试操作符以及
if/then测试结构。
7.测试
108
7.1测试结构
if/then结构是用来检测一系列命令的退出状态是否为0(按UNIX惯例,
退出码0表示命令执行成功),如果为0,则执行接下来的一个或多个命令。
测试结构会使用一个特殊的命令[(参看特殊字符章节左方括号)。等同于
test命令,它是一个内建命令,写法更加简洁高效。该命令将其参数视为
比较表达式或文件测试,以比较结果作为其退出状态码返回(0为真,1
假)。
Bash2.02版本中引入了扩展测试命令[[...]],它提供了一种与其他语
言语法更为相似的方式进行比较操作。注意,[[是一个关键字而非一个命
令。
Bash[[$a-lt$b]]视为一整条语句,执行并返回退出状态。
结构((...))let...根据其执行的算术表达式的结果决定退出状
态码。这样的算术扩展结构可以用来进行数值比较
7.1测试结构
109
((0&&1))#逻辑与
echo$?#1***
#然后...
let"num=((0&&1))"
echo$num#0
#然而...
let"num=((0&&1))"
echo$?#1***
((200||11))#逻辑或
echo$?#0***
#...
let"num=((200||11))"
echo$num#1
let"num=((200||11))"
echo$?#0***
((200|11))#按位或
echo$?#0***
#...
let"num=((200|11))"
echo$num#203
let"num=((200|11))"
echo$?#0***
#"let"结构的退出状态与双括号算术扩展的退出状态是相同的。
注意,双括号算术扩展表达式的退出状态码不是一个错误的值。算术表达式为
0,返回1;算术表达式不为0,返回0
var=-2&&((var+=2))
echo$?#1
var=-2&&((var+=2))&&echo$var
#并不会输出$var,因为((var+=2))的状态码
1
7.1测试结构
110
if不仅可以用来测试括号内的条件表达式,还可以用来测试其他任何命
令。
ifcmpab&>/dev/null#消去输出结果
thenecho"Filesaandbareidentical."
elseecho"Filesaandbdiffer."
fi
#下面介绍一个非常实用的“if-grep"结构:
#-----------------------------------
ifgrep-qBashfile
thenecho"FilecontainsatleastoneoccurrenceofBash."
fi
word=Linux
letter_sequence=inu
ifecho"$word"|grep-q"$letter_sequence"
#使用-q选项消去grep的输出结果
then
echo"$letter_sequencefoundin"$word"
else
echo"$letter_sequencenotfoundin$word"
fi
ifCOMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
thenecho"Commandsucceed."
elseecho"Commandfailed."
fi
感谢StéphaneChazelas提供了后两个例子。
样例7-1.什么才是真?
#!/bin/bash
#提示:
#如果你不确定某个表达式的布尔值,可以用if结构进行测试。
7.1测试结构
111
echo
echo"Testing\"0\""
if[0]
then
echo"0istrue."
else
echo"0isfalse."
fi#0为真。
echo
echo"Testing\"1\""
if[1]
then
echo"1istrue."
else
echo"1isfalse."
fi#1为真。
echo
echo"Testing\"-1\""
if[-1]
then
echo"-1istrue."
else
echo"-1isfalse."
fi#-1为真。
echo
echo"Testing\"NULL\""
if[]#NULL,
then
echo"NULListrue."
else
echo"NULLisfalse."
fi#NULL为假。
7.1测试结构
112
echo
echo"Testing\"xyz\""
if[xyz]#字符串
then
echo"Randomstringistrue."
else
echo"Randomstringisfalse."
fi#随机字符串为真。
echo
echo"Testing\"$xyz\""
if[$xyz]#原意是测试$xyz是否为空,但是
#现在$xyz只是一个没有初始化的变量。
then
echo"Uninitializedvariableistrue."
else
echo"Uninitializedvariableisflase."
fi#未初始化变量含有null空值,为假。
echo
echo"Testing\"-n\$xyz\""
if[-n"$xyz"]#更加准确的写法。
then
echo"Uninitializedvariableistrue."
else
echo"Uninitializedvariableisfalse."
fi#未初始化变量为假。
echo
xyz=#初始化为空。
echo"Testing\"-n\$xyz\""
if[-n"$xyz"]
then
echo"Nullvariableistrue."
7.1测试结构
113
else
echo"Nullvariableisfalse."
fi#空变量为假。
echo
#什么时候"false"为真?
echo"Testing\"false\""
if["false"]#看起来"false"只是一个字符串
then
echo"\"false\"istrue."#+测试结果为真。
else
echo"\"false\"isfalse."
fi#"false"为真。
echo
echo"Testing\"\$false\""#未初始化的变量。
if["$false"]
then
echo"\"\$false\"istrue."
else
echo"\"\$false\"isfalse."
fi#"$false"为假。
#得到了我们想要的结果。
#如果测试空变量"$true"会有什么样的结果?
echo
exit0
练习:理解样例7-1
7.1测试结构
114
if[condition-true]
then
command1
command2
...
else#如果测试条件为假,则执行else后面的代码段
command3
command4
...
fi
如果把ifthen写在同一行时,则必须在if语句后加上一个分号来
结束语句。因为ifthen都是关键字。以关键字(或者命令)开头的语
句,必须先结束该语句(分号;),才能执行下一条语句。
if[-x"$filename"];then
Elseifelif
elif
elifelseif的缩写。可以把多个if/then语句连到外边去,更加简洁
明了。
if[condition1]
then
command1
command2
command3
elif[condition2]
#等价于elseif
then
command4
command5
else
default-command
fi
7.1测试结构
115
iftestcondition-true完全等价于if[condition-true]。当语句开
始执行时,左括号[是作为调用test命令的标记 ,而右括号则不严格要
求,但在新版本的Bash里,右括号必须补上。
test命令是Bash内建命令,可以用来检测文件类型和比较字符串。在
Bash脚本中, test不调用sh-utils包下的文件/usr/bin/test。同
样, [也不会调用链接到/usr/bin/test/usr/bin/[文件。
bash$typetest
testisashellbuiltin
bash$type'['
[isashellbuiltin
bash$type'[['
[[isashellkeyword
bash$type']]'
]]isashellkeyword
bash$type']'
bash:type:]:notfound
如果你想在Bash脚本中使用/usr/bin/test,那你必须把路径写全。
样例7-2. test/usr/bin/test[]/usr/bin/[的等价性
#!/bin/bash
echo
iftest-z"$1"
then
echo"Nocommand-linearguments."
else
echo"Firstcommand-lineargumentis$1."
fi
echo
if/usr/bin/test-z"$1"#等价于内建命令"test"
#^^^^^^^^^^^^^#指定全路径
then
1
7.1测试结构
116
echo"Nocommand-linearguments."
else
echo"Firstcommand-lineargumentis$1."
fi
echo
if[-z"$1"]#功能和上面的代码相同。
#if[-z"$1"理论上可行,但是Bash会提示缺失右括号
then
echo"Nocommand-linearguments."
else
echo"Firstcommand-lineargumentis$1."
fi
echo
if/usr/bin/[-z"$1"]#功能和上面的代码相同。
#if/usr/bin/[-z"$1"#理论上可行,但是会报错
##已经在Bash3.x版本被修复了
then
echo"Nocommand-linearguments."
else
echo"Firstcommand-lineargumentis$1."
fi
echo
exit0
Bash里, [[]]是比[]更加通用的写法。其作为扩展 test命令从
ksh88中被继承了过来。
[[]]中不会进行文件名扩展或字符串分割,但是可以进行参数扩展和命
令替换。
7.1测试结构
117
file=/etc/passwd
if[[-e$file]]
then
echo"Passwordfileexists."
fi
使用[[...]]代替[...]可以避免很多逻辑错误。比如可以在[[]]中使用
&&||<>操作符,而在[]中使用则会报错。
[[]]中会自动执行八进制和十六进制的进制转换操作。
7.1测试结构
118
#[[八进制和十六进制进制转换]]
#感谢MoritzGronbach提出。
decimal=15
octal=017#=15(十进制)
hex=0x0f#=15(十进制)
if["$decimal"-eq"$octal"]
then
echo"$decimalequals$octal"
else
echo"$decimalisnotequalto$octal"#15不等于017
fi#在单括号[]之间不会进行进制转换。
if[["$decimal"-eq"$octal"]]
then
echo"$decimalequals$octal"#15等于017
else
echo"$decimalisnotequalto$octal"
fi#在双括号[[]]之间会进行进制转换。
if[["$decimal"-eq"$hex"]]
then
echo"$decimalequals$hex"#15等于0x0f
else
echo"$decimalisnotequalto$hex"
fi#十六进制也可以进行转换。
语法上并不严格要求在if之后一定要写test命令或者测试结构( []
[[]])。
7.1测试结构
119
dir=/home/bozo
ifcd"$dir"2>/dev/null;then#"2>/dev/null"重定向消去错误输出。
echo"Nowin$dir."
else
echo"Can'tchangeto$dir."
fi
ifCOMMAND的退出状态就是 COMMAND的退出状态。
同样的,测试括号也不一定需要与if一起使用。其可以同列表结构结合而不需
if
var1=20
var2=22
["$var1"-ne"$var2"]&&echo"$var1isnotequalto$var2"
home=/home/bozo
[-d"$home"]||echo"$homedirectorydoesnotexist."
(())结构扩展和执行算术表达式。如果执行结果为0,其返回的退出状态码
1(假)。非0表达式返回的退出状态为0(真)。这与上述所使用的test
[]结构形成鲜明的对比。
样例7-3.使用(())进行算术测试
#!/bin/bash
#arith-tests.sh
#算术测试。
#((...))结构执行并测试算术表达式。
#[...]结构的退出状态正好相反。
((0))
echo"Exitstatusof\"((0))\"is$?."#1
((1))
7.1测试结构
120
echo"Exitstatusof\"((1))\"is$?."#0
((5>4))#
echo"Exitstatusof\"((5>4))\"is$?."#0
((5>9))#
echo"Exitstatusof\"((5>9))\"is$?."#1
((5==5))#
echo"Exitstatusof\"((5==5))\"is$?."#0
#((5=5))会报错。
((5-5))#0
echo"Exitstatusof\"((5-5))\"is$?."#1
((5/4))#合法
echo"Exitstatusof\"((5/4))\"is$?."#0
((1/2))#结果小于1
echo"Exitstatusof\"((1/2))\"is$?."#舍入至0
#1
((1/0))2>/dev/null#0,非法
#^^^^^^^^^^^
echo"Exitstatusof\"((1/0))\"is$?."#1
#"2>/dev/null"的作用是什么?
#如果将其移除会发生什么?
#尝试移除这条语句并重新执行脚本。
#=======================================#
#((...))if-then中也非常有用
var1=5
var2=4
if((var1>var2))
then#^^注意不是$var1$var2,为什么?
echo"$var1isgreaterthen$var2"
7.1测试结构
121
fi#5大于4
exit0
.标记是一个具有特殊意义(元语义)的符号或者短字符串。在Bash里像
[.(点命令)这样的标记可以扩展成关键字和命令。
1
7.1测试结构
122
7.2文件测试操作
下列每一个 test选项在满足条件时,返回0(真)。
-e
检测文件是否存在
-a
检测文件是否存在
等价于-e。不推荐使用,已被弃用 。
-f
文件是常规文件(regularfile),而非目录或设备文件
-s
文件大小不为0
-d
文件是一个目录
-b
文件是一个块设备
-c
文件是一个字符设备
1
7.2文件测试操作
123
device0="/dev/sda2"#/(根目录)
if[-b"$device0"]
then
echo"$device0isablockdevice."
fi
#/dev/sda2是一个块设备。
device1="/dev/ttyS1"#PCMCIA调制解调卡
if[-c"$device1"]
then
echo"$device1isacharacterdevice."
fi
#/dev/ttyS1是一个字符设备。
-p
文件是一个管道设备
functionshow_input_type()
{
[-p/dev/fd/0]&&echoPIPE||echoSTDIN
}
show_input_type"Input"#STDIN
echo"Input"|show_input_type#PIPE
#这个例子由CarlAnderson提供。
-h
文件是一个符号链接
7.2文件测试操作
124
-L
文件是一个符号链接
-S
文件是一个套接字
-t
文件(文件描述符)与终端设备关联
该选项通常被用于测试脚本中的stdin[-t0]stdout[-t1]是否
为终端设备。
-r
该文件对执行测试的用户可读
-w
该文件对执行测试的用户可写
-x
该文件可被执行测试的用户所执行
-g
文件或目录设置了set-group-id sgid标志
如果一个目录设置了sgid标志,那么在该目录中所有的新建文件的权限组都归
属于该目录的权限组,而非文件创建者的权限组。该标志对共享文件夹很有用。
-u
7.2文件测试操作
125
文件设置了set-user-id suid标志。
一个属于root的可执行文件设置了suid标志后,即使是一个普通用户执行也拥
root权限 。对需要访问硬件设备的可执行文件(例如pppdcdrecord
很有用。如果没有suid标志,这些可执行文件就不能被非root用户所调用了。
-rwsr-xr-t1root178236Oct22000/usr/sbin/pppd
设置了suid标志后,在权限中会显示s
-k
设置了粘滞位(stickybit)
标志粘滞位是一种特殊的文件权限。如果文件设置了粘滞位,那么该文件将会被存
储在高速缓存中以便快速访问 。如果目录设置了该标记,那么它将会对目录的写
权限进行限制,目录中只有文件的拥有者可以修改或删除文件。设置标记后你可以
在权限中看到t
drwxrwxrwt7root1024May1921:26tmp/
如果一个用户不是设置了粘滞位目录的拥有者,但对该目录有写权限,那么他仅仅
可以删除目录中他所拥有的文件。这可以防止用户不经意间删除或修改其他人的文
件,例如/tmp文件夹。(当然目录的所有者可以删除或修改该目录下的所有文
件)
-O
执行用户是文件的拥有者
-G
文件的组与执行用户的组相同
-N
2
3
7.2文件测试操作
126
文件在在上次访问后被修改过了
f1-ntf2
文件f1比文件f2
f1-otf2
文件f1比文件f2
f1-eff2
文件f1和文件f2硬链接到同一个文件
!
取反——对测试结果取反(如果条件缺失则返回真)
样例7-4.检测链接是否损坏
#!/bin/bash
#broken-link.sh
#Leebigelow<ligelowbee@yahoo.com>编写。
#ABSGuide经许可可以使用。
#该脚本用来发现输出损坏的链接。输出的结果是被引用的,
#+所以可以直接导到xargs中进行处理:)
#例如:shbroken-link.sh/somedir/someotherdir|xargsrm
#
#更加优雅的方式:
#
#find"somedir"-type1-print0|\
#xargs-r0file|\
#grep"brokensymbolic"|
#sed-e's/^\|:*brokensymbolic.*$/"/g'
#
#但是这种方法不是纯Bash写法。
#警告:小心/proc文件下的文件和任意循环链接!
7.2文件测试操作
127
############################################
#如果不给脚本传任何参数,那么directories-to-search设置为当前目录
#+否则设置为传进的参数
#####################
[$#-eq0]&&directory=`pwd`||directory=$@
#函数linkchk是用来检测传入的文件夹中是否包含损坏的链接文件,
#+并引用输出他们。
#如果文件夹中包含子文件夹,那么将子文件夹继续传给linkchk函数进行检测。
#################
linkchk(){
forelementin$1/*;do
[-h"$element"-a!-e"$element"]&&echo\"$element\"
[-d"$element"]&&linkchk$element
#-h用来检测是否是链接,-d用来检测是否是文件夹。
done
}
#检测传递给linkchk()函数的参数是否是一个存在的文件夹,
#+如果不是则报错。
################
fordirectoryin$direcotrys;do
if[-d$directory]
thenlinkchk$directory
else
echo"$directoryisnotadirectory"
echo"Usage$0dir1dir2..."
fi
done
exit$?
样例31-1样例11-8样例11-3样例31-3样例A-1也包含了文件测试操作符
的使用。
1
7.2文件测试操作
128
.摘自1913年版本的韦氏词典
Deprecate
...
Toprayagainst,asanevil;
toseektoavertbyprayer;
todesiretheremovalof;
toseekdeliverancefrom;
toexpressdeepregretfor;
todisapproveofstrongly.
.注意使用suid的可执行文件可能会带来安全问题。suid标记对shell脚本没
有影响。
.Linux系统中,文件已经不使用粘滞位了,粘滞位只作用于目录。
1
2
3
7.2文件测试操作
129
7.3其他比较操作
二元比较操作可以比较变量或者数量。需要注意的是,整数和字符串比较使用的是
两套不同的操作符。
整数比较
-eq
等于
if["$a"-eq"$b"]
-ne
不等于
if["$a"-ne"$b"]
-gt
大于
if["$a"-gt"$b"]
-ge
大于等于
if["$a"-ge"$b"]
-lt
小于
if["$a"-lt"$b"]
7.3其他比较操作
130
-le
小于等于
if["$a"-le"$b"]
<
小于(使用双圆括号
(("$a"<"$b"))
<=
小于等于(使用双圆括号)
(("$a"<="$b"))
>
大于(使用双圆括号)
(("$a">"$b"))
>=
大于等于(使用双圆括号)
(("$a">="$b"))
字符串比较
=
等于
if["$a"="$b"]
注意在 =前后要加上空格
7.3其他比较操作
131
if["$a"="$b"]和上面不等价。
==
等于
if["$a"=="$b"]
=同义
==操作符在双方括号和单方括号里的功能是不同的。
[[$a==z*]]#$a"z"开头时为真(模式匹配)
[[$a=="z*"]]#$a等于z*时为真(字符匹配)
[$a==z*]#发生文件匹配和字符分割。
["$a"=="z*"]#$a等于z*时为真(字符匹配)
#感谢StéphaneChazelas
!=
不等于
if["$a"!="$b"]
[[...]]结构中会进行模式匹配。
<
小于,按照ASCII排序。
if[["$a"<"$b"]]
if["$a"\<"$b"]
注意在[]结构里<需要被转义
>
7.3其他比较操作
132
大于,按照ASCII码排序。
if[["$a">"$b"]]
if["$a"\>"$b"]
注意在[]结构里>需要被转义。
样例27-11包含了比较操作符。
-z
字符串为空,即字符串长度为0
String=''#长度为0的字符串变量。
if[-z"$String"]
then
echo"\$Stringisnull."
else
echo"\$StringisNOTnull."
fi#$Stringisnull.
-n
字符串非空( null)。
使用-n时字符串必须是在括号中且被引用的。使用!-z判断未引用的字
符串或者直接判断(样例7-6)通常可行,但是非常危险。判断字符串时一定要引
用 。
样例7-5.算术比较和字符串比较
1
7.3其他比较操作
133
#!/bin/bash
a=4
b=5
#这里的"a""b"可以是整数也可以是字符串。
#因为Bash的变量是弱类型的,因此字符串和整数比较有很多相同之处。
#Bash中可以用处理整数的方式来处理全是数字的字符串。
#但是谨慎使用。
echo
if["$a"-ne"$b"]
then
echo"$aisnotequalto$b"
echo"(arithmeticcomparison)"
fi
echo
if["$a"!="$b"]
then
echo"$aisnotequalto$b."
echo"(stringcomparison)"
#"4"!="5"
#ASCII52!=ASCIII53
fi
#在这个例子里"-ne""!="都可以。
echo
exit0
样例7-6.测试字符串是否为空( null
#!/bin/bash
#str-test.sh:测试是否为空字符串或是未引用的字符串。
7.3其他比较操作
134
#使用if[...]结构
#如果字符串未被初始化,则其值是未定义的。
#这种状态就是空"null"(并不是0)。
if[-n$string1]#并未声明或是初始化string1
then
echo"String\"string1\"isnotnull."
else
echo"String\"string1\"isnull."
fi
#尽管没有初始化string1,但是结果显示其非空。
echo
#再试一次。
if[-n"$string1"]#这次引用了$string1
then
echo"String\"string1\"isnotnull."
else
echo"String\"string1\"isnull."
fi#在测试括号内引用字符串得到了正确的结果。
echo
if[$string1]#这次只有一个$string1
then
echo"String\"string1\"isnotnull."
else
echo"String\"string1\"isnull."
fi#结果正确。
#独立的[...]测试操作符可以用来检测字符串是否为空。
#最好将字符串进行引用(if["$string1"])。
#
#StephaneChazelas指出:
#if[$string1]只有一个参数"]"
#if["$string1"]则有两个参数,空的"$string1""]"
7.3其他比较操作
135
echo
string1=initialized
if[$string1]#$string1这次仍然没有被引用。
then
echo"String\"string1\"isnotnull."
else
echo"String\"string1\"isnull."
fi#这次的结果仍然是正确的。
#最好将字符串引用("$string1"
string1="a=b"
if[$string1]#$string1这次仍然没有被引用。
then
echo"String\"string1\"isnotnull."
else
echo"String\"string1\"isnull."
fi#这次没有引用就错了。
exit0#同时感谢FlorianWisser的提示。
样例7-7. zmore
#!/bin/bash
#zmore
#使用筛选器'more'查看gzipped文件。
E_NOARGS=85
E_NOTFOUND=86
E_NOTGZIP=87
if[$#-eq0]#作用和if[-z"$1"]相同。
#$1可以为空:zmore""arg2arg3
7.3其他比较操作
136
then
echo"Usage:`basename$0`filename">&2
#将错误信息通过标准错误stderr进行输出。
exit$E_NOARGS
#脚本的退出状态为85.
fi
filename=$1
if[!-f"$filename"]#引用字符串以防字符串中带有空格。
then
echo"File$filenamenotfound!">&2#通过标准错误stderr进行
输出。
exit$E_NOTFOUND
fi
if[${filename##*.}!="gz"]
#在括号内使用变量代换。
then
echo"File$1isnotagzippedfile!"
exit$E_NOTGZIP
fi
zcat$1|more
#使用筛选器'more'
#也可以用'less'替代
exit$?#脚本的退出状态由管道pipe的退出状态决定。
#实际上"exit$?"不一定要写出来,
#+因为无论如何脚本都会返回最后执行命令的退出状态。
复合比较
-a
逻辑与
7.3其他比较操作
137
exp1-aexp2返回真当且仅当exp1exp2均为真。
-o
逻辑或
如果exp1exp2为真,则exp1-oexp2返回真。
以上两个操作和双方括号结构中的Bash比较操作符号&&||类似。
[[condition1&&condition2]]
测试操作-o-a可以在test命令或在测试括号中进行。
if["$expr1"-a"$expr2"]
then
echo"Bothexpr1andexpr2aretrue."
else
echo"Eitherexpr1orexpr2isfalse."
fi
rihad指出:
[1-eq1]&&[-n"`echotrue1>&2`"]#
[1-eq2]&&[-n"`echotrue1>&2`"]#没有输出
#^^^^^^^条件为假。到这里为止,一切都按预期执行。
#但是
[1-eq2-a-n"`echotrue1>&2`"]#
#^^^^^^^条件为假。但是为什么结果为真?
#是因为括号内的两个条件子句都执行了么?
[[1-eq2&&-n"`echotrue1>&2`"]]#没有输出
#并不是。
#所以显然&&||具备短路机制,
#+例如对于&&,若第一个表达式为假,则不执行第二个表达式直接返回假,
#+-a-o则不是。
7.3其他比较操作
138
复合比较操作的例子可以参考样例8-3样例27-17样例A-29
.S.C.指出在复合测试中,仅仅引用字符串可能还不够。比如表达式[-n
"$string"-o"$a"="$b"]在某些Bash版本下,如果$string为空
可能会出错。更加安全的方式是,对于可能为空的字符串,添加一个额外的字
符,例如["x$string"!=x-o"x$a"="x$b"](其中的x互相抵
消)。
1
7.3其他比较操作
139
7.4嵌套if/then条件测试
可以嵌套if/then条件测试结构。嵌套的结果等价于使用&&复合比较操作
符。
a=3
if["$a"-gt0]
then
if["$a"-lt5]
then
echo"Thevalueof\"a\"liessomewherebetween0and5."
fi
fi
#和下面的结果相同
if["$a"-gt0]&&["$a"-lt5]
then
echo"Thevalueof\"a\"liessomewherebetween0and5."
fi
样例37-4样例17-11中展示了嵌套if/then条件测试结构。
7.4嵌套if/then条件测试
140
7.5牛刀小试
系统文件xinitrc可以用来启动软件XServer。该文件包含了许多if/then
测试结构。下面的代码摘录自较早版本的xinitrc(大约在RedHat7.1
本)。
if[-f$HOME/.Xclients];then
exec$HOME/.Xclients
elif[-f/etc/X11/xinit/Xclients];then
exec/etc/X11/xinit/Xclients
else
#安全分支。尽管程序不会执行这个分支。
#(我们在Xclients中也提供了相同的机制)增强程序可靠性。
xclock-geometry100x100-5+5&
xterm-geometry80x50-50+150&
if[-f/usr/bin/netscape-a-f/usr/share/doc/HTML/index.ht
ml];then
netscape/usr/share/doc/HTML/index.html
fi
fi
试着解释代码片段中的条件测试结构,然后试着在/etc/X11/xinit/xinitrc查看最新版
本,并且分析其中的if/then条件测试结构。为了更好的进行分析,你可能需要继续
阅读后面章节中对grepsed正则表达式的讨论。
7.5牛刀小试
141
第八章运算符相关话题
本章目录
8.1运算符
8.2数字常量
8.3双圆括号结构
8.4运算符优先级
8.运算符相关话题
142
8.1.运算符
赋值运算符
变量赋值,初始化或改变一个变量的值。
=
等号 =赋值运算符,既可用于算术赋值,也可用于字符串赋值。
var=27
category=minerals#"="左右不允许有空格
注意,不要混淆 =赋值运算符与 =测试操作符
#=作为测试操作符
if["$string1"="$string2"]
then
command
fi
#["X$string1"="X$string2"]这样写是安全的,
#这样写可以避免任意一个变量为空时的报错。
#(变量前加的"X"字符规避了变量为空的情况)
算术运算符
+
-
8.1运算符
143
*
/
**
幂运算
#Bash,2.02版本,推出了"**"幂运算操作符。
let"z=5**3"#5*5*5
echo"z=$z"#z=125
%
取余(返回整数除法的余数)
bash$expr5%3
2
5/3=1,余2取余运算符经常被用于生成一定范围内的数(案例9-11,案例9-15),以
及格式化程序输出(案例27-16,案例A-6)取余运算符还可以用来产生素数(案
A-15),取余的出现大大扩展了整数的算术运算。
样例8-1.最大公约数
#!/bin/bash
#gcd.sh:最大公约数
#使用欧几里得算法
8.1运算符
144
#两个整数的最大公约数(gcd
#是两数能同时整除的最大数
#欧几里得算法使用辗转相除法
#Ineachpass,
#dividend<---divisor
#divisor<---remainder
#untilremainder=0.
#Thegcd=dividend,onthefinalpass.
#
#关于欧几里得算法更详细的讨论,可以查看:
#JimLoy'ssite,http://www.jimloy.com/number/euclids.htm.
#------------------------------------------------------
#参数检查
ARGS=2
E_BADARGS=85
if[$#-ne"$ARGS"]
then
echo"Usage:`basename$0`first-numbersecond-number"
exit$E_BADARGS
fi
#------------------------------------------------------
gcd()
{
dividend=$1#随意赋值,
divisor=$2#两数谁大谁小是无关紧要的,
#为什么?
remainder=1#如果在测试括号里使用了一个未初始化的变量,
#会报错的。
until["$remainder"-eq0]
do#^^^^^^^^^^该变量必须在使用前初始化!
let"remainder=$dividend%$divisor"
8.1运算符
145
dividend=$divisor#对被除数,除数重新赋值
divisor=$remainder
done#欧几里得算法
}#最后的$dividend就是最大公约数(gcd
gcd$1$2
echo;echo"GCDof$1and$2=$dividend";echo
#练习:
#---------
#1)检查命令行参数,保证其为整数,
#+如果有错误,捕捉错误并在脚本退出前打印出适当的错误信息。
#2)使用本地变量(localvariables)重写gcd()函数。
exit0
+=
加等(加上一个数) let"var+=5"的结果是 var变量的值增加了5
-=
减等(减去一个数)
*=
乘等(乘以一个数)let"var*=4"的结果是 var变量的值乘了4
/=
除等(除以一个数)
%=
1
8.1运算符
146
余等(取余赋值)
小结
算术运算符常用于 exprlet表达式中。
样例8-2.使用算术运算符
#!/bin/bash
#使变量自增110种不同的方法实现
n=1;echo-n"$n"
let"n=$n+1"#可以使用let"n=n+1"
echo-n"$n"
:$((n=$n+1))
#":"是必要的,不加的话,bash会将
#+"$((n=$n+1))"看做一条命令。
echo-n"$n"
((n=n+1))
#更简洁的写法。
#感谢DavidLombard指出。
echo-n"$n"
n=$(($n+1))
echo-n"$n"
:$[n=$n+1]
#":"是必要的,不加的话,bash会将
#+"$[n=$n+1]"看做一条命令。
#即使"n"是字符串,也是可行的。
echo-n"$n"
n=$[$n+1]
#即使"n"是字符串,也是可行的。
#*不要用这种写法,它已被废弃且不具有兼容性。
#感谢StephaneChazelas.
8.1运算符
147
echo-n"$n"
#使用C风格的自增运算符也是可以的
#感谢FrankWang指出。
let"n++"#let"++n"可行
echo-n"$n"
((n++))#((++n))可行
echo-n"$n"
:$((n++))#:$((++n))可行
echo-n"$n"
:$[n++]#:$[++n]可行
echo-n"$n"
echo
exit0
在早期的Bash版本中,整型变量是带符号的长整型数(32-bit),取值范围从
-21474836482147483647。如果算术操作超出了整数的取值范围,结果会不准
确。
echo$BASH_VERSION#Bash1.14版本
a=2147483646
echo"a=$a"#a=2147483646
let"a+=1"#自增"a".
echo"a=$a"#a=2147483647
let"a+=1"#再次自增"a",超出取值范围。
echo"a=$a"#a=-2147483648
#错误:超出范围,
#+最左边的符号位被重置,
#+结果变负
Bash版本>=2.05b,Bash支持了64-bit整型数。
8.1运算符
148
注意,Bash并不支持浮点运算,Bash会将带小数点的数看做字符串。
a=1.5
let"b=$a+1.3"#报错
#t2.sh:let:b=1.5+1.3:syntaxerrorinexpression
#(errortokenis".5+1.3")
echo"b=$b"#b=1
如果你想在脚本中使用浮点数运算,借助bc或外部数学函数库吧。
位运算
位运算很少出现在shell脚本中,在bash中加入位运算的初衷似乎是为了操控和检测
来自 portssockets的数据。位运算在编译型语言中能发挥更大的作用,比
C/C++,位运算提供了直接访问系统硬件的能力。然而,聪明的vladz在他的
base64.sh(案例A-54)脚本中也用到了位运算。下面介绍位运算符。
<<
左移运算符(左移1位相当于乘2)
<<=
左移赋值
let"var<<=2"的结果是var变量的值向左移了2(乘以4)
>>
右移运算符(右移1位相当于除2)
>>=
右移赋值
8.1运算符
149
&
按位与(AND
&=
按位与等(AND-equal
|
按位或(OR
|=
按位或等(OR-equal
~
按位取反
^
按位异或(XOR
^=
按位异或等(XOR-equal
逻辑(布尔)运算符
!
(NOT)
8.1运算符
150
if[!-f$FILENAME]
then
...
&&
(AND)
if[$condition1]&&[$condition2]
#等同于:if[$condition1-a$condition2]
#返回true如果condition1condition2同时为真...
if[[$condition1&&$condition2]]#可行
#注意,&&运算符不能用在[...]结构里。
&&也可以被用在 list结构中连接命令。
||
(OR)
if[$condition1]||[$condition2]
#等同于:if[$condition1-a$condition2]
#返回true如果condition1condition2任意一个为真...
if[[$condition1||$condition2]]#可行
#注意,||运算符不能用在[...]结构里。
小结
样例8-3.在条件测试中使用&&||
#!/bin/bash
8.1运算符
151
a=24
b=47
if["$a"-eq24]&&["$b"-eq47]
then
echo"Test#1succeeds."
else
echo"Test#1fails."
fi
#错误:if["$a"-eq24&&"$b"-eq47]
#这样写的话,bash会先执行'["$a"-eq24'
#然后就找不到右括号']'...
#
#注意:if[[$a-eq24&&$b-eq24]]这样写是可以的
#双方括号测试结构比单方括号更加灵活。
#(双方括号中的"&&"与单方括号中的"&&"意义不同)
#感谢StephaneChazelas指出。
if["$a"-eq98]||["$b"-eq47]
then
echo"Test#2succeeds."
else
echo"Test#2fails."
fi
#使用-a-o选项也具有同样的效果。
#感谢PatrickCallahan指出。
if["$a"-eq24-a"$b"-eq47]
then
echo"Test#3succeeds."
else
echo"Test#3fails."
fi
8.1运算符
152
if["$a"-eq98-o"$b"-eq47]
then
echo"Test#4succeeds."
else
echo"Test#4fails."
fi
a=rhino
b=crocodile
if["$a"=rhino]&&["$b"=crocodile]
then
echo"Test#5succeeds."
else
echo"Test#5fails."
fi
exit0
&&||运算符也可以用在算术运算中。
bash$echo$((1&&2))$((3&&0))$((4||0))$((0||0))
1010
其他运算符
,
逗号运算符逗号运算符用于连接两个或多个算术操作,所有的操作会被依次求值
(可能会有副作用)。
2
8.1运算符
153
let"t1=((5+3,7-1,15-4))"
echo"t1=$t1"^^^^^^#t1=11
#这里的t1被赋值了11,为什么?
let"t2=((a=9,15/3))"#"a"赋值并对"t2"求值。
echo"t2=$t2a=$a"#t2=5a=9
逗号运算符常被用在 for循环中。参看案例11-13
.取决与不同的上下文,+=也可能作为字符串连接符。它可以很方便地修改
环境变量。
.副作用,顾名思义,就是预料之外的结果。
1
2
8.1运算符
154
8.2.数字常量
通常情况下,shell脚本会把数字以十进制整数看待(base10),除非数字加了特殊的
前缀或标记。带前缀0的数字是八进制数(base8);带前缀0x的数字是十六进制数
(base16)内嵌#的数字会以BASE#NUMBER的方式进行求值(不能超出当前
shell支持整数的范围)。
样例8-4.数字常量的表示
#!/bin/bash
#numbers.sh:不同进制数的表示
#十进制数:默认
let"dec=32"
echo"decimalnumber=$dec"#32
#一切正常。
#八进制数:带前导'0'的数
let"oct=032"
echo"octalnumber=$oct"#26
#结果以十进制打印输出了。
#-----------------------
#十六进制数:带前导'0x''0X'的数
let"hex=0x32"
echo"hexadecimalnumber=$hex"#50
echo$((0x9abc))#39612
#^^^^双圆括号进行表达式求值
#结果以十进制打印输出。
#其他进制数:BASE#NUMBER
#BASE范围:2-64
#NUMBER必须以BASE规定的正确形式书写,如下:
8.2数字常量
155
let"bin=2#111100111001101"
echo"binarynumber=$bin"#31181
let"b32=32#77"
echo"base-32number=$b32"#231
let"b64=64#@_"
echo"base-64number=$b64"#4031
#这种表示法只对进制范围(2-64)内的ASCII字符有效。
#10数字+26小写字母+26大写字母+@+_
echo
echo$((36#zz))$((2#10101010))$((16#AF16))$((53#1aA))
#1295170448223375
#重要提醒:
#---------
#使用超出进制范围以外的符号会报错。
let"bad_oct=081"
#(可能的)报错信息:
#bad_oct=081:valuetoogreatforbase(errortokenis"081"
)
#Octalnumbersuseonlydigitsintherange0-7.
exit$?#退出码=1(错误)
#感谢RichBartellStephaneChazelas的说明。
8.2数字常量
156
双圆括号结构
let命令类似, ((...))结构允许对算术表达式的扩展和求值。它
let命令的简化形式。例如,a=$((5+3))会将变量a赋值成5+3,也就是
8。在Bash中,双圆括号结构也允许以C风格的方式操作变量。例如,((var++))
样例8-5.C风格的方式操作变量
#!/bin/bash
#c-vars.sh
#C风格的方式操作变量,使用((...))结构
echo
((a=23))#C风格的变量赋值,注意"="等号前后都有空格
echo"a(initialvalue)=$a"#23
((a++))#后缀自增'a'C-style.
echo"a(aftera++)=$a"#24
((a--))#后缀自减'a',C-style.
echo"a(aftera--)=$a"#23
((++a))#前缀自增'a',C-style.
echo"a(after++a)=$a"#24
((--a))#前缀自减'a',C-style.
echo"a(after--a)=$a"#23
echo
########################################################
#注意,C风格的++--运算符,前缀形式与后缀形式有不同的
#+副作用。
8.3双圆括号结构
157
n=1;let--n&&echo"True"||echo"False"#False
n=1;letn--&&echo"True"||echo"False"#True
#感谢JeroenDomburg
########################################################
echo
((t=a<45?7:11))#C风格三目运算符。
#^^^
echo"Ifa<45,thent=7,elset=11."#a=23
echo"t=$t"#t=7
echo
#-----------
#复活节彩蛋!
#-----------
#ChetRamey偷偷往Bash里加入了C风格的语句结构,
#还没写文档说明(实际上很多是从ksh中继承过来的)
#Bash文档中,Ramey((...))结构称为shell算术运算,
#但是这种表述并不准确...
#抱歉啊,Chet,把你的秘密抖出来了。
#参看"for""while"循环章节关于((...))结构的部分。
#((...))结构在Bash2.04版本之后才能正常工作。
exit
还可以参看样例11-13样例8-4
8.3双圆括号结构
158
运算符优先级
在脚本中,运算执行的顺序被称为优先级:高优先级的操作会比低优先级的操作先
执行。
8-1.运算符优先级(从高到低)
运算符 含义 注解
var++var-- 后缀自增/
自减 C风格运算符
++var--var 前缀自增/
自减
!~ 按位取反/
逻辑取反
对每一比特位取反/对逻
辑判断的结果取反
** 幂运算 算数运算符
*/% ,,
算数运算符
+- ,减 算数运算符
<<>> 左移,
比特位运算符
-z-n 一元比较 字符串是/否为空
-e-f-t-x,etc 一元比较 文件测试
-lt-gt-le-ge<=
>= 复合比较 字符串/整数比较
-nt-ot-ef 复合比较 文件测试
&AND(按位
)按位与操作
^XOR(按位
异或)按位异或操作
\ OR(按位或)
按位
或操
1
8.4运算符优先级
159
&&-a AND(逻辑
)逻辑与,复合比较
\ \ -o OR(
辑或)
逻辑或,
复合比
?: if/else
目运算符 C风格运算符
=赋值 不要与test中的等号混淆
*=/=%=+=-=
<<=>>=&= 赋值运算 先运算后赋值
,逗号运算
连接一系列语句
实际上,你只需要记住以下规则就可以了:
先乘除取余,后加减,与算数运算相似
复合逻辑运算符,&&,||,-a,-o优先级较低
优先级相同的操作按从左至右顺序求值
现在,让我们利用运算符优先级的知识来分析一下FedoraCoreLinux
/etc/init.d/functions文件。
while[-n"$remaining"-a"$retry"-gt0];do
#初看之下很恐怖...
#分开来分析
while[-n"$remaining"-a"$retry"-gt0];do
#--condition1--^^--condition2-
#如果变量"$remaining"长度不为0
#+并且AND(-a)
#+变量"$retry"大于0
#+那么
#+[方括号表达式]返回成功(0)
#+while-loop开始迭代执行语句。
#=============================================================
8.4运算符优先级
160
=
#"condition1""condition2"AND之前执行,为什么?
#因为AND(-a)优先级比-n,-gt来得低,逻辑与会在最后求值。
################################################################
#
if[-f/etc/sysconfig/i18n-a-z"${NOLOCALE:-}"];then
#同样,分开来分析
if[-f/etc/sysconfig/i18n-a-z"${NOLOCALE:-}"];then
#--condition1---------^^--condition2-----
#如果文件"/etc/sysconfig/i18n"存在
#+并且AND(-a)
#+变量$NOLOCALE长度不为0
#+那么
#+[方括号表达式]返回成功(0)
#+执行接下来的语句。
#
#和之前的情况一样,逻辑与AND(-a)最后求值。
#因为在方括号测试结构中,逻辑运算的优先级是最低的。
#=============================================================
=
#注意:
#${NOLOCALE:-}是一个参数扩展式,看起来有点多余。
#但是,如果$NOLOCALE没有提前声明,它会被设成null
#在某些情况下,这会有点问题。
为了避免在复杂比较运算中的错误,可以把运算分散到几个括号结构中。
if["$v1"-gt"$v2"-o"$v1"-lt"$v2"-a-e"$filename
"]
#这样写不清晰...
if[["$v1"-gt"$v2"]]||[["$v1"-lt"$v2"]]&&[[-e"
$filename"]]
#好多了--把逻辑判断分散到多个组之中
1
8.4运算符优先级
161
.Precedence(优先级),根据上下文,与priority含义相近。
1
8.4运算符优先级
162
第三部分shell进阶
目录
9.换个角度看变量
9.1内部变量
9.2指定变量属性: decalretypeset
9.3 $RANDOM:随机产生整数
10.变量处理
10.1字符串处理
10.1.1使用awk处理字符串
10.1.2参考资料
10.2参数替换
11.循环与分支
11.1循环
11.2嵌套循环
11.3循环控制
11.4测试与分支
12.命令替换
13.算术扩展
14.休息时间
第三部分shell进阶
163
第十章变量处理
本章目录
10.1字符串处理
10.1.1使用awk处理字符串
10.1.2参考资料
10.2参数替换
10.变量处理
164
10.1字符串处理
Bash支持的字符串操作数量达到了一个惊人的数目。但可惜的是,这些操作工具
缺乏一个统一的核心。他们中的一些是参数代换的子集,另外一些则是UNIX
expr函数的子集。这将会导致语法前后不一致或者功能上出现重叠,更不用说
那些可能导致的混乱了。
字符串长度
$
exprlength$string
上面两个表达式等价于C语言中的strlen()函数。
expr"$string":'.*'
stringZ=abcABC123ABCabc
echo${#stringZ}#15
echo`exprlength$stringz`#15
echo`expr"$stringZ":'.*'`#15
样例10-1.在文本的段落之间插入空行
10.1字符串处理
165
#!/bin/bash
#paragraph-space.sh
#版本2.1,发布日期2012729
#在无空行的文本文件的段落之间插入空行。
#像这样使用:$0<FILENAME
MINLEN=60#可以试试修改这个值。它用来做判断。
#假设一行的字符数小于$MINLEN,并且以句点结束段落。
#+结尾部分有练习!
whilereadline#当文件有许多行的时候
do
echo"$line"#输出行本身。
len=${#line}
if[["$len"-lt"$MINLEN"&&"$line"=~[*{\.}]$]]
#if[["$len"-lt"$MINLEN"&&"$line"=~\[*\.\]]]
#新版Bash将不能正常运行前一个版本的脚本。Ouch
#感谢HalimSrama指出这点,并且给出了修正版本。
thenecho#在该行以句点结束时,
fi#+增加一行空行。
done
exit
#练习:
#-----
#1)该脚本通常会在文件的最后插入一个空行。
#+尝试解决这个问题。
#2)在第17行仅仅考虑到了以句点作为句子终止的情况。
#+修改以满足其他的终止符,例如?,!"
起始部分字符串匹配长度
exprmatch"$string"'$substring'
其中, $substring是一个正则表达式
10.1字符串处理
166
expr"$string":'$substring'
其中, $substring是一个正则表达式。
stringZ=abcABC123ABCabc
#|------|
#12345678
echo`exprmatch"$stringZ"'abc[A-Z]*.2'`#8
echo`expr"$stringZ":'abc[A-Z]*.2'`#8
索引
exprindex$string$substring
返回在$string中第一个出现的$substring字符所在的位置。
stringZ=abcABC123ABCabc
#123456...
echo`exprindex"$stringZ"C12`#6
#C的位置。
echo`exprindex"$stringZ"1c`#3
#'c'(第三号位)'1'出现的更早。
几乎等价于C语言中的strchr()
截取字符串(字符串分片)
${string:position}
$string中截取自$position起的字符串。
如果参数$string"*"或者"@",那么将会截取自$position起的位置参
${string:position:length}
1
10.1字符串处理
167
$string中截取自$position起,长度为$length的字符串。
stringZ=abcABC123ABCabc
#0123456789.....
#索引位置从0开始。
echo${stringZ:0}#abcABC123ABCabc
echo${stringZ:1}#bcABC123ABCabc
echo${stringZ:7}#23ABCabc
echo${stringz:7:3}#23A
#三个字符的子字符串。
#从右至左进行截取可行么?
echo${stringZ:-4}#abcABC123ABCabc
#${parameter:-default}将会得到整个字符串。
#但是……
echo${stringZ:(-4)}#Cabc
echo${stringZ:-4}#Cabc
#现在可以了。
#括号或者增加空格都可以"转义"位置参数。
#感谢DanJacobson指出这些。
其中,参数positionlength可以传入一个变量而不一定需要传入常量。
样例10-2.产生一个8个字符的随机字符串
10.1字符串处理
168
#!/bin/bash
#rand-string.sh
#产生一个8个字符的随机字符串。
if[-n"$1"]#如果在命令行中已经传入了参数,
then#+那么就以它作为起始字符串。
str0="$1"
else#否则,就将脚本的进程标识符PID作为起始字符串。
str0="$$"
fi
POS=2#从字符串的第二位开始。
LEN=8#截取八个字符。
str1=$(echo"$str0"|md5sum|md5sum)
#^^^^^^^^^^^^
#将字符串通过管道计算两次md5来进行两次混淆。
randstring="${str1:$POS:$LEN}"
#^^^^^^^^
#允许传入参数
echo"$randstring"
exit$?
#bozo$./rand-string.shmy-password
#1bdd88c4
#不过不建议将其作为一种能够抵抗黑客的生成密码的方法。
如果参数$string"*"或者"@",那么将会截取自$position起,最大个
数为$length的位置参数。
echo${*:2}#输出第二个及之后的所有位置参数。
echo${@:2}#同上。
echo${*:2:3}#从第二个位置参数起,输出三个位置参数。
10.1字符串处理
169
exprsubstr$string$position$length
$string中截取自$position起,长度为$length的字符串。
stringZ=abcABC123ABCabc
#123456789......
#索引位置从1开始。
echo`exprsubstr$stringZ12`#ab
echo`exprsubstr$stringZ43`#ABC
exprmatch"$string"'\($substring\)'
$string中截取自$position起的字符串,其中$substring正则表
达式
expr"$string":'\($substring\)'
$string中截取自$position起的字符串,其中$substring是正则表
达式。
stringZ=abcABC123ABCabc
#=======
echo`exprmatch"$stringZ"'\(.[b-c]*[A-Z]..[0-9]\)'`#abcAB
C1
echo`expr"$stringZ":'\(.[b-c]*[A-Z]..[0-9]\)'`#abcAB
C1
echo`expr"$stringZ":'\(.......\)'`#abcAB
C1
#上面所有的形式都给出了相同的结果。
exprmatch"$string"'.*\($substring\)'
$string结尾部分截取$substring字符串,其中$substring是正则表
达式。
expr"$string":'.*\($substring\)'
10.1字符串处理
170
$string结尾部分截取$substring字符串,其中$substring是正则表
达式。
stringZ=abcABC123ABCabc
#======
echo`exprmatch"$stringZ"'.*\([A-C][A-C][A-C][a-c]*\)'`#
ABCabc
echo`expr"$stringZ":'.*\(......\)'`#
ABCabc
删除子串
${string#substring}
删除从$string起始部分起,匹配到的最短的$substring
${string##substring}
删除从$string起始部分起,匹配到的最长的$substring
10.1字符串处理
171
stringZ=abcABC123ABCabc
#|----|最长
#|----------|最短
echo${stringZ#a*C}#123ABCabc
#删除'a''c'之间最短的匹配。
echo${stringZ##a*C}#abc
#删除'a''c'之间最长的匹配。
#你可以使用变量代替substring
X='a*C'
echo${stringZ#$X}#123ABCabc
echo${stringZ##$X}#abc
#同上。
${string%substring}
删除从$string结尾部分起,匹配到的最短的$substring
例如:
10.1字符串处理
172
#将当前目录下所有后缀名为"TXT"的文件改为"txt"后缀。
#例如"file1.TXT"改为"file1.txt"
SUFF=TXT
suff=txt
foriin$(ls*.$SUFF)
do
mv-f$i$(i%.$SUFF).$suff
#除了从变量$i右侧匹配到的最短的字符串之外,
#+其他一切都保持不变。
done###如果需要,循环可以压缩成一行的形式。
#感谢RoryWinston
${string%%substring}
删除从$string结尾部分起,匹配到的最长的$substring
stringZ=abcABC123ABCabc
#||最短
#|------------|最长
echo${stringZ%b*c}#abcABC123ABCa
#从结尾处删除'b''c'之间最短的匹配。
echo${stringZ%%b*c}#a
#从结尾处删除'b''c'之间最长的匹配。
这个操作对生成文件名非常有帮助。
样例10-3.改变图像文件的格式及文件名
#!/bin/bash
#cvt.sh:
#将目录下所有的MacPaint文件转换为"pbm"格式。
10.1字符串处理
173
#使用由BrianHenderson(bryanh@giraffe-data.com)维护的
#+"netpbm"包下的"macptobpm"二进制工具。
#Netpbm是大多数Linux发行版的标准组成部分。
OPERATION=macptopbm
SUFFIX=pbm#新的文件名后缀。
if[-n"$1"]
then
directory=$1#如果已经通过脚本参数传入了目录名的情况……
else
directory=$PWD#否则就使用当前工作目录。
fi
#假设目标目录下的所有MacPaint图像文件都拥有
#+".mac"的文件后缀名。
forfilein$directory/*#文件名匹配。
do
filename=${file%.*c}#从文件名中删除".mac"后缀
#+('.*c'匹配'.''c'之间的
#所有字符,包括其本身)
$OPERATION$file>"$filename.$SUFFIX"
#将转换结果重定向到新的文件。
rm-f$file#在转换后删除原文件。
echo"$filename.$SUFFIX"#将记录输出到stdout中。
done
exit0
#练习:
#-----
#这个脚本会将当前工作目录下的所有文件进行转换。
#修改脚本,使得它仅转换".mac"后缀的文件。
#***还可以使用另外一种方法。***#
#!/bin/bash
10.1字符串处理
174
#将图像批处理转换成不同的格式。
#假设已经安装了imagemagick。(在大部分Linux发行版中都有)
INFMT=png#可以是tif,jpg,gif等等。
OUTFMT=pdf#可以是tif,jpg,gif,pdf等等。
forpicin*"$INFMT"
do
p2=$(ls"$pic"|sed-es/\.$INFMT//)
#echo$p2
convert"$pic"$p2.$OUTFMT
done
exit$?
样例10-4.将流音频格式转换成ogg格式
#!/bin/bash
#ra2ogg.sh:将流音频文件(*.ra)转换成ogg格式。
#使用"mplayer"媒体播放器程序:
#http://www.mplayerhq.hu/homepage
#使用"ogg"库与"oggenc"
#http://www.xiph.org/
#
#脚本同时需要安装一些解码器,例如sipr.so等等一些。
#这些解码器可以在compat-libstdc++包中找到。
OFILEPREF=${1%%ra}#删除"ra"后缀。
OFILESUFF=wav#wav文件后缀。
OUTFILE="$OFILEPREF""$OFILESUFF"
E_NOARGS=85
if[-z"$1"]#必须指定一个文件进行转换。
then
echo"Usage:`basename$0`[filename]"
exit$E_NOAGRS
fi
10.1字符串处理
175
######################################################
mplayer"$1"-aopcm:file=$OUTFILE
oggenc"$OUTFILE"#oggenc自动加上正确的文件后缀名。
######################################################
rm"$OUTFILE"#立即删除*.wav文件。
#如果你仍需保留原文件,注释掉上面这一行即可。
exit$?
#注意:
#-----
#在网站上,点击一个*.ram的流媒体音频文件
#+通常只会下载到*.ra音频文件的URL
#你可以使用"wget"或者类似的工具下载*.ra文件本身。
#练习:
#-----
#这个脚本仅仅转换*.ra文件。
#修改脚本增加适应性,使其可以转换*.ram或其他文件格式。
#
#如果你非常有热情,你可以扩展这个脚本使其
#+可以自动下载并且转换流媒体音频文件。
#给定一个URL,自动下载流媒体音频文件(使用"wget")
#+然后转换它。
下面是使用字符串截取结构对getopt的一个简单模拟。
样例10-5.模拟getopt
#!/bin/bash
#getopt-simple.sh
#作者:ChrisMorgan
#允许在高级脚本编程指南中使用。
getopt_simple()
10.1字符串处理
176
{
echo"getopt_simple()"
echo"Parametersare'$*'"
until[-z"$1"]
do
echo"Processingparameterof:'$1'"
if[${1:0:1}='/']
then
tmp=${1:1}#删除开头的'/'
parameter=${tmp%%=*}#取出名称。
value=${tmp##*=}#取出值。
echo"Parameter:'$parameter',value:'$value'"
eval$parameter=$value
fi
shift
done
}
#将所有参数传递给getopt_simple()
getopt_simple$*
echo"testis'$test'"
echo"test2is'$test2'"
exit0#可以查看该脚本的修改版UseGetOpt.sh
---
shgetopt_example.sh/test=value1/test2=value2
Parametersare'/test=value1/test2=value2'
Processingparameterof:'/test=value1'
Parameter:'test',value:'value1'
Processingparameterof:'/test2=value2'
Parameter:'test2',value:'value2'
testis'value1'
test2is'value2'
子串替换
10.1字符串处理
177
${string/substring/replacement}
替换匹配到的第一个$substring$replacement
${string//substring/replacement}
替换匹配到的所有$substring$replacement
stringZ=abcABC123ABCabc
echo${stringZ/abc/xyz}#xyzABC123ABCabc
#将匹配到的第一个'abc'替换为'xyz'
echo${stringZ//abc/xyz}#xyzABC123ABCxyz
#将匹配到的所有'abc'替换为'xyz'
echo---------------
echo"$stringZ"#abcABC123ABCabc
echo---------------
#字符串本身并不会被修改!
#匹配以及替换的字符串可以是参数么?
match=abc
repl=000
echo${stringZ/$match/$repl}#000ABC123ABCabc
#^^^^^
echo${stringZ//$match/$repl}#000ABC123ABC000
#Yes!^^^^^^^^
echo
#如果没有给定$replacement字符串会怎样?
echo${stringZ/abc}#ABC123ABCabc
echo${stringZ//abc}#ABC123ABC
#仅仅是将其删除而已。
${string/#substring/replacement}
2
10.1字符串处理
178
替换$string中最前端匹配到的$substring$replacement
${string/%substring/replacement}
替换$string中最末端匹配到的$substring$replacement
stringZ=abcABC123ABCabc
echo${stringZ/#abc/XYZ}#XYZABC123ABCabc
#将前端的'abc'替换为'XYZ'
echo${stringZ/%abc/XYZ}#abcABC123ABCXYZ
#将末端的'abc'替换为'XYZ'
.这种情况同时适用于命令行参数和传入函数的参数。
.注意根据使用时上下文的不同, $substring$replacement可以是
文本字符串也可以是变量。可以参考第一个样例。
1
2
10.1字符串处理
179
10.1.1使用awk处理字符串
Bash脚本中可以调用字符串处理工具awk来替换内置的字符串处理操作。
样例10-6.使用另一种方式来截取和定位子字符串
10.1字符串处理
180
#!/bin/bash
#substring-extraction.sh
String=23skidoo1
#012345678Bash
#123456789awk
#注意不同字符串索引系统:
#Bash中第一个字符的位置为0
#Awk中第一个字符的位置为1
echo${String:2:4}#从第3位开始(0-1-2),4个字符的长度
#skid
#Awk中与${string:pos:length}等价的是substr(string,pos,length)
echo|awk'
{printsubstr("'"${String}"'",3,4)#skid
}
'
#将空的"echo"通过管道传递给awk作为一个模拟输入,
#+这样就不需要提供一个文件名来操作awk了。
echo"----"
#同样的:
echo|awk'
{printindex("'"${String}"'","skid")#3
}#skid从第3位开始)
'#这里使用awk等价于"exprindex"
exit0
10.1字符串处理
181
10.1.2参考资料
更多关于脚本中处理字符串的资料,可以查看章节10.2以及expr命令的相关
章节
脚本样例:
1. 样例16-9
2. 样例10-9
3. 样例10-10
4. 样例10-11
5. 样例10-13
6. 样例A-36
7. 样例A-41
10.1字符串处理
182
10.2参数替换
参数替换用来处理或扩展变量。
${parameter}
等同于$parameter,是变量parameter的值。在一些特定的环境下,只允许使
用不易混淆的${parameter}形式。
可以用于连接变量与字符串。
your_id=${USER}-on-${HOSTNAME}
echo"$your_id"
#
echo"Old\$PATH=$PATH"
PATH=${PATH}:/opt/bin#在脚本执行过程中临时在$PATH中加入/opt/bin
echo"New\$PATH=$PATH"
${parameter-default},${parameter:-default}
在没有设置变量的情况下使用缺省值。
var1=1
var2=2
#没有设置var3
echo${var1-$var2}#1
echo${var3-$var2}#2
#^注意前面的$前缀。
echo${username-`whoami`}
#如果变量$username没有被设置,输出`whoami`的结果。
10.2参数替换
183
${parameter-default}${parameter:-default}的作用几乎相
同,唯一不同的情况就是当变量parameter已经被声明但值为空时。
#!/bin/bash
#param-sub.sh
#无论变量的值是否为空,其是否已被声明决定了缺省设置的触发。
username0=
echo"username0hasbeendeclared,butissettonull."
echo"username0=${username0-`whoami`}"
#将不会输出`whoami`的结果。
echo
echousername1hasnotbeendeclared.
echo"username1=${username1-`whoami`}"
#将会输出`whoami`的结果。
username2=
echo"username2hasbeendeclared,butissettonull."
echo"username2=${username2:-`whoami`}"
#^
#因为这里是:-而不是-,所以将会输出`whoami`的结果。
#与上面的username0比较。
#
#再来一次:
variable=
#变量已被声明,但其值为空。
echo"${varibale-0}"#没有输出。
echo"${variable:-1}"#1
#^
unservariable
10.2参数替换
184
echo"${variable-2}"#2
echo"${variable:-3}"#3
exit0
当传入的命令行参数的数量不足时,可以使用这种缺省参数结构。
DEFAULT_FILENAME=generic.data
filename=${1:-$DEFAULT_FILENAME}
#如果没有其他特殊情况,下面的代码块将会操作文件"generic.data"
#代码块开始
#...
#...
#...
#代码块结束
#摘自样例"hanoi2.bash"
DISKS=${1:-E_NOPARAM}#必须指定碟子的个数。
#$DISKS设置为传入的第一个命令行参数,
#+如果没有传入第一个参数,则设置为$E_NOPARAM
可以查看样例3-4样例31-2样例A-6
可以同使用与链设置缺省命令行参数做比较。
${parameter=default},${parameter:=default}
在没有设置变量的情况下,将其设置为缺省值。
两种形式的作用几乎相同,唯一不同的情况与上面类似,就是当变量parameter
经被声明但值为空时。
echo${var=abc}#abc
echo${vat=xyz}#abc
#$var已经在第一条语句中被赋值为abc,因此第二条语句将不会改变它的值。
1
10.2参数替换
185
${parameter+alt_value},
${parameter:+alt_value}
如果变量已被设置,使用alt_value,否则使用空值。
两种形式的作用几乎相同,唯一不同的情况就是当变量parameter已经被声明但值
为空时,看下面的例子。
echo"######\${parameter+alt_value}########"
echo
a=${param1+xyz}
echo"a=$a"#a=
param2=
a=${param2+xyz}
echo"a=$a"#a=xyz
param3=123
a=${param3+xyz}
echo"a=$a"#a=xyz
echo
echo"######\${parameter:+alt_value}########"
echo
a=${param4:+xyz}
echo"a=$a"#a=
param5=
a=${param5:+xyz}
echo"a=$a"#a=
#不同于a=${param5+xyz}
param6=123
a=${param6:+xyz}
echo"a=$a"#a=xyz
${parameter?err_msg},${parameter:?err_msg}
10.2参数替换
186
如果变量已被设置,那么使用原值,否则输出err_msg并且终止脚本,返回错误
1
两种形式的作用几乎相同,唯一不同的情况与上面类似,就是当变量parameter
经被声明但值为空时。
样例10-7.如何使用变量替换和错误信息
#!/bin/bash
#检查系统环境变量。
#这是一种良好的预防性维护措施。
#如果控制台用户的名称$USER没有被设置,那么主机将不能够识别用户。
:${HOSTNAME?}${USER?}${HOME?}${MAIL?}
echo
echo"Nameofthemachineis$HOSTNAME."
echo"Youare$USER."
echo"Yourhomedirectoryis$HOME."
echo"YourmailINBOXislocatedin$MAIL."
echo
echo"Ifyouarereadingthismessage,"
echo"critcialenvironmentalvariableshavebeenset."
echo
echo
#------------------------------------------------------
#${variablename?}结构统一可以检查脚本中的变量是否被设置。
ThisVariable=Value-of-ThisVariable
#顺带一提,这个字符串的值可以被设置成名称中不可以使用的禁用字符。
:${ThisVariable?}
echo"ValueofThisVariableis$ThisVariable."
echo;echo
:${ZZXy23AB?"ZZXy23ABhasnotbeenset."}
#因为ZZXy23AB没有被设置,所以脚本会终止同时显示错误消息。
10.2参数替换
187
#你可以指定错误消息。
#:${variablename?"ERRORMESSAGE"}
#与这些结果相同:dummy_variable=${ZZXy23AB?}
#dummy_variable=${ZZXy23AB?"ZZXy23ABhasnotbe
enset."}
#
#echo${ZZXy23AB?}>/dev/null
#将上面这些检查变量是否被设置的方法同"set-u"作比较。
echo"Youwillnotseethismessage,becausescriptalreadyterm
inated."
HERE=0
exit$HERE#将不会从这里退出。
#事实上,这个脚本将会返回退出码(echo$?1
样例10-8.参数替换与"usage"消息
10.2参数替换
188
#!/bin/bash
#usage-message.sh
:${1?"Usage:$0ARGUMENT"}
#如果命令行参数缺失,脚本将会在这里结束,并且返回下面的错误信息。
#usage-message.sh:1:Usage:usage-message.shARGUMENT
echo"Thesetwolinesechoonlyifcommand-lineparametergiven."
echo"command-lineparameter=\"$1\""
exit0#仅当命令行参数存在是才会从这里退出。
#在传入和未传入命令行参数的情况下查看退出状态。
#如果传入了命令行参数,那么"$?"的结果是0
#如果没有,那么"$?"的结果是1
参数替换用来处理或扩展变量。下面的表达式是对expr处理字符串的操作的补
足(查看样例16-9)。这些特殊的表达式通常养来解析文件的路径名。
变量长度/删除子串
$
字符串的长度( $var中字符的个数)。对任意数组array${#array}返回
数组中第一个元素的长度。
以下情况例外:
${#*}${#@}返回位置参数的个数。
任意数组array${#array[*]}${#array[@]}返回数组中元素的
个数。
样例10-9.变量长度
10.2参数替换
189
#!/bin/bash
#length.sh
E_NO_ARGS=65
if[$#-eq0]#脚本必须传入参数。
then
echo"Pleaseinvokethisscriptwithoneormorecommand-line
arguments."
exit$E_NO_ARGS
fi
var01=abcdEFGH28ij
echo"var01=${var01}"
echo"Lengthofvar01=${#var01}"
#现在我们尝试加入空格。
var02="abcdEFGH28ij"
echo"var02=${var02}"
echo"Lengthofvar02=${#var02}"
echo"Numberofcommand-lineargumentspassedtoscript=${#@}"
echo"Numberofcommand-lineargumentspassedtoscript=${#*}"
exit0
${var#Pattern},${var##Pattern}
${var#Pattern}删除$var前缀部分匹配到的最短长度的$Pattern
${var##Pattern}删除$var前缀部分匹配到的最长长度的$Pattern
摘自样例A-7的例子:
10.2参数替换
190
#函数摘自样例"day-between.sh"
#删除传入的参数中的前缀0
strip_leading_zero()#删除传入参数中可能存在的
{#+前缀0
return=${1#0}#"1"代表"$1",即传入的参数。
}#"$1"中删除"0"
下面是由ManfredSchwarb提供的上述函数的改进版本:
strip_leading_zero2()#删除前缀0
{#否则Bash会将其解释为8进制数。
shopt-sextglob#启用扩展通配特性。
localval=${1##+(0)}#使用本地变量,匹配前缀中所有的0
shopt-uextglob#禁用扩展通配特性。
_strip_leading_zero2=${var:-0}
#如果输入的为0,那么返回0而不是""
另外一个样例:
echo`basename$PWD`#当前工作目录的目录名。
echo"${PWD##*/}"#当前工作目录的目录名。
echo
echo`basename$0`#脚本名。
echo$0#脚本名。
echo"${0##*/}"#脚本名。
echo
filename=test.data
echo"${filename##*.}"#data
#文件扩展名。
${var%Pattern},${var%%Pattern}
${var%Pattern}删除$var后缀部分匹配到的最短长度的$Pattern
${var%%Pattern}删除$var后缀部分匹配到的最长长度的$Pattern
Bash第二个版本中增加了一些额外的选择。
10.2参数替换
191
样例10-10.参数替换中的模式匹配
#!/bin/bash
#patt-matching.sh
#使用###%%%参数替换操作符进行模式匹配
var1=abcd12345abc6789
pattern1=a*c#通配符*可以匹配ac之间的任意字符
echo
echo"var1=$var1"#abcd12345abc6789
echo"var1=${var1}"#abcd12345abc6789
#(另一种形式)
echo"Numberofcharactersin${var1}=${#var1}"
echo
echo"pattern1=$pattern1"#a*c(匹配'a''c'之间的一切)
echo"--------------"
echo'${var1#$pattern1}='"${var1#$pattern1}"#d12
345abc6789
#匹配到首部最短的3个字符abcd1234
5abc6789
#^|-|
echo'${var1##$pattern1}='"${var1##$pattern1}"#
6789
#匹配到首部最长的12个字符abcd1234
5abc6789
#^|-----
-----|
echo;echo;echo
pattern2=b*9#匹配'b''9'之间的任意字符
echo"var1=$var1"#仍旧是abcd12345abc6789
echo
echo"pattern2=$pattern2"
echo"--------------"
echo'${var1%pattern2}='"${var1%$pattern2}"#abcd123
45a
10.2参数替换
192
#匹配到尾部最短的6个字符abcd12345
abc6789
#^
|----|
echo'${var1%%pattern2}='"${var1%%$pattern2}"#a
#匹配到尾部最长的12个字符abcd12345
abc6789
#^|-----
--------|
#牢记###是从字符串左侧开始,
#%%%是从右侧开始。
echo
exit0
样例10-11.更改文件扩展名:
10.2参数替换
193
#!/bin/bash
#rfe.sh:更改文件扩展名。
#
#rfeold_extensionnew_extension
#
#如:
#将当前目录下所有*.gif文件重命名为*.jpg
#rfegifjpg
E_BADARGS=65
case$#in
0|1)#竖线|在这里表示逻辑或关系。
echo"Usage:`basename$0`old_file_suffixnew_file_suffix"
exit$E_BADARGS#如果只有0个或1个参数,那么退出脚本。
;;
esac
forfilenamein*.$1
#遍历以第一个参数作为后缀名的文件列表。
do
mv$filename${filename%$1}$2
#删除文件后缀名,增加第二个参数作为后缀名。
done
exit0
变量扩展/替换子串
下面这些结构采用自ksh
${var:pos}
扩展为从偏移量pos处截取的变量var
${var:pos:len}
10.2参数替换
194
扩展为从偏移量pos处截取变量var最大长度为len的字符串。
${var/Pattern/Replacement}
替换var中第一个匹配到的PatternReplacement
如果Replacement被省略,那么匹配到的第一个Pattern将被替换为空,即删除。
${var//Pattern/Replacement}
全局替换。替换var中所有匹配到的PatternReplacement
跟上面一样,如果Replacement被省略,那么匹配到的所有Pattern将被替换为
空,即删除。
样例10-12.使用模式匹配解析任意字符串
#!/bin/bash
var1=abcd-1234-defg
echo"var1=$var1"
t=${var1#*-*}
echo"var1(witheverything,uptoandincludingfirst-strippe
dout)=$t"
#t=${var1#*-}效果相同,
#+因为#只匹配最短的字符串,
#+并且*可以任意匹配,其中也包括空字符串。
#(感谢StephaneChazelas指出这一点。)
t=${var##*-*}
echo"Ifvar1containsa\"-\",returnsemptystring...var1=
$t"
t=${var1%*-*}
echo"var1(witheverythingfromthelast-onstrippedout)=$t
"
echo
10.2参数替换
195
#-------------------------------------------
path_name=/home/bozo/ideas/thoughts/for.today
#-------------------------------------------
echo"path_name=$path_name"
t=${path_name##/*/}
echo"path_name,strippedofprefixes=$t"
#在这里与t=`basename$path_name`效果相同。
#t=${path_name%/};t=${t##*/}是更加通用的方法,
#+但有时仍旧也会出现问题。
#如果$path_name以换行结束,那么`basename$path_name`将会失效,
#+但是上面这种表达式却可以。
#(感谢S.C.
t=${path_name%/*.*}
#t=`dirname$path_name`效果相同。
echo"path_name,strippedofsuffixes=$t"
#在一些情况下会失效,比如"../","/foo////",#"foo/","/"
#在删除后缀时,尤其是当文件名没有后缀,目录名却有后缀时,
#+事情会变的非常复杂。
#(感谢S.C.
echo
t=${path_name:11}
echo"$path_name,withfirst11charsstrippedoff=$t"
t=${path_name:11:5}
echo"$path_name,withfirst11charsstrippedoff,length5=$t
"
echo
t=${path_name/bozo/clown}
echo"$path_namewith\"bozo\"replacedby\"clown\"=$t"
t=${path_name/today/}
echo"$path_namewith\"today\"deleted=$t"
t=${path_name//o/O}
echo"$path_namewithallo'scapitalized=$t"
t=${path_name//o/}
echo"$path_namewithallo'sdeleted=$t"
10.2参数替换
196
exit0
${var/#Pattern/Replacement}
替换var前缀部分匹配到的PatternReplacement
${var/%Pattern/Replacement}
替换var后缀部分匹配到的PatternReplacement
样例10-13.在字符串首部或尾部进行模式匹配
10.2参数替换
197
#!/bin/bash
#var-match.sh:
#演示在字符串首部或尾部进行模式替换。
v0=abc1234zip1234abc#初始值。
echo"v0=$v0"#abc1234zip1234abc
echo
#在字符串首部进行匹配
v1=${v0/#abc/ABCDEF}#abc1234zip123abc
#|-|
echo"v1=$v1"#ABCDEF1234zip1234abc
#|----|
#在字符串尾部进行匹配
v2=${v0/%abc/ABCDEF}#abc1234zip123abc
#|-|
echo"v2=$v2"#abc1234zip1234ABCDEF
#|----|
echo
#--------------------------------------------
#必须在字符串的最开始或者最末尾的地方进行匹配,
#+否则将不会发生替换。
#--------------------------------------------
v3=${v0/#123/000}#虽然匹配到了,但是不在最开始的地方。
echo"v3=$v3"#abc1234zip1234abc
#没有替换。
v4=${v0/%123/000}#虽然匹配到了,但是不在最末尾的地方。
echo"v4=$v4"#abc1234zip1234abc
#没有替换。
exit0
${!varprefix*},${!varprefix@}
匹配先前声明过所有以varprefix作为变量名前缀的变量。
10.2参数替换
198
#这是带*@的间接引用的一种变换形式。
#Bash2.04版本中加入了这个特性。
xyz23=whatever
xyz23=
a=${!xyz*}#扩展为声明变量中以"xyz"
#^^^+开头变量名。
echo"a=$a"#a=xyz23xyz24
a=${!xyz@}#同上。
echo"a=$a"#a=xyz23xyz24
echo"---"
abc23=something_else
b=${!abc*}
echo"b=$b"#b=abc23
c=${!b}#这是我们熟悉的间接引用的形式。
echo$c#something_else
.如果在非交互的脚本中, $parameter为空,那么程序将会终止,并且返
错误码127(意为找不到命令)。
1
10.2参数替换
199
第十一章循环与分支
奥赛罗夫人,您为什么把这句话说了又说呢?
——《奥赛罗》,莎士比亚
本章目录
11.1循环
11.2嵌套循环
11.3循环控制
11.4测试与分支
对代码块的处理是结构化和构建shell脚本的关键。循环与分支结构恰好提供了这
样一种对代码块处理的工具。
11.循环与分支
200
11.1循环
循环是当循环控制条件为真时,一系列命令迭代 执行的代码块。
for循环
forargin[list]
这是shell中最基本的循环结构,它与C语言形式的循环有着明显的不同。
forargin[list]
do
command(s)...
done
在循环的过程中, arg会从list中连续获得每一个变量的值。
forargin"$var1""$var2""$var3"..."$varN"
#第一次循环中,arg=$var1
#第二次循环中,arg=$var2
#第三次循环中,arg=$var3
#...
#N次循环中,arg=$varN
#为了防止可能的字符分割问题,[list]中的参数都需要被引用。
参数list中允许含有通配符
如果dofor写在同一行时,需要在list之后加上一个分号。
forargin[list];do
样例11-1.简单的for循环
1
11.1循环
201
#!/bin/bash
#列出太阳系的所有行星。
forplanetinMercuryVenusEarthMarsJupiterSaturnUranusNep
tunePluto
do
echo$planet#每一行输出一个行星。
done
echo;echo
forplanetin"MercuryVenusEarthMarsJupiterSaturnUranusNe
ptunePluto"
#所有的行星都输出在一行上。
#整个'list'被包裹在引号中时是作为一个单一的变量。
#为什么?因为空格也是变量的一部分。
do
echo$planet
done
echo;echo"Whoops!Plutoisnolongeraplanet!"
exit0
[list]中的每一个元素中都可能含有多个参数。这在处理参数组中非常有用。在这种
情况下,使用set命令(查看样例15-16)强制解析[list]中的每一个元素,并
将元素的每一个部分分配给位置参数。
样例11-2. for循环[list]中的每一个变量有两个参数的情况
11.1循环
202
#!/bin/bash
#让行星再躺次枪。
#将每个行星与其到太阳的距离放在一起。
forplanetin"Mercury36""Venus67""Earth93""Mars142""Jup
iter483"
do
set--$planet#解析变量"planet"
#+并将其每个部分赋值给位置参数。
#"--"防止一些极端情况,比如$planet为空或者以破折号开头。
#因为位置参数会被覆盖掉,因此需要先保存原先的位置参数。
#你可以使用数组来保存
#original_params=("$@")
echo"$1$2,000,000milesfromthesum"
#-------两个制表符---将后面的一系列0连到参数$2上。
done
#(感谢S.C.做出的额外注释。)
exit0
一个单一变量也可以成为for循环中的[list]
样例11-3.文件信息:查看一个单一变量中含有的文件列表的文件信息
11.1循环
203
#!/bin/bash
#fileinfo.sh
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind"#你可能会感兴趣的一系列文件。
#包含一个不存在的文件,/usr/bin/fakefile
echo
forfilein$FILES
do
if[!-e"$file"]#检查文件是否存在。
then
echo"$filedoesnotexist.";echo
continue#继续判断下一个文件。
fi
ls-l$file|awk'{print$8"filesize:"$5}'#
输出其中的两个域。
whatis`basename$file`#文件信息。
#脚本正常运行需要注意提前设置好whatis的数据。
#使用root权限运行/usr/bin/makewhatis可以完成。
echo
done
exit0
for循环中的[list]可以是一个参数。
样例11-4.操作含有一系列文件的参数
11.1循环
204
#!/bin/bash
filename="*txt"
forfilein$filename
do
echo"Contentsof$file"
echo"---"
cat"$file"
echo
done
如果在匹配文件扩展名的for循环中的[list]含有通配符(*?),那么将会进
行文件名扩展。
样例11-5.for循环中操作文件
11.1循环
205
#!/bin/bash
#list-glob.sh:通过文件名扩展在for循环中产生[list]
#通配=文件名扩展。
echo
forfilein*
#^Bash在检测到通配表达式时,
#+会进行文件名扩展。
do
ls-l"$file"#列出$PWD(当前工作目录)下的所有文件。
#回忆一下,通配符"*"会匹配所有的文件名,
#+但是,在文件名扩展中,他将不会匹配以点开头的文件。
#如果没有匹配到文件,那么它将会扩展为它自身。
#为了防止出现这种情况,需要设置nullglob选项。
#+(shopt-snullglob)
#感谢S.C.
done
echo;echo
forfilein[jx]*
do
rm-f$file#删除当前目录下所有以"j""x"开头的文件。
echo"Removedfile\"$file\"".
done
echo
exit0
如果在for循环中省略in[list]部分,那么循环将会遍历位置参数
$@)。样例A-15中使用到了这一点。也可以查看样例15-17
样例11-6.缺少in[list]for循环
11.1循环
206
#!/bin/bash
#尝试在带参数和不带参数两种情况下调用这个脚本,观察发生了什么。
fora
do
echo-n"$a"
done
#缺失'inlist'的情况下,循环会遍历'$@'
#+(命令行参数列表,包括空格)。
echo
exit0
可以在for循环中使用命令代换生成[list]。查看样例16-54样例11-11
16-48
样例11-7.for循环中使用命令代换生成[list]
#!/bin/bash
#for-loopcmd.sh:带命令代换所生成[list]for循环
NUMBERS="973837.53"
fornumberin`echo$NUMBERS`#fornumberin973837.53
do
echo-n"$number"
done
echo
exit0
下面是使用命令代换生成[list]的更加复杂的例子。
样例11-8.一种替代grep搜索二进制文件的方法
11.1循环
207
#!/bin/bash
#bin-grep.sh:在二进制文件中定位匹配的字符串。
#一种替代`grep`搜索二进制文件的方法
#"grep-a"的效果类似
E_BADARGS=65
E_NOFILE=66
if[$#-ne2]
then
echo"Usage:`basename$0`search_stringfilename"
exit$E_BADARGS
fi
if[!-f"$2"]
then
echo"File\"$2\"doesnotexist."
exit$E_NOFILE
fi
IFS=$'\012'#按照AntonFilippov的意见应该是
#IFS="\n"
forwordin$(strings"$2"|grep"$1")
#"strings"命令列出二进制文件中的所有字符串。
#将结果通过管道输出到"grep"中,检查是不是匹配的字符串。
do
echo$word
done
#就像S.C.指出的那样,第23-30行可以换成下面的形式:
#strings"$2"|grep"$1"|tr-s"$IFS"'[\n*]'
#尝试运行脚本"./bin-grep.shmem/bin/ls"
exit0
11.1循环
208
下面的例子同样展示了如何使用命令代换生成[list]
样例11-9.列出系统中的所有用户
#!/bin/bash
#userlist.sh
PASSWORD_FILE=/etc/passwd
n=1#用户数量
fornamein$(awk'BEGIN{fs=":"}{print$1}'<"$PASSWORD_FILE")
#分隔符=:^^^^^^
#输出第一个域^^^^^^^^
#读取密码文件/etc/passwd^^^^^^^^^^^^^^^^^
do
echo"USER#$n=$name"
let"n+=1"
done
#USER#1=root
#USER#2=bin
#USER#3=daemon
#...
#USER#33=bozo
exit$?
#讨论:
#-----
#一个普通用户是如何读取/etc/passwd文件的?
#提示:检查/etc/passwd的文件权限。
#这算不算是一个安全漏洞?为什么?
另外一个关于[list]的例子也来自于命令代换。
样例11-10.检查目录中所有二进制文件的原作者
11.1循环
209
#!/bin/bash
#findstring.sh
#在指定目录的二进制文件中寻找指定的字符串。
directory=/usr/bin
fstring="FreeSoftwareFoundation"#查看哪些文件来自于FSF
forfilein$(find$directory-typef-name'*'|sort)
do
strings-f$file|grep"$fstring"|sed-e"s%$driectory%%"
#"sed"表达式中,你需要替换掉"/"分隔符,
#+因为"/"是一个会被过滤的字符。
#如果不做替换,将会产生一个错误。(你可以尝试一下。)
done
exit$?
#简单的练习:
#----------
#修改脚本,使其可以从命令行参数中获取$directory$fstring
最后一个关于[list]和命令代换的例子,但这个例子中的命令是一个函数
generate_list()
{
echo"onetwothree"
}
forwordin$(generate_list)#"word"获得函数执行的结果。
do
echo"$word"
done
#one
#two
#three
for循环的结果可以通过管道导向至一个或多个命令中。
11.1循环
210
样例11-11.列出目录中的所有符号链接。
#!/bin/bash
#symlinks.sh:列出目录中的所有符号链接。
directory=${1-`pwd`}
#如果没有特别指定,缺省目录为当前工作目录。
#等价于下面的代码块。
#---------------------------------------------------
#ARGS=1#只有一个命令行参数。
#
#if[$#-ne"$ARGS"]#如果不是只有一个参数的情况下
#then
#directory=`pwd`#设为当前工作目录。
#else
#directory=$1
#fi
#---------------------------------------------------
echo"symboliclinksindirectory\"$directory\""
forfilein"$(find$directory-type1)"#-type1=符号链接
do
echo"$file"
done|sort#否则文件顺序会是乱序。
#严格的来说这里并不需要使用循环,
#+因为"find"命令的输出结果已经被扩展成一个单一字符串了。
#然而,为了方便大家理解,我们使用了循环的方式。
#Dominik'Aeneas'Schnitzer指出,
#+不引用$(find$directory-type1)的话,
#脚本将在文件名包含空格时阻塞。
exit0
#--------------------------------------------------------
#JeanHelou提供了另外一种方法:
11.1循环
211
echo"symboliclinksindirectory\"$directory\""
#备份当前的内部字段分隔符。谨慎永远没有坏处。
OLDIFS=$IFS
IFS=:
forfilein$(find$directory-type1-printf"%p$IFS")
do#^^^^^^^^^^^^^^^^
echo"$file"
done|sort
#James"Mike"Conley建议将Helou的代码修改为:
OLDIFS=$IFS
IFS=''#空的内部字段分隔符意味着将不会分隔任何字符串
forfilein$(find$directory-type1)
do
echo$file
done|sort
#上面的代码可以在目录名包含冒号(前一个允许包含空格)
#+的情况下仍旧正常工作。
只需要对上一个样例做一些小小的改动,就可以把在标准输出stdout中的循环
重定向到文件中。
样例11-12.将目录中的所有符号链接保存到文件中。
11.1循环
212
#!/bin/bash
#symlinks.sh:列出目录中的所有符号链接。
OUTFILE=symlinks.list
directory=${1-`pwd`}
#如果没有特别指定,缺省目录为当前工作目录。
echo"symboliclinksindirectory\"$directory\"">"$OUTFILE"
echo"---------------------------">>"$OUTFILE"
forfilein"$(find$directory-type1)"#-type1=符号链接
do
echo"$file"
done|sort>>"$OUTFILE"#stdout的循环结果
#^^^^^^^^^^^^^重定向到文件。
#echo"Outputfile=$OUTFILE"
exit$?
还有另外一种看起来非常像C语言中循环那样的语法。你需要使用到双圆括号
法。
样例11-13.C语言风格的循环
#!/bin/bash
#用多种方式数到10
echo
#基础版
forain12345678910
do
echo-n"$a"
11.1循环
213
done
echo;echo
#+==========================================+
#使用"seq"
forain`seq10`
do
echo-n"$a"
done
echo;echo
#+==========================================+
#使用大括号扩展语法
#Bash3+版本有效。
forain{1..10}
do
echo-n"$a"
done
echo;echo
#+==========================================+
#现在用类似C语言的语法再实现一次。
LIMIT=10
for((a=1;a<=LIMIT;a++))#双圆括号语法,不带$LIMIT
do
echo-n"$a"
done#ksh93中学习到的特性。
echo;echo
#+==========================================+
11.1循环
214
#我们现在使用C语言中的逗号运算符来使得两个变量同时增加。
for((a=1,b=1;a<=LIMIT;a++,b++))
do#逗号连接操作。
echo-n"$a-$b"
done
echo;echo
exit0
还可以查看样例27-16样例27-17样例A-6
---
接下来,我们将展示在真实环境中应用的循环。
样例11-14.在批处理模式下使用efax
#!/bin/bash
#传真(必须提前安装了'efax'模块)。
EXPECTED_ARGS=2
E_BADARGS=85
MODEM_PORT="/dev/ttyS2"#你的电脑可能会不一样。
#^^^^^PCMCIA调制解调卡缺省端口。
if[$#-ne$EXPECTED_ARGS]
#检查是不是传入了适当数量的命令行参数。
then
echo"Usage:`basename$0`phone#text-file"
exit$E_BADARGS
fi
if[!-f"$2"]
then
echo"File$2isnotatextfile."
#File不是一个正常文件或者文件不存在。
exit$E_BADARGS
11.1循环
215
fi
faxmake$2#根据文本文件创建传真格式文件。
forfilein$(ls$2.0*)#连接转换后的文件。
#在参数列表中使用通配符(文件名通配)。
do
fil="$fil$file"
done
efax-d"$MODEM_PORT"-t"T$1"$fil#最后使用efax
#如果上面一行执行失败,尝试添加-o1
#S.C.指出,上面的for循环可以被压缩为
#efax-d/dev/ttyS2-o1-t"T$1"$2.0*
#+但是这并不是一个好主意。
exit$?#efax同时也会将诊断信息传递给标准输出。
11.1循环
216
关键字dodone圈定了for循环代码块的范围。但是在一些特殊的
情况下,也可以被大括号取代。
for((n=1;n<=10;n++))
#没有do
{
echo-n"*$n*"
}
#没有done
#输出:
#*1**2**3**4**5**6**7**8**9**10*
#并且echo$?返回0,因此Bash并不认为这是一个错误。
echo
#但是注意在典型的for循环fornin[list]...中,
#+需要在结尾加一个分号。
fornin123
{echo-n"$n";}
#^
#感谢Yongye指出这一点。
while循环
while循环结构会在循环顶部检测循环条件,若循环条件为真(退出状态0
则循环持续进行。与for循环不同的是, while循环是在不知道循环次数的
情况下使用的。
11.1循环
217
while[condition]
do
command(s)...
done
while循环结构中,你不仅可以使用像if/test中那样的括号结构,也可
以使用用途更广泛的双括号结构while[[condition]])。
就像在for循环中那样,将do和循环条件放在同一行时需要加一个分号。
while[condition];do
while循环中,括号结构并不是必须存在的。比如说getopts结构
样例11-15.简单的while循环
#!/bin/bash
var0=0
LIMIT=10
while["$var0"-lt"$LIMIT"]
#^^
#必须有空格,因为这是测试结构
do
echo-n"$var0"#-n不会另起一行
#^空格用来分开输出的数字。
var0=`expr$var0+1`#var0=$(($var0+1))效果相同。
#var0=$((var0+1))效果相同。
#let"var0+=1"效果相同。
done#还有许多其他的方法也可以达到相同的效果。
echo
exit0
样例11-16.另一个例子
11.1循环
218
#!/bin/bash
echo
#等价于:
while["$var1"!="end"]#whiletest"$var1"!="end"
do
echo"Inputvariable#1(endtoexit)"
readvar1#不是'read$var1'(为什么?)。
echo"variable#1=$var1"#因为存在"#",所以需要使用引号。
#如果输入的是"end",也将会在这里输出。
#在结束本轮循环之前都不会再测试循环条件了。
echo
done
exit0
一个while循环可以有多个测试条件,但只有最后的那一个条件决定了循环是否
终止。这是一种你需要注意到的不同于其他循环的语法。
样例11-17.多条件while循环
11.1循环
219
#!/bin/bash
var1=unset
previous=$var1
whileecho"previous-variable=$previous"
echo
previous=$var1
["$var1"!=end]#记录下$var1之前的值。
#while循环中有4个条件,但只有最后的那个控制循环。
#最后一个条件的退出状态才会被记录。
do
echo"Inputvariable#1(endtoexit)"
readvar1
echo"variable#1=$var1"
done
#猜猜这是怎样实现的。
#这是一个很小的技巧。
exit0
就像for循环一样,while循环也可以使用双圆括号结构写得像C语言那样
(也可以查看样例8-5)。
样例11-18.C语言风格的while循环
11.1循环
220
#!/bin/bash
#wh-loopc.sh:"while"循环中计数到10
LIMIT=10#循环10次。
a=1
while["$a"-le$LIMIT]
do
echo-n"$a"
let"a+=1"
done#没什么好奇怪的吧。
echo;echo
#+==============================================+
#现在我们用C语言风格再写一次。
((a=1))#a=1
#双圆括号结构允许像C语言一样在赋值语句中使用空格。
while((a<=LIMIT))#双圆括号结构,
do#+并且没有使用"$"
echo-n"$a"
((a+=1))#let"a+=1"
#是的,就是这样。
#双圆括号结构允许像C语言一样自增一个变量。
done
echo
#这可以让CJava程序猿感觉更加舒服。
exit0
在测试部分, while循环可以调用函数
11.1循环
221
t=0
condition()
{
((t++))
if[$t-lt5]
then
return0#true
else
return1#false
fi
}
whilecondition
#^^^^^^^^^
#调用函数循环四次。
do
echo"Stillgoing:t=$t"
done
#Stillgoing:t=1
#Stillgoing:t=2
#Stillgoing:t=3
#Stillgoing:t=4
if测试结构一样, while循环也可以省略括号。
whilecondition
do
command(s)...
done
while循环中结合read命令,我们就得到了一个非常易于使用的while
read结构。它可以用来读取和解析文件。
11.1循环
222
cat$filename|#从文件获得输入。
whilereadline#只要还有可以读入的行,循环就继续。
do
...
done
#====================摘自样例脚本"sd.sh"===================#
whilereadvalue#一次读入一个数据。
do
rt=$(echo"scale=$SC;$rt+$value"|bc)
((ct++))
done
am=$(echo"scale=$SC;$rt/$ct"|bc)
echo$am;return$ct#这个功能返回2个值。
#注意:这个技巧在$ct>255的情况下会失效。
#如果要操作更大的数字,注释掉上面的"return$ct"就可以了。
}<"$datafile"#传入数据文件。
while循环后面可以通过<将标准输入重定位到文件中。while
循环同样可以通过管道传入标准输入中。
until
while循环相反, until循环测试其顶部的循环条件,直到其中的条件为真
时停止。
until[condition-is-true]
do
commands(s)...
done
注意到,跟其他的一些编程语言不同, until循环的测试条件在循环顶部。
就像在for循环中那样,将do和循环条件放在同一行时需要加一个分号。
11.1循环
223
until[condition-is-true];do
样例11-19. until循环
#!/bin/bash
END_CONDITION=end
until["$var1"="$END_CONDITION"]
#在循环顶部测试条件。
do
echo"Inputvariable#1"
echo"($END_CONDITIONtoexit)"
readvar1
echo"variable#1=$var1"
echo
done
#---#
#就像"for""while"循环一样,
#+"until"循环也可以写的像C语言一样。
LIMIT=10
var=0
until((var>LIMIT))
do#^^^^^^没有方括号,没有$前缀。
echo-n"$var"
((var++))
done#012345678910
exit0
如何在forwhileuntil之间做出选择?我们知道在C语言中,在已知
循环次数的情况下更加倾向于使用for循环。但是在Bash中情况可能更加复杂
一些。Bash中的for循环相比起其他语言来说,结构更加松散,使用更加灵
活。因此使用你认为最简单的就好。
1
11.1循环
224
.迭代:重复执行一个或一组命令。通常情况下,会使用 while
until进行控制。
1
11.1循环
225
11.2嵌套循环
嵌套循环,顾名思义就是在循环里面还有循环。外层循环会不断的触发内层循环直
到外层循环结束。当然,你仍然可以使用break可以终止外层或内层的循环。
样例11-20.嵌套循环
#!/bin/bash
#nested-loop.sh:嵌套"for"循环。
outer=1#设置外层循环计数器。
#外层循环。
forain12345
do
echo"Pass$outerinouterloop."
echo"---------------------"
inner=1#重设内层循环计数器。
#=====================================
#内层循环。
forbin12345
do
echo"Pass$innerininnerloop."
let"inner+=1"#增加内层循环计数器。
done
#内层循环结束。
#=====================================
let"outer+=1"#增加外层循环计数器。
echo#在每次外层循环输出中加入空行。
done
#外层循环结束。
exit0
11.2嵌套循环
226
查看样例27-11详细了解嵌套while循环。查看样例27-13详细了解嵌套until
11.2嵌套循环
227
11.3循环控制
Tournezcenttours,tournezmilletours,
Tournezsouventettourneztoujours...
——保尔·魏尔伦,《木马》
本节介绍两个会影响循环行为的命令。
break,continue
breakcontinue命令 的作用和在其他编程语言中的作用一样。 break
用来中止(跳出)循环,而continue则是略过未执行的循环部分,直接进行下
一次循环。
样例11-21.循环中breakcontinue的作用
#!/bin/bash
LIMIT=19#循环上界
echo
echo"PrintingNumbers1through20(butnot3and11)."
a=0
while[$a-le"$LIMIT"]
do
a=$(($a+1))
if["$a"-eq3]||["$a"-eq11]#除了311
then
continue#略过本次循环的剩余部分。
fi
echo-n"$a"#a等于311时,将不会执行这条语句。
done
1
11.3循环控制
228
#思考:
#为什么循环不会输出到20
echo;echo
echoPrintingNumbers1through20,butsomethinghappensafter
2.
################################################################
##
#'break'代替了'continue'
a=0
while["$a"-le"$LIMIT"]
do
a=$(($a+1))
if["$a"-gt2]
then
break#中止循环。
fi
echo-n"$a"
done
echo;echo;echo
exit0
break命令接受一个参数。普通的break命令仅仅跳出其所在的那层循环,而
breakN命令则可以跳出其上N层的循环。
样例11-22.跳出多层循环
11.3循环控制
229
#!/bin/bash
#break-levels.sh:跳出循环.
#"breakN"跳出N层循环。
forouterloopin12345
do
echo-n"Group$outerloop:"
#------------------------------------------
forinnerloopin12345
do
echo-n"$innerloop"
if["$innerloop"-eq3]
then
break#尝试一下break2看看会发生什么。
#(它同时中止了内层和外层循环。)
fi
done
#------------------------------------------
echo
done
echo
exit0
break类似, continue也接受一个参数。普通的continue命令仅仅影
响其所在的那层循环,而continueN命令则可以影响其上N层的循环。
样例11-23. continue影响外层循环
11.3循环控制
230
#!/bin/bash
#"continueN"命令可以影响其上N层循环。
forouterinIIIIIIIVV#外层循环
do
echo;echo-n"Group$outer:"
#------------------------------------------------------------
--------
forinnerin12345678910#内层循环
do
if[["$inner"-eq7&&"$outer"="III"]]
then
continue2#影响两层循环,包括外层循环
#将其替换为普通的"continue",那么只会影响内层循环。
fi
echo-n"$inner"#78910将不会出现在"GroupIII."中。
done
#------------------------------------------------------------
--------
done
echo;echo
#思考:
#想一个"continueN"在脚本中的实际应用情况。
exit0
样例11-24.真实环境中的continueN
#AlbertReiner举出了一个如何使用"continueN"的例子:
#---------------------------------------------------
#如果我有许多任务需要运行,并且运行所需要的数据都以文件的形
#+式存在文件夹中。现在有多台设备可以访问这个文件夹,我想将任
11.3循环控制
231
#+务分配给这些不同的设备来完成。
#那么我通常会在每台设备上执行下面的代码:
whiletrue:
do
fornin.iso.*
do
["$n"=".iso.opts"]&&continue
beta=${n#.iso.}
[-r.Iso.$beta]&&continue
[-r.lock.$beta]&&sleep10&&continue
lockfile-r0.lock.$beta||continue
echo-n"$beta:"`date`
run-isotherm$beta
date
ls-alF.Iso.$beta
[-r.Iso.$beta]&&rm-rf.lock.$beta
continue2
done
break
done
exit0
#这个脚本中出现的sleepN只针对这个脚本,通常的形式是:
whiletrue
do
forjobin{pattern}
do
{jobalreadydoneorrunning}&&continue
{markjobasrunning,dojob,markjobasdone}
continue2
done
break#或者使用类似`sleep600`这样的语句来防止脚本结束。
done
#这样做可以保证脚本只会在没有任务时(包括在运行过程中添加的任务)
#+才会停止。合理使用文件锁保证多台设备可以无重复的并行执行任务(这
#+在我的设备上通常会消耗好几个小时,所以我想避免重复计算)。并且,
11.3循环控制
232
#+因为每次总是从头开始搜索文件,因此可以通过文件名决定执行的先后
#+顺序。当然,你可以不使用'continue2'来完成这些,但是你必须
#+添加代码去检测某项任务是否完成(以此判断是否可以执行下一项任务或
#+终止、休眠一段时间再执行下一项任务)。
continueN结构不易理解并且可能在一些情况下有歧义,因此不建议使
用。
.这两个命令是内建命令,而另外的循环命令,如whilecase则是
关键词
1
11.3循环控制
233
11.4测试与分支
caseselect结构并不属于循环结构,因为它们并没有反复执行代码块。但
是和循环结构相似的是,它们会根据代码块顶部或尾部的条件控制程序流。
下面介绍两种在代码块中控制程序流的方法:
case(in)/ esac
shell脚本中, case模拟了C/C++语言中的switch,可以根据条件跳转到
其中一个分支。其相当于简写版的if/then/else语句。很适合用来创建菜单选
项哟!
case"$variable"in
"$condition1")
command...
;;
"$condition2")
command...
;;
esac
对变量进行引用不是必须的,因为在这里不会进行字符分割。
条件测试语句必须以右括号)结束。
每一段代码块都必须以双分号;;结束。
如果测试条件为真,其对应的代码块将被执行,而后整个case代码段
结束执行。
case代码段必须以esac结束(倒着拼写case)。
样例11-25.如何使用case
1
11.4测试与分支
234
#!/bin/bash
#测试字符的种类。
echo;echo"Hitakey,thenhitreturn."
readKeypress
case"$Keypress"in
[[:lower:]])echo"Lowercaseletter";;
[[:upper:]])echo"Uppercaseletter";;
[0-9])echo"Digit";;
*)echo"Punctuation,whitespace,orother";;
esac#字符范围可以用[方括号]表示,也可以用POSIX形式的[[双方括号
]]表示。
#在这个例子的第一个版本中,用来测试是小写还是大写字符使用的是[a-z][A
-Z]
#这在一些特定的语言环境和Linux发行版中不起效。
#POSIX形式具有更好的兼容性。
#感谢FrankWang指出这一点。
#练习:
#-----
#这个脚本接受一个单字符然后结束。
#修改脚本,使得其可以循环接受输入,并且检测键入的每一个字符,直到键入"X"
为止。
#提示:将所有东西包在"while"中。
exit0
样例11-26.使用case创建菜单
#!/bin/bash
#简易的通讯录数据库
clear#清屏。
echo"ContactList"
echo"-----------"
11.4测试与分支
235
echo"Chooseoneofthefollowingpersons:"
echo
echo"[E]vans,Roland"
echo"[J]ones,Mildred"
echo"[S]mith,Julie"
echo"[Z]ane,Morris"
echo
readperson
case"$person"in
#注意变量是被引用的。
"E"|"e")
#同时接受大小写的输入。
echo
echo"RolandEvans"
echo"4321FlashDr."
echo"Hardscrabble,CO80753"
echo"(303)734-9874"
echo"(303)734-9892fax"
echo"revans@zzy.net"
echo"Businesspartner&oldfriend"
;;
#注意用双分号结束这一个选项。
"J"|"j")
echo
echo"MildredJones"
echo"249E.7thSt.,Apt.19"
echo"NewYork,NY10009"
echo"(212)533-2814"
echo"(212)533-9972fax"
echo"milliej@loisaida.com"
echo"Ex-girlfriend"
echo"Birthday:Feb.11"
;;
#SmithZane的信息稍后添加。
11.4测试与分支
236
*)
#缺省设置。
#空输入(直接键入回车)也是执行这一部分。
echo
echo"Notyetindatabase."
;;
esac
echo
#练习:
#-----
#修改脚本,使得其可以循环接受多次输入而不是只显示一个地址后终止脚本。
exit0
你可以用case来检测命令行参数。
#!/bin/bash
case"$1"in
"")echo"Usage:${0##*/}<filename>";exit$E_PARAM;;
#没有命令行参数,或者第一个参数为空。
#注意${0##*/}是参数替换${var##pattern}
一种形式。
#最后的结果是$0.
-*)FILENAME=./$1;;#如果传入的参数以短横线开头,那么将其替换为./$1
#+以避免后续的命令将其解释为一个选项。
*)FILENAME=$1;;#否则赋值为$1
esac
下面是一个更加直观的处理命令行参数的例子:
11.4测试与分支
237
#!/bin/bash
while[$#-gt0];do#遍历完所有参数
case"$1"in
-d|--debug)
#检测是否是"-d"或者"--debug"
DEBUG=1
;;
-c|--conf)
CONFFILE="$2"
shift
if[!-f$CONFFILE];then
echo"Error:Suppliedfiledoesn'texist!"
exit$E_CONFFILE#找不到文件。
fi
;;
esac
shift#检测下一个参数
done
#摘自StefanoFalsetto"Log2Rot"脚本中"rottlog"包的一部分。
#已授权使用。
样例11-27.使用命令替换生成case变量
#!/bin/bash
#case-cmd.sh:使用命令替换生成"case"变量。
case$(arch)in#$(arch)返回设备架构。
#等价于'uname-m"
i386)echo"80386-basedmachine";;
i486)echo"80486-basedmachine";;
i586)echo"Pentium-basedmachine";;
i686)echo"Pentium2+-basedmachine";;
*)echo"Othertypeofmachine";;
esac
exit0
11.4测试与分支
238
case还可以用来做字符串模式匹配。
样例11-28.简单的字符串匹配
11.4测试与分支
239
#!/bin/bash
#match-string.sh:使用'case'结构进行简单的字符串匹配。
match_string()
{#字符串精确匹配。
MATCH=0
E_NOMATCH=90
PARAMS=2#需要2个参数。
E_BAD_PARAMS=91
[$#-eq$PARAMS]||return$E_BAD_PARAMS
case"$1"in
"$2")return$MATCH;;
*)return$E_NOMATCH;;
esac
}
a=one
b=two
c=three
d=two
match_string$a#参数个数不够
echo$?#91
match_string$a$b#匹配不到
echo$?#90
match_string$a$d#匹配成功
echo$?#0
exit0
样例11-29.检查输入
11.4测试与分支
240
#!/bin/bash
#isaplpha.sh:使用"case"结构检查输入。
SUCCESS=0
FAILURE=1#以前是FAILURE=-1,
#+但现在Bash不允许返回负值。
isalpha()#测试字符串的第一个字符是否是字母。
{
if[-z"$1"]#检测是否传入参数。
then
return$FAILURE
fi
case"$1"in
[a-zA-Z]*)return$SUCCESS;;#是否以字母形式开始?
*)return$FAILURE;;
esac
}#可以与C语言中的函数"isalpha()"作比较。
isalpha2()#测试整个字符串是否都是字母。
{
[$#-eq1]||return$FAILURE
case$1in
*[!a-zA-Z]*|"")return$FAILURE;;
*)return$SUCCESS;;
esac
}
isdigit()#测试整个字符串是否都是数字。
{#换句话说,也就是测试是否是一个整型变量。
[$#-eq1]||return$FAILURE
case$1in
*[!0-9]*|"")return$FAILURE;;
*)return$SUCCESS;;
esac
11.4测试与分支
241
}
check_var()#包装后的isalpha()
{
ifisalpha"$@"
then
echo"\"$*\"beginswithanalphacharacter."
ifisalpha2"$@"
then#其实没必要检查第一个字符是不是字母。
echo"\"$*\"containsonlyalphacharacters."
else
echo"\"$*\"containsatleastonenon-alphacharacter."
fi
else
echo"\"$*\"beginswithanon-alphacharacter."
#如果没有传入参数同样同样返回存在非字母
fi
echo
}
digit_check()#包装后的isdigit()
{
ifisdigit"$@"
then
echo"\"$*\"containsonlydigits[0-9]."
else
echo"\"$*\"hasatleastonenon-digitcharacter."
fi
echo
}
a=23skidoo
b=H3llo
11.4测试与分支
242
c=-What?
d=What?
e=$(echo$b)#命令替换。
f=AbcDef
g=27234
h=27a34
i=27.34
check_var$a
check_var$b
check_var$c
check_var$d
check_var$e
check_var$f
check_var#如果不传入参数会发送什么?
#
digit_check$g
digit_check$h
digit_check$i
exit0#S.C.改进了本脚本。
#练习:
#-----
#写一个函数'isfloat()'来检测输入值是否是浮点数。
#提示:可以参考函数'isdigit()',在其中加入检测合法的小数点即可。
select
select结构是学习自KornShell。其同样可以用来构建菜单。
selectvariable[inlist]
do
command...
break
done
11.4测试与分支
243
而效果则是终端会提示用户输入列表中的一个选项。注意, select默认使用提
示字串3PromptString3$PS3,#?),但同样可以被修改。
样例11-30.使用select创建菜单
#!/bin/bash
PS3='Chooseyourfavoritevegetable:'#设置提示字串。
#否则默认为#?
echo
selectvegetablein"beans""carrots""potatoes""onions""rutab
agas"
do
echo
echo"Yourfavoriteveggieis$vegetable."
echo"Yuck!"
echo
break#如果没有'break'会发生什么?
done
exit
#练习:
#-----
#修改脚本,使得其可以接受其他输入而不是"select"语句中所指定的。
#例如,如果用户输入"peas,",那么脚本会通知用户"Sorry.Thatisnoto
nthemenu."
如果inlist被省略,那么select将会使用传入脚本的命令行参数( $@)或者
传入函数的参数作为list
可以与forvariable[inlist]inlist被省略的情况做比较。
样例11-31.在函数中使用select创建菜单
11.4测试与分支
244
#!/bin/bash
PS3='Chooseyourfavoritevegetable:'
echo
choice_of()
{
selectvegetable
#[inlist]被省略,因此'select'将会使用传入函数的参数作为list
do
echo
echo"Yourfavoriteveggieis$vegetable."
echo"Yuck!"
echo
break
done
}
choice_ofbeansricecarrortsradishesrutabagaspinach
#$1$2$3$4$5$6
#传入了函数choice_of()
exit0
还可以参照样例37-3
1
11.4测试与分支
245
.在写匹配行的时候,可以在左边加上左括号(,使整个结构看起来更加优
雅。
case$(arch)in#$(arch)返回设备架构。
(i386)echo"80386-basedmachine";;
#^^
(i486)echo"80486-basedmachine";;
(i586)echo"Pentium-basedmachine";;
(i686)echo"Pentium2+-basedmachine";;
(*)echo"Othertypeofmachine";;
esac
1
11.4测试与分支
246
第十二章命令替换
命令替换重新指定一个 或多个命令的输出。其实就是将命令的输出导到另外一个
地方 。
命令替换的通常形式是( `...`),即用反引号引用命令。
script_name=`basename$0`
echo"Thenameofthisscriptis$scirpt_name."
命令的输出可以作为另一个命令的参数,也可以赋值给一个变量。甚至在for
环中可以用输出产生参数表。
rm`catfilename`#"filename"中包含了一系列需要被删除的文件名。
#
#S.C.指出这样写可能会导致出现"arglisttoolong"的错误。
#更好的写法应该是xargsrm--<filename
#--可以在"filename"文件名以"-"为开头时仍旧正常执行
textfile_listing=`ls*.txt`
#变量中包含了当前工作目录下所有的名为*.txt的文件。
echo$textfile_listing
textfile_listing2=$(ls*.txt)#命令替换的另一种形式。
echo$textfile_listing2
#结果相同。
#这样将一系列文件名赋值给一个单一字符串可能会出现换行。
#
#而更加安全的方式是将这一系列文件存入数组。
#shopt-snullglob#设置后,如果没有匹配到文件,那么变量会被
赋值为空。
#textfile_listing=(*.txt)
#
#感谢S.C.
1
2
12.命令替换
247
命令替换本质上是调用了一个子进程来执行。
命令替换有可能会出现字符分割的情况。
COMMAND`echoab`#2个参数:ab
COMMAND"`echoab`"#1个参数:"ab"
COMMAND`echo`#没有参数
COMMAND"`echo`"#一个空参数
#感谢S.C.
但即使不存在字符分割的情况,使用命令替换也会出现丢失尾部换行符的情
况。
12.命令替换
248
#cd"`pwd`"#你是不是认为这条语句在任何情况下都不会出现错误?
#但事实却不是这样的。
mkdir'dirwithtrailingnewline
'
cd'dirwithtrailingnewline
'
cd"`pwd`"#Bash会出现如下错误提示:
#bash:cd:/tmp/filewithtrailingnewline:Nosuchfileor
directory
cd"$PWD"#这样写是对的。
old_tty_setting=$(stty-g)#保存旧的设置。
echo"Hitakey"
stty-icanon-echo#禁用终端的canonical模式。
#同时禁用echo
key=$(ddbs=1count=12>/dev/null)#使用'dd'获得键值。
stty"$old_tty_setting"#恢复旧的设置。
echo"Youhit${#key}key."#${#variable}表示$variable
的字符个数。
#
#除了按下回车键外,其余情况都会输出"Youhit1key."
#按下回车键会输出"Youhit0key."
#因为唯一的换行符在命令替换中被丢失了。
#这段代码摘自StéphaneChazelas
使用echo输出未被引用的命令代换的变量时会删掉尾部的换行。这可能
会导致非常不好的情况出现。
12.命令替换
249
dir_listing=`ls-l`
echo$dir_listing#未被引用
#你希望会出现按行显示出文件列表。
#但是,你却看到了:
#total3-rw-rw-r--1bozobozo30May1317:151.txt-rw-r
w-r--1bozo
#bozo51May1520:57t2.sh-rwxr-xr-x1bozobozo217Mar
521:13wi.sh
#所有换行都消失了。
echo"$dir_listing"#被引用
#-rw-rw-r--1bozo30May1317:151.txt
#-rw-rw-r--1bozo51May1520:57t2.sh
#-rwxr-xr-x1bozo217Mar521:13wi.sh
你甚至可以使用重定向或者cat命令把一个文件的内容通过命令代换赋值给一
个变量。
variable1=`<file1`#"file1"的内容赋值给variable1
variable2=`catfile2`#"file2"的内容赋值给variable2
#使用cat命令会开一个新进程,因此执行速度会比
重定向慢。
#需要注意的是,这些变量中可能包含一些空格或者控制字符。
#无需显示的赋值给一个变量。
echo"`<$0`"#输出脚本自身。
12.命令替换
250
#摘录自系统文件/etc/rc.d/rc.sysinit
#+RedHatLinux发行版)
if[-f/fsckoptions];then
fsckoptions=`cat/fsckoptions`
...
fi
#
#
if[-e"/proc/ide/${disk[$device]}/media"];then
hdmedia=`cat/proc/ide/${disk[$device]}/media`
...
fi
#
#
if[!-n"`uname-r|grep--"-"`"];then
ktag="`cat/proc/version`"
...
fi
#
#
if[$usb="1"];then
sleep5
mouseoutput=`cat/proc/bus/usb/devices2>/dev/null|grep-E"
^I.*Cls=03.*Prot=02"`
kbdoutput=`cat/proc/bus/usb/devices2>/dev/null|grep-E"^I
.*Cls=03.*Prot=01"`
...
fi
12.命令替换
251
尽量不要将一大段文字赋值给一个变量,除非你有足够的理由。也绝不要
将一个二进制文件的内容赋值给一个变量。
样例12-1.蠢蠢的脚本
#!/bin/bash
#stupid-script-tricks.sh:不要在自己的电脑上尝试。
#摘自"StupidScriptTricks"卷一。
exit99###如果你有胆,就注释掉这行。:)
dangerous_variable=`cat/boot/vmlinuz`#压缩的Linux内核。
echo"string-lengthof\$dangerous_variable=${#dangerous_v
ariable}"
#$dangerous_variable的长度为794151
#(更新版本的内核可能更大。)
#'wc-c/boot/vmlinuz'的结果不同。
#echo"$dangerous_variable"
#不要作死。否则脚本会挂起。
#将二进制文件的内容赋值给一个变量没有任何意义。
exit0
尽管脚本会挂起,但并不会出现缓存溢出的情况。而这正是像Bash这样的解
释型语言相比起编译型语言能够提供更多保护的一个例子。
命令替换允许将循环的输出结果赋值给一个变量。这其中的关键在于循环内部的
echo命令。
样例12-2.将循环的输出结果赋值给变量
12.命令替换
252
#!/bin/bash
#csubloop.sh:将循环的输出结果赋值给变量。
variable1=`foriin12345
do
echo-n"$i"#在这里,'echo'命令非常关键。
done`
echo"variable1=$variable1"#variable1=12345
i=0
variable2=`while["$i"-lt10]
do
echo-n"$i"#很关键的'echo'
let"i+=1"#i自增。
done`
echo"variable2=$variable2"#variable2=0123456789
#这个例子表明可以在变量声明时嵌入循环。
exit0
命令替换能够让Bash做更多的事情。而这仅仅需要在书写程序或者脚本时将
结果输出到标准输出stdout中,然后将这些输出结果赋值给变量即可。
#include<stdio.h>
/*"Hello,world."Cprogram*/
intmain()
{
printf("Hello,world.\n");
return(0);
}
12.命令替换
253
bash$gcc-0hellohello.c
#!/bin/bash
#hello.sh
greeting=`./hello`
echo$greeting
bash$shhello.sh
Hello,world.
在命令替换中,你可以使用$(...)来替代反引号。
output=$(sed-n/"$1"/p$file)#摘自"grp.sh"
#将文本文件的内容赋值给一个变量。
File_contents1=$(cat$file1)
File_contents2=$(<$file2)#这么做也是可以的。
$(...)和反引号在处理双反斜杠上有所不同。
bash$echo`echo\\`
bash$echo$(echo\\)
\
$(...)允许嵌套。
word_count=$(wc-w$(echo*|awk'{print$8}'))
样例12-3.寻找变位词(anagram
#!/bin/bash
3
12.命令替换
254
#agram2.sh
#嵌套命令替换的例子。
#其中使用了作者写的工具包"yawl"中的"anagram"工具。
#http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#http://bash.deta.in/yawl-0.3.2.tar.gz
E_NOARGS=86
E_BADARG=87
MINLEN=7
if[-z"$1"]
then
echo"Usage$0LETTERSET"
exit$E_NOARGS#脚本需要命令行参数。
elif[${#1}-lt$MINLEN]
then
echo"Argumentmusthaveatleast$MINLENletters."
exit$E_BADARG
fi
FILTER='.......'#至少需要7个字符。
#1234567
Anagrams=($(echo$(anagram$1|grep$FILTER)))
#$($(嵌套命令集))
#(赋值给数组)
echo
echo"${#Anagrams[*]}7+letteranagramsfound"
echo
echo${Anagrams[0]}#第一个变位词。
echo${Anagrams[1]}#第二个变位词。
#以此类推。
#echo"${Anagrams[*]}"#将所有变位词在一行里面输出。
#可以配合后面的数组章节来理解上面的代码。
12.命令替换
255
#建议同时查看另一个寻找变位词的脚本agram.sh
exit$?
以下是包含命令替换的样例:
1. 样例11-8
2. 样例11-27
3. 样例9-16
4. 样例16-3
5. 样例16-22
6. 样例16-17
7. 样例16-54
8. 样例11-14
9. 样例11-11
10. 样例16-32
11. 样例20-8
12. 样例A-16
13. 样例29-3
14. 样例16-47
15. 样例16-48
16. 样例16-49
.在命令替换中可以使用外部系统命令,内建命令甚至是脚本函数
.从技术的角度来讲,命令替换实际上是获得了命令输出到标准输出的结果,
然后通过赋值号将结果赋值给一个变量。
.事实上,使用反引号进行嵌套也是可行的。但是JohnDefault提醒到需要将
内部的反引号进行转义。
word_count=\`wc-w\\\`echo*|awk'{print$8}'\\\`
\`
1
2
3
12.命令替换
256
第十三章算术扩展
算术扩展为脚本中的(整数)算术操作提供了强有力的工具。你可以使用反引号、
双圆括号或者let将字符串转换为数学表达式。
差异比较
使用反引号的算术扩展(通常与expr一起使用)
z=`expr$z+3`#'expr'命令执行了算术扩展。
使用双圆括号let的算术扩展。
事实上,在算术扩展中,反引号已经被双圆括号((...))$((...))以及
let所取代。
13.算术扩展
257
z=$(($z+3))
z=$((z+3))#同样正确。
#在双圆括号内,参数引用形式可用可不用。
#$((EXPRESSION))是算术扩展。#不要与命令替换混淆。
#双圆括号不是只能用作赋值算术结果。
n=0
echo"n=$n"#n=0
((n+=1))#自增。
#(($n+=1))是错误用法!
echo"n=$n"#n=1
letz=z+3
let"z+=3"#引号允许在赋值表达式中使用空格。
#'let'事实上执行的算术运算而非算术扩展。
以下是包含算术扩展的样例:
1. 样例16-9
2. 样例11-15
3. 样例27-1
4. 样例27-11
5. 样例A-16
13.算术扩展
258
第十四章休息时间
作者开始玩不转不是外国人的游戏了。亲爱的读者可以藉此休息一下,如果可以,
请帮助我们推广一下本书原作和译作。
原作作者致所有读者
各位Linux用户,你们好!你们现在正阅读的这本书能够给你们带来好运。
所以赶紧打开你们的邮箱,将本文的访问链接发给你的10位朋友。
但是在发邮件之前,记得粘贴一段大约100行的Bash脚本在邮件后面。
千万不要打断这个传递,并且一定要在48小时内发送邮件!
布鲁克林区的WilfredP.没有发出10封邮件。当他第三天起床时发现他变成了一名
COBOL程序员。
纽波特纽斯港的HowardL.按时发出了10封邮件。然后一个月内,他就有了足够的
硬件来搭建一个100个节点的Beowulf集群来玩Tuxracer
芝加哥的AmeliaV.看到以后不屑一顾,置之不理。不久之后,她的终端炸了。现
在她不得不为微软工作,撰写文档。
千万不要打断这个传递!马上去发邮件吧!
Courtesy'NIX"fortunecookies",withsomealterationsandmanyapologies
14.休息时间
259
第五部分高级话题
目录
18.正则表达式
18.1正则表达式简介
18.2文件名替换
19.嵌入文档
20.I/O重定向
20.1使用exec
20.2重定向代码块
20.3应用程序
22.限制模式的Shell
24.函数
24.1复杂函数和函数复杂性
24.2局部变量
24.3不适用局部变量的递归
25.别名
27.数组
30.网络编程
33.选项
34.陷阱
38.后记
38.1作者后记
38.2关于作者
38.3从哪里可以获得帮助
38.4用来制作这本书的工具
38.5致谢
38.6免责声明
第五部分进阶话题
260
19嵌入文档
Hereandnow,boys.    --AldousHuxley,Island
嵌入文档是一段有特殊作用的代码块,它用I/O重定向在交互程序和交互命令中传
递和反馈一个命令列表,例如ftpcat或者是ex文本编辑器
COMMAND<<InputComesFromHERE
...
...
...
InputComesFromHERE
嵌入文档用限定符作为命令列表的边界,在限定符前需要一个指定的标识符
<<,这会将一个程序或命令的标准输入(stdin)进行重定向,它类似交互程序<
命令文件的方式,其中命令文件内容如下
command#1
command#2
...
嵌入文档的格式大致如下
interactive-program<<LimitString
command#1
command#2
...
LimitString
限定符的选择必须保证特殊以确保不会和命令列表里的内容发生混淆。
注意嵌入文档有时候用作非交互的工具和命令有着非常好的效果,例如wall
样例19-1.broadcast:给每个登陆者发送信息
19.嵌入文档
261
#!/bin/bash
wall<<zzz23EndOfMessagezzz23
E-mailyournoontimeordersforpizzatothesystemadministrato
r.
(Addanextradollarforanchovyormushroomtopping.)
#额外的信息文本.
#注意:'wall'会打印注释行.
zzz23EndOfMessagezzz23
#更有效的做法是通过
#wall<信息文本
#然而,在脚本里嵌入信息模板不乏是一种迅速而又随性的解决方式.
exit
样例:19-2.dummyfile:创建一个有两行内容的虚拟文件
19.嵌入文档
262
#!/bin/bash
#非交互的使用`vi`编辑文件.
#仿照'sed'.
E_BADARGS=85
if[-z"$1"]
then
echo"Usage:`basename$0`filename"
exit$E_BADARGS
fi
TARGETFILE=$1
#插入两行到文件中保存
#--------Beginheredocument-----------#
vi$TARGETFILE<<x23LimitStringx23
i
Thisisline1oftheexamplefile.
Thisisline2oftheexamplefile.
^[
ZZ
x23LimitStringx23
#----------Endheredocument-----------#
#注意"^""["进行了转义
#+这段起到了和键盘上按下Control-V<Esc>相同的效果.
#BramMoolenaar指出这种情况下'vim'可能无法正常工作
#+因为在与终端交互的过程中可能会出现问题.
exit
上述脚本实现了ex的功能,而不是vi.嵌入文档包含了ex足够通用的命令
列表来形成自有的类别,所以又称之为ex脚本.
19.嵌入文档
263
#!/bin/bash
#替换所有的以".txt"后缀结尾的文件的"Smith""Jones"
ORIGINAL=Smith
REPLACEMENT=Jones
forwordin$(fgrep-l$ORIGINAL*.txt)
do
#-------------------------------------
ex$word<<EOF
:%s/$ORIGINAL/$REPLACEMENT/g
:wq
EOF
#:%sisthe"ex"substitutioncommand.
#:wqiswrite-and-quit.
#-------------------------------------
done
类似的ex脚本cat脚本.
样例19-3.使用cat的多行信息
19.嵌入文档
264
#!/bin/bash
#'echo'可以输出单行信息,
#+但是如果是输出消息块就有点问题了.
#'cat'嵌入文档却能解决这个局限.
cat<<End-of-message
-------------------------------------
Thisisline1ofthemessage.
Thisisline2ofthemessage.
Thisisline3ofthemessage.
Thisisline4ofthemessage.
Thisisthelastlineofthemessage.
-------------------------------------
End-of-message
#替换上述嵌入文档内的7行文本
#+cat>$Newfile<<End-of-message
#+^^^^^^^^^^
#+将输出追加到$Newfile,而不是标准输出.
exit0
#--------------------------------------------
#由于上面的"exit0",下面的代码将不会生效.
#S.C.pointsoutthatthefollowingalsoworks.
echo"-------------------------------------
Thisisline1ofthemessage.
Thisisline2ofthemessage.
Thisisline3ofthemessage.
Thisisline4ofthemessage.
Thisisthelastlineofthemessage.
-------------------------------------"
#然而,文本可能不包括双引号除非出现了字符串逃逸.
-的作用是标记了一个嵌入文档限制符(<<-LimitString),它能抑制输出的行首的
tab(非空格).这在脚本可读性方面可能非常有用.
19.嵌入文档
265
样例19-4.抑制tab的多行信息
#!/bin/bash
#和之前的样例一样,...
#嵌入文档内的'-',也就是<<-
#+抑制了文档行首的'tab',
#+*不是*空格.
cat<<-ENDOFMESSAGE
Thisisline1ofthemessage.
Thisisline2ofthemessage.
Thisisline3ofthemessage.
Thisisline4ofthemessage.
Thisisthelastlineofthemessage.
ENDOFMESSAGE
#脚本的输出将左对齐.
#行首的tab将不会输出.
#上面5行的"信息"tab开始,不是空格.
#空格不会受影响<<-.
#注意这个选项对*内嵌的*tab没有影响.
exit0
嵌入文档支持参数和命令替换.因此可以向嵌入文档传递不同的参数,变向的改其输
.
样例19-5.可替换参数的嵌入文档
19.嵌入文档
266
#!/bin/bash
#另一个使用参数替换的'cat'嵌入文档.
#试一试没有命令行参数,./scriptname
#试一试一个命令行参数,./scriptnameMortimer
#试试用一两个单词引用命令行参数,
#./scriptname"MortimerJones"
CMDLINEPARAM=1#Expectatleastcommand-lineparameter.
if[$#-ge$CMDLINEPARAM]
then
NAME=$1#Ifmorethanonecommand-lineparam,
#+thenjusttakethefirst.
else
NAME="JohnDoe"#Default,ifnocommand-lineparameter.
fi
RESPONDENT="theauthorofthisfinescript"
cat<<Endofmessage
Hello,there,$NAME.
Greetingstoyou,$NAME,from$RESPONDENT.
#这个注释在输出时显示(为什么?).
Endofmessage
#注意输出了空行.
#所以可以这样注释.
exit
这个包含参数替换的嵌入文档是相当有用的
样例19-6.上传文件对到Sunsite入口目录
19.嵌入文档
267
#!/bin/bash
#upload.sh
#上传文件对(Filename.lsm,Filename.tar.gz)
#+Sunsite/UNC(ibiblio.org)的入口目录.
#Filename.tar.gz是个tarball.
#Filename.lsmis是个描述文件.
#Sunsite需要"lsm"文件,否则将会退回给发送者
E_ARGERROR=85
if[-z"$1"]
then
echo"Usage:`basename$0`Filename-to-upload"
exit$E_ARGERROR
fi
Filename=`basename$1`#Stripspathnameoutoffilen
ame.
Server="ibiblio.org"
Directory="/incoming/Linux"
#脚本里不需要硬编码,
#+但最好可以替换命令行参数.
Password="your.e-mail.address"#Changeabovetosuit.
ftp-n$Server<<End-Of-Session
#-n禁用自动登录
useranonymous"$Password"#Ifthisdoesn'twork,thent
ry:
#quoteuseranonymous"$Passw
ord"
binary
bell#Ring'bell'aftereachfilet
ransfer.
19.嵌入文档
268
cd$Directory
put"$Filename.lsm"
put"$Filename.tar.gz"
bye
End-Of-Session
exit0
在嵌入文档头部引用或转义"限制符"来禁用参数替换.原因是引用/转义限定符能
有效的转义"$","`","\"这些特殊符号,使他们维持字面上的意思.(感谢Allen
Halsey指出这点.)
样例19-7.禁用参数替换
#!/bin/bash
#A'cat'here-document,butwithparametersubstitutiondisabl
ed.
NAME="JohnDoe"
RESPONDENT="theauthorofthisfinescript"
cat<<'Endofmessage'
Hello,there,$NAME.
Greetingstoyou,$NAME,from$RESPONDENT.
Endofmessage
#'限制符'引用或转义时不会有参数替换.
#下面的嵌入文档也有同样的效果
#cat<<"Endofmessage"
#cat<<\Endofmessage
#同样的:
cat<<"SpecialCharTest"
19.嵌入文档
269
Directorylistingwouldfollow
iflimitstringwerenotquoted.
`ls-l`
Arithmeticexpansionwouldtakeplace
iflimitstringwerenotquoted.
$((5+3))
Aasinglebackslashwouldecho
iflimitstringwerenotquoted.
\\
SpecialCharTest
exit
生成脚本或者程序代码时可以用禁用参数的方式来输出文本.
样例19-8.生成其他脚本的脚本
#!/bin/bash
#generate-script.sh
#BasedonanideabyAlbertReiner.
OUTFILE=generated.sh#Nameofthefiletogenerate.
#-----------------------------------------------------------
#'嵌入文档涵盖了生成脚本的主体部分.
(
cat<<'EOF'
#!/bin/bash
echo"Thisisageneratedshellscript."
#注意我们现在在一个子shell,
#+我们不能访问"外部"脚本变量.
echo"Generatedfilewillbenamed:$OUTFILE"
#上面这行并不能按照预期的正常工作
19.嵌入文档
270
#+因为参数扩展已被禁用.
#相反的,结果是文字输出.
a=7
b=3
let"c=$a*$b"
echo"c=$c"
exit0
EOF
)>$OUTFILE
#-----------------------------------------------------------
#在上述的嵌入文档内引用'限制符'防止变量扩展
if[-f"$OUTFILE"]
then
chmod755$OUTFILE
#生成可执行文件.
else
echo"Problemincreatingfile:\"$OUTFILE\""
fi
#这个方法适用于生成C,Perl,Python,Makefiles等等
exit0
可以从嵌入文档的输出设置一个变量的值.这实际上是种灵活的命令替换.
variable=$(cat<<SETVAR
Thisvariable
runsovermultiplelines.
SETVAR
)
echo"$variable"
同样的脚本里嵌入文档可以作为函数的输入.
19.嵌入文档
271
样例19-9.嵌入文档和函数
#!/bin/bash
#here-function.sh
GetPersonalData()
{
readfirstname
readlastname
readaddress
readcity
readstate
readzipcode
}#可以肯定的是这应该是个交互式的函数,...
#作为函数的输入.
GetPersonalData<<RECORD001
Bozo
Bozeman
2726NondescriptDr.
Bozeman
MT
21226
RECORD001
echo
echo"$firstname$lastname"
echo"$address"
echo"$city,$state$zipcode"
echo
exit0
可以这样使用:作为一个虚构的命令接受嵌入文档的输出.这样实际上就创建了一个
"匿名"嵌入文档.
样例19-10."匿名"嵌入文档
19.嵌入文档
272
#!/bin/bash
:<<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?}#Printerrormessageifoneofth
evariablesnotset.
TESTVARIABLES
exit$?
上面技巧的一种变体允许"可添加注释"的代码块.
样例19-11.可添加注释的代码块
#!/bin/bash
#commentblock.sh
:<<COMMENTBLOCK
echo"Thislinewillnotecho."
这些注释没有"#"前缀.
则是另一种没有"#"前缀的注释方法.
&*@!!++=
上面这行不会产生报错信息,
因为bash解释器会忽略它.
COMMENTBLOCK
echo"Exitvalueofabove\"COMMENTBLOCK\"is$?."#0
#没有错误输出.
echo
#上面的技巧经常用于工作代码的注释用作排错目的
#这省去了在每一行开头加上"#"前缀,
#+然后调试完不得不删除每行的前缀的重复工作.
#注意我们用了":",在这之上,是可选的.
echo"Justbeforecommented-outcodeblock."
#下面这个在双破折号之间的代码不会被执行.
#=============================================================
19.嵌入文档
273
======
:<<DEBUGXXX
forfilein*
do
cat"$file"
done
DEBUGXXX
#=============================================================
======
echo"Justaftercommented-outcodeblock."
exit0
################################################################
######
#注意,然而,如果将变量中包含一个注释的代码块将会引发问题
#例如:
#/!/bin/bash
:<<COMMENTBLOCK
echo"Thislinewillnotecho."
&*@!!++=
${foo_bar_bazz?}
$(rm-rf/tmp/foobar/)
$(touchmy_build_directory/cups/Makefile)
COMMENTBLOCK
$shcommented-bad.sh
commented-bad.sh:line3:foo_bar_bazz:parameternullornotse
t
#有效的补救办法就是在49行的位置加上单引号,变为'COMMENTBLOCK'.
:<<'COMMENTBLOCK'
19.嵌入文档
274
#感谢KurtPfeifle指出这一点.
另一个漂亮的方法使得"自文档化"的脚本成为可能
样例19-12.自文档化的脚本
19.嵌入文档
275
#!/bin/bash
#self-document.sh:self-documentingscript
#Modificationof"colm.sh".
DOC_REQUEST=70
if["$1"="-h"-o"$1"="--help"]#请求帮助.
then
echo;echo"Usage:$0[directory-name]";echo
sed--silent-e'/DOCUMENTATIONXX$/,/^DOCUMENTATIONXX$/p'"$0"
|
sed-e'/DOCUMENTATIONXX$/d';exit$DOC_REQUEST;fi
:<<DOCUMENTATIONXX
Listthestatisticsofaspecifieddirectoryintabularformat.
---------------------------------------------------------------
Thecommand-lineparametergivesthedirectorytobelisted.
Ifnodirectoryspecifiedordirectoryspecifiedcannotberead,
thenlistthecurrentworkingdirectory.
DOCUMENTATIONXX
if[-z"$1"-o!-r"$1"]
then
directory=.
else
directory="$1"
fi
echo"Listingof"$directory":";echo
(printf"PERMISSIONSLINKSOWNERGROUPSIZEMONTHDAYHH:MMPROG
-NAME\n"\
;ls-l"$directory"|sed1d)|column-t
exit0
使用catscript是另一种可行的方法.
19.嵌入文档
276
DOC_REQUEST=70
if["$1"="-h"-o"$1"="--help"]#Requesthelp.
then#Usea"catscript"
...
cat<<DOCUMENTATIONXX
Listthestatisticsofaspecifieddirectoryintabularformat.
---------------------------------------------------------------
Thecommand-lineparametergivesthedirectorytobelisted.
Ifnodirectoryspecifiedordirectoryspecifiedcannotberead,
thenlistthecurrentworkingdirectory.
DOCUMENTATIONXX
exit$DOC_REQUEST
fi
另请参阅样例A-28,样例A-40,样例A-41,and样例A-42更多样例请阅读脚
本附带的注释文档.
嵌入文档创建了临时文件,但这些文件在打开且不可被其他程序访问后删除.
bash$bash-c'lsof-a-p$$-d0'<<EOF
>EOF
lsof1213bozo0rREG3,5030386/tmp/t1213-0-sh
(deleted)
某些工具在嵌入文档内部并不能正常运行.
在嵌入文档的最后关闭限定符必须在起始的第一个字符的位置开始.行首不能是
空格.限制符后尾随空格同样会导致意想不到的行为.空格可以防止限制符被当
做其他用途.[1]
19.嵌入文档
277
#!/bin/bash
echo"----------------------------------------------------------
------------"
cat<<LimitString
echo"Thisisline1ofthemessageinsidetheheredocument."
echo"Thisisline2ofthemessageinsidetheheredocument."
echo"Thisisthefinallineofthemessageinsidetheheredocu
ment."
LimitString
#^^^^限制符的缩进.出错!这个脚本将不会如期运行.
echo"----------------------------------------------------------
------------"
#这些评论在嵌入文档范围外并不能输出
echo"Outsidetheheredocument."
exit0
echo"Thislinehadbetternotecho."#紧跟着个'exit'命令.
有些人非常聪明的使用了一个单引号(!)做为限制符.但这并不是个好主意
19.嵌入文档
278
#这个可以运行.
cat<<!
Hello!
!Threemoreexclamations!!!
!
#但是...
cat<<!
Hello!
Singleexclamationpointfollows!
!
!
#Crasheswithanerrormessage.
#然而,下面这样也能运行.
cat<<EOF
Hello!
Singleexclamationpointfollows!
!
EOF
#使用多字符限制符更为安全.
为嵌入文档设置这些任务有些复杂,可以考虑使用expect,一种专门用来和程序
进行交互的脚本语言。
Notes:  除此之外,DennisBenzinger指出,使用<<-抑制tab.
19.嵌入文档
279
20I/O重定向
目录
20.1使用exec
20.2重定向代码块
20.3应用程序
有三个默认打开的文件[1], stdin(标准输入,键盘), stdout(标准输出,屏幕)
stderr(标准错误,屏幕上输出的错误信息)。这些和任何其他打开的文件都可
以被重定向。重定向仅仅意味着捕获输出文件,命令,脚本,甚至是一个脚本的代
码块(样例3-1)(样例3-2)作为另一个文件,命令,程序或脚本的输入。
每个打开的文件都有特定的文件描述符。[2],stdinstdoutstderr
文件描述符分别为0,1,2。当然了,还有附件的文件描述符3-9。有时候
stdinstdoutstderr临时性的复制链接分配这些附加的文件描述符会
非常有用.[3]。这简化了复杂重定向和重组后的恢复(样例20-1)
COMMAND_OUTPUT>
#重定向标准输出到一个文件.
#如果文件不存在则创建,否则覆盖.
ls-lR>dir-tree.list
#创建了一个包含目录树列表的文件.
:>filename
#">"清空了文件.
#如果文件不存在,则创建了个空文件(效果类似'touch').
#":"是个虚拟占位符,不会有输出.
>filename
#">"清空了文件.
#如果文件不存在,则创建了个空文件(效果类似'touch').
#(结果和上述的":>"一样,但在某些shell环境中不能正常运行.)
COMMAND_OUTPUT>>
#重定向标准输出到一个文件.
#如果文件不存在则创建,否则新内容在文件末尾追加.
20.I/O重定向
280
#单行重定向命令(只作用于本身所在的那行):
#--------------------------------------------------------
------------
1>filename
#以覆盖的方式将标准错误重定向到文件"filename."
1>>filename
#以追加的方式将标准输出重定向到文件"filename."
2>filename
#以覆盖的方式将标准错误重定向到文件"filename."
2>>filename
#以追加的方式将标准错误重定向到文件"filename."
&>filename
#以覆盖的方式将标准错误标准输出同时重定向到文件"filename."
#bash4中才有这个新功能.
M>N
#"M"是个文件描述符,如果不明确指定,默认为1.
#"N"是个文件名.
#文件描述符"M"重定向到文件"N."
M>&N
#"M"是个文件描述符,如果不设置默认为1.
#"N"是另一个文件描述符.
#=========================================================
=====================
#重定向标准输出,一次一行.
LOGFILE=script.log
echo"Thisstatementissenttothelogfile,\"$LOGFILE\"
."1>$LOGFILE
echo"Thisstatementisappendedto\"$LOGFILE\"."1>>$LOG
FILE
echo"Thisstatementisalsoappendedto\"$LOGFILE\"."1>
>$LOGFILE
echo"Thisstatementisechoedtostdout,andwillnotapp
earin\"$LOGFILE\"."
20.I/O重定向
281
#这些重定向命令在每行结束后自动"重置".
#重定向标准错误,一次一行.
ERRORFILE=script.errors
bad_command12>$ERRORFILE#Errormessagesentto$
ERRORFILE.
bad_command22>>$ERRORFILE#Errormessageappended
to$ERRORFILE.
bad_command3#Errormessageechoedto
stderr,
#+anddoesnotappearin
$ERRORFILE.
#这些重定向命令每行结束后会自动重置”.
#===========================================================
============
2>&1
#重定向标准错误标准输出.
#错误信息发送到标准输出相同的位置.
>>filename2>&1
bad_command>>filename2>&1
#同时将标准输出标准错误追加到文件"filename"...
2>&1|[command(s)]
bad_command2>&1|awk'{print$5}'#found
#通过管道传递标准错误.
#bash4中可以将"2>&1|"缩写为"|&".
i>&j
#重定向文件描述符ij.
#文件描述符i指向的文件输出将会重定向到文件描述符j指向的文件
>&j
#默认的标准输出(stdout)重定向到j.
#所有的标准输出将会重定向到j指向的文件.
20.I/O重定向
282
0<FILENAME
<FILENAME
#从文件接收输入.
#类似功能命令是">",经常会组合使用.
#
#grepsearch-word<filename
[j]<>filename
#打开并读写文件"filename",
#+并且分配文件描述符"j".
#如果"filename"不存在则创建.
#如果文件描述符"j"未指定,默认分配文件描述符0,标准输入.
#
#这是一个写指定文件位置的应用程序.
echo1234567890>File#写字符串到"File".
exec3<>File#打开并分配文件描述符3"File".
read-n4<&3#读取4字符.
echo-n.>&3#写一个小数点.
exec3>&-#关闭文件描述符3.
catFile#==>1234.67890
#随机访问.
|
#管道.
#一般是命令和进程的链接工具.
#类似">",但更一般.
#在连接命令,脚本,文件和程序方面非常有用.
cat*.txt|sort|uniq>result-file
#所有.txt文件输出进行排序并且删除复制行,
#最终保存结果到"result-file".
可以用单个命令行表示输入和输出的多个重定向或管道.
20.I/O重定向
283
command<input-file>output-file
#或者等价:
<input-filecommand>output-file#尽管这不标准.
command1|command2|command3>output-file
更多详情见样例16-31and样例A-14.
多个输出流可以重定向到一个文件.
ls-yz>>command.log2>&1
#捕获不合法选项"yz"的结果到文件"command.log."
#因为标准错误输出被重定向到了文件,
#+任何错误信息都会在这.
#注意,然而,接下来的这个案例并"不能"同样的结果.
ls-yz2>&1>>command.log
#输出一条错误信息,但是不会写入到文件.
#恰恰的,命令输出(这个例子里为空)写入到文件,但错误信息只会在标准输出
.
#如果同时重定向标准输出标准错误输出,
#+命令的顺序不同会导致不同.
关闭文件描述符
n<&-
关闭输入文件描述符n.
0<&-,<&-
关闭标准输入.
n>&-
关闭输出文件描述符n.
1>&-,>&-
关闭标准输出.
20.I/O重定向
284
子进程能继承文件描述符.这就是管道符能工作的原因.通过关闭文件描述符来防止
继承.
#只重定向到标准错误到管道.
exec3>&1#保存当前标准输出"".
ls-l2>&1>&33>&-|grepbad3>&-#关闭'grep'文件描述符3
(但不是'ls').
#^^^^^^^^
exec3>&-#现在关闭它.
#感谢,S.C.
更多关于I/O重定向详情见AppendixF.
注意
[1]UNIXLinux,数据流和周边外设(devicefiles)都被看做文件.
[2]文件描述符仅仅是操作系统分配的一个可追踪的打开的文件号.可以认为是一
个简化的文件指针.类似于C语言的文件句柄.
[3]bash创建一个子进程的时候使用文件描述符5会有问题.例如exec,子进
程继承了文件描述符5(详情见ChetRamey's归档的e-mail,SUBJECT:RE:File
descriptor5isheldopen).最好将这个文件描述符单独规避.
20.I/O重定向
285
20.1使用exec
一个exec<filename命令重定向了标准输入到一个文件。自此所有标准输
都来自该文件而不是默认来源(通常是键盘输入)。在使用sedawk时候这种
方式可以逐行读文件并逐行解析。
样例20-1.使用exec重定向标准输入
20.1使用exec
286
#!/bin/bash
#使用'exec'重定向标准输入.
exec6<&0#链接文件描述符#6到标准输入.
#.
exec<data-file#标准输入被文件"data-file"替换
reada1#读取文件"data-file"首行.
reada2#读取文件"data-file"第二行
echo
echo"Followinglinesreadfromfile."
echo"-------------------------------"
echo$a1
echo$a2
echo;echo;echo
exec0<&66<&-
#现在在之前保存的位置将从文件描述符#6标准输出恢复.
#+且关闭文件描述符#6(6<&-)让其他程序正常使用.
#
#<&66<&-alsoworks.
echo-n"Enterdata"
readb1#现在按预期的,从正常的标准输入"read".
echo"Inputreadfromstdin."
echo"----------------------"
echo"b1=$b1"
echo
exit0
同理, exec>filename重定向标准输出到指定文件.他将所有的命令输出通常
标准输出重定向到指定的位置.
20.1使用exec
287
execN>filename影响整个脚本或当前shellPID从重定向脚本或shell的那
时候已经发生了改变.然而N>filename影响的就是新派生的进程,而不是整
个脚本或shell
样例20-2.使用exec重定向标准输出
20.1使用exec
288
#!/bin/bash
#reassign-stdout.sh
LOGFILE=logfile.txt
exec6>&1#链接文件描述符#6到标准输出.
#保存标准输出.
exec>$LOGFILE#标准输出被文件"logfile.txt"替换.
#-----------------------------------------------------------#
#所有在这个块里的命令的输出都会发送到文件$LOGFILE.
echo-n"Logfile:"
date
echo"-------------------------------------"
echo
echo"Outputof\"ls-al\"command"
echo
ls-al
echo;echo
echo"Outputof\"df\"command"
echo
df
#-----------------------------------------------------------#
exec1>&66>&-#关闭文件描述符#6恢复标准输出.
echo
echo"==stdoutnowrestoredtodefault=="
echo
ls-al
echo
exit0
样例20-3.exec在一个脚本里同时重定向标准输入标准输出
20.1使用exec
289
#!/bin/bash
#upperconv.sh
#转化指定的输入文件成大写.
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if[!-r"$1"]#指定的输入文件是否可读?
then
echo"Can'treadfrominputfile!"
echo"Usage:$0input-fileoutput-file"
exit$E_FILE_ACCESS
fi#同样的错误退出
#+等同如果输入文件($1)未指定(为什么?).
if[-z"$2"]
then
echo"Needtospecifyoutputfile."
echo"Usage:$0input-fileoutput-file"
exit$E_WRONG_ARGS
fi
exec4<&0
exec<$1#将从输入文件读取.
exec7>&1
exec>$2#将写入输出文件.
#假定输出文件可写(增加检测?).
#-----------------------------------------------
cat-|tra-zA-Z#转化大写.
#^^^^^#读取标准输入.
#^^^^^^^^^^#写到标准输出.
#然而标准输入和标准输出都会被重定向.
#注意'cat'可能会被遗漏.
#-----------------------------------------------
exec1>&77>&-#恢复标准输出.
20.1使用exec
290
exec0<&44<&-#恢复标准输入.
#恢复后,下面这行会预期从标准输出打印.
echo"File\"$1\"writtento\"$2\"asuppercaseconversion."
exit0
I/O重定向是种明智的规避inaccessiblevariableswithinasubshell问题的方法.
样例20-4.规避子shell
#!/bin/bash
#avoid-subshell.sh
#MatthewWalker的建议.
Lines=0
echo
catmyfile.txt|whilereadline;
do{
echo$line
((Lines++));#递增变量的值趋近外层循环
#使用子shell会有问题.
}
done
echo"Numberoflinesread=$Lines"#0
#报错!
echo"------------------------"
exec3<>myfile.txt
whilereadline<&3
do{
echo"$line"
((Lines++));#递增变量的值趋近外层循环.
#没有子shell,就不会有问题.
}
20.1使用exec
291
done
exec3>&-
echo"Numberoflinesread=$Lines"#8
echo
exit0
#下面的行并不在脚本里.
$catmyfile.txt
Line1.
Line2.
Line3.
Line4.
Line5.
Line6.
Line7.
Line8.
20.1使用exec
292
20.2重定向代码块
有如while,until,for循环,甚至if/then也可以重定向标准输入测试代码块.甚至
连一个函数都可以用这个方法进行重定向(样例24-11).代码块的末尾部分的"<"
就是用来完成这个的.
样例20-5.while循环的重定向
#!/bin/bash
#redir2.sh
if[-z"$1"]
then
Filename=names.data#如果不指定文件名的默认值.
else
Filename=$1
fi
#+Filename=${1:-names.data}
#canreplacetheabovetest(parametersubstitution).
count=0
echo
while["$name"!=Smith]#为什么变量"$name"加引号?
do
readname#$Filename读取值,而不是标准输入.
echo$name
let"count+=1"
done<"$Filename"#重定向标准输入到文件$Filename.
#^^^^^^^^^^^^
echo;echo"$countnamesread";echo
exit0
#注意在一些老的脚本语言中,
#+循环的重定向会跑在子shell的环境中.
20.2重定向代码块
293
#因此,$count返回0,在循环外已经初始化过值.
#Bashksh*只要可能*会避免启动子shell,
#+所以这个脚本作为样例运行成功.
#(感谢HeinerSteven指出这点.)
#然而...
#Bash有时候**"只读的while"循环启动子进程,
#+不同于"while"循环的重定向.
abc=hi
echo-e"1\n2\n3"|whilereadl
doabc="$l"
echo$abc
done
echo$abc
#感谢,BrunodeOliveiraSchneider上面的演示代码.
#也感谢BrianOnn纠正了注释的错误.
样例20-6.另一种形式的while循环重定向
#!/bin/bash
#这是之前的另一种形式的脚本.
#HeinerSteven提议在重定向循环时候运行在子shell可以作为一个变通方案
#+因此直到循环终止时循环内部的变量不需要保证他们的值
if[-z"$1"]
then
Filename=names.data#如果不指定文件名的默认值.
else
Filename=$1
fi
exec3<&0#保存标准输入到文件描述符3.
exec0<"$Filename"#重定向标准输入.
20.2重定向代码块
294
count=0
echo
while["$name"!=Smith]
do
readname#从重定向的标准输入($Filename)读取值.
echo$name
let"count+=1"
done#$Filename循环读
#+因为第20.
#这个脚本的早期版本在"while"循环done<"$Filename"终止
#练习:
#为什么这个没必要?
exec0<&3#恢复早前的标准输入.
exec3<&-#关闭临时的文件描述符3.
echo;echo"$countnamesread";echo
exit0
样例20-7.until循环的重定向
20.2重定向代码块
295
#!/bin/bash
#同先前的脚本一样,不过用的是"until"循环.
if[-z"$1"]
then
Filename=names.data#如果不指定文件的默认值.
else
Filename=$1
fi
#while["$name"!=Smith]
until["$name"=Smith]#!==.
do
readname#$Filename读取值,而不是标准输入.
echo$name
done<"$Filename"#重定向标准输入到文件"$Filename".
#^^^^^^^^^^^^
#和之前的"while"循环样例相同的结果.
exit0
样例20-8.for循环的重定向
20.2重定向代码块
296
#!/bin/bash
if[-z"$1"]
then
Filename=names.data#如果不指定文件的默认值.
else
Filename=$1
fi
line_count=`wc$Filename|awk'{print$1}'`
#目标文件的行数.
#
#非常作和不完善,然而这只是证明"for"循环中的重定向标准输入是可行的
#+如果你足够聪明的话.
#
#简介的做法是line_count=$(wc-l<"$Filename")
fornamein`seq$line_count`#回忆下"seq"可以输入数组序列.
#while["$name"!=Smith]--"while"循环更复杂的循环-
-
do
readname#$Filename读取值,而不是标准输入
.
echo$name
if["$name"=Smith]#这需要所有这些额外的设置.
then
break
fi
done<"$Filename"#重定向标准输入到文件"$Filename".
#^^^^^^^^^^^^
exit0
我们可以修改先前的样例也可以重定向循环的输出.
样例20-9.for循环的重定向(同时重定向标准输入和标准输出)
20.2重定向代码块
297
#!/bin/bash
if[-z"$1"]
then
Filename=names.data#如果不指定文件的默认值.
else
Filename=$1
fi
Savefile=$Filename.new#报错的结果的文件名.
FinalName=Jonah#停止"read"的终止字符.
line_count=`wc$Filename|awk'{print$1}'`#目标文件行数.
fornamein`seq$line_count`
do
readname
echo"$name"
if["$name"="$FinalName"]
then
break
fi
done<"$Filename">"$Savefile"#重定向标准输入到文件$Filena
me,
#^^^^^^^^^^^^^^^^^^^^^^^^^^^并且报错结果到备份文件.
exit0
样例20-10.if/thentest的重定向
20.2重定向代码块
298
#!/bin/bash
if[-z"$1"]
then
Filename=names.data#如果不指定文件的默认值.
else
Filename=$1
fi
TRUE=1
if["$TRUE"]#iftrueif:都可以工作.
then
readname
echo$name
fi<"$Filename"
#^^^^^^^^^^^^
#只读取文件的首行.
#"if/then"test除非嵌入在循环内部否则没办法迭代.
exit0
样例20-11.上述样例的数据文件names.data
20.2重定向代码块
299
Aristotle
Arrhenius
Belisarius
Capablanca
Dickens
Euler
Goethe
Hegel
Jonah
Laplace
Maroczy
Purcell
Schmidt
Schopenhauer
Semmelweiss
Smith
Steinmetz
Tukhashevsky
Turing
Venn
Warshawski
Znosko-Borowski
#+这是"redir2.sh","redir3.sh","redir4.sh","redir4a.sh","red
ir5.sh"的数据文件.
代码块的标准输出的重定向影响了保存到文件的输出.见样例样例3-2.
嵌入文档是种特别的重定向代码块的方法.既然如此,它使得在while循环的标准输
入里传入嵌入文档的输出变得可能.
20.2重定向代码块
300
#这个样例来自AlbertSiersema
#得到了使用许可(感谢!).
functiondoesOutput()
#当然这也是个外部命令.
#这里用函数进行演示会更好一点.
{
ls-al*.jpg|awk'{print$5,$9}'
}
nr=0#我们希望在'while'循环里可以操作这些
totalSize=0#+并且在'while'循环结束时看到改变.
whilereadfileSizefileName;do
echo"$fileNameis$fileSizebytes"
letnr++
totalSize=$((totalSize+fileSize))#Or:"lettotalSize+=file
Size"
done<<EOF
$(doesOutput)
EOF
echo"$nrfilestotaling$totalSizebytes"
20.2重定向代码块
301
20.3应用程序
使用I/O重定向可以同时解析和固定命令输出的片段(see样例15-7).这也使得可以
生成报告和日志文件.
样例20-12.日志记录事件
#!/bin/bash
#logevents.sh
#作者:StephaneChazelas.
#用于ABS许可指南.
#事件记录到文件.
#必须root身份执行(可以写入/var/log).
ROOT_UID=0#只有$UID0的用户具有root权限.
E_NOTROOT=67#root会报错.
if["$UID"-ne"$ROOT_UID"]
then
echo"Mustberoottorunthisscript."
exit$E_NOTROOT
fi
FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5
#===取消下面两行注释来激活脚本.===
#LOG_EVENTS=1
#LOG_VARS=1
log()#时间和日期写入日志文件.
{
echo"$(date)$*">&7#*追加*日期到文件.
20.3应用程序
302
#^^^^^^^命令替换
#见下文.
}
case$LOG_LEVELin
1)exec3>&24>/dev/null5>/dev/null;;
2)exec3>&24>&25>/dev/null;;
3)exec3>&24>&25>&2;;
*)exec3>/dev/null4>/dev/null5>/dev/null;;
esac
FD_LOGVARS=6
if[[$LOG_VARS]]
thenexec6>>/var/log/vars.log
elseexec6>/dev/null#清空输出.
fi
FD_LOGEVENTS=7
if[[$LOG_EVENTS]]
then
#exec7>(execgawk'{printstrftime(),$0}'>>/var/log/even
t.log)
#上述行在最近高于bash2.04版本会失败,为什么?
exec7>>/var/log/event.log#追加到"event.log".
log#写入时间和日期.
elseexec7>/dev/null#清空输出.
fi
echo"DEBUG3:beginning">&${FD_DEBUG3}
ls-l>&52>&4#命令1>&52>&4
echo"Done"#命令2
echo"sendingmail">&${FD_LOGEVENTS}
#输出信息"sendingmail"到文件描述符#7.
20.3应用程序
303
exit0
20.3应用程序
304
第二十二章.限制模式的Shell
限制模式下被禁用的命令
在限制模式下运行一个脚本或部分脚本将禁用一些命令,尽管这些命令在正常
模式下是可用的。这是个安全措施,可以限制脚本用户的权限,减少运行脚本
可能带来的损害。
被禁用的命令和功能:
使用cd来改变工作目录。
修改$PATH,$SHELL,$BASH_ENV$ENV环境变量
读取或修改$SHELLOPTSshell环境选项。
输出重定向。
调用包含/的命令。
调用exec来替代shell进程。
其他各种会造成混乱或颠覆脚本用途的命令。
在脚本中跳出限制模式。
22-1.在限制模式运行脚本
#!/bin/bash
#在脚本开头用"#!/bin/bash-r"
#+可以让整个脚本在限制模式运行。
echo
echo"改变目录。"
cd/usr/local
echo"现在是在`pwd`"
echo"回到家目录。"
cd
echo"现在是在`pwd`"
echo
#到此为止一切都是正常的,非限制模式。
22.限制模式的Shell
305
set-r
#set--restricted效果相同。
echo"==>现在是限制模式<=="
echo
echo
echo"在限制模式试图改变目录。"
cd..
echo"依旧在`pwd`"
echo
echo
echo"\$SHELL=$SHELL"
echo"试图在限制模式改变Shell"
SHELL="/bin/ash"
echo
echo"\$SHELL=$SHELL"
echo
echo
echo"试图在限制模式重定向输出内容。"
ls-l/usr/bin>bin.files
ls-lbin.files#尝试列出试图创建的文件。
echo
exit0
22.限制模式的Shell
306
第二十三章.进程替换
管道将一个命令的标准输出输送到另一个命令的标准输入是个强大的技
术。但是如果你需要用管道输送多个命令的标准输出怎么办?这时候进程替换
就派上用场了。
进程替换把一个(或多个)进程的输出送到另一个进程的标准输入
样板命令列表要用括号括起来
>(command_list)
<(command_list)
进程替换使用/dev/fd/<n>文件发送括号内进程的结果到另一个进程。[1]
"<"">"与括号之间没有空格,加上空格或报错。
bash$echo>(true)
/dev/fd/63
bash$echo<(true)
/dev/fd/63
bash$echo>(true)<(true)
/dev/fd/63/dev/fd/62
bash$wc<(cat/usr/share/dict/linux.words)
4835234835234992010/dev/fd/63
bash$grepscript/usr/share/dict/linux.words|wc
2622623601
bash$wc<(grepscript/usr/share/dict/linux.words)
2622623601/dev/fd/63
23.进程替换
307
Bash用两个文件描述符创建管道, --fInfOut--true标准输入
fOut(dup2(fOut,0)),然后Bash传递一个/dev/fd/fIn参数给echo。在不
使用/dev/fd/<n>的系统里,Bash可以用临时文件(感谢S.C.指出这点)。
进程替换可以比较两个不同命令的输出,或者同一个命令使用不同选项的输出。
bash$comm<(ls-l)<(ls-al)
total12
-rw-rw-r--1bozobozo78Mar1012:58File0
-rw-rw-r--1bozobozo42Mar1012:58File2
-rw-rw-r--1bozobozo103Mar1012:58t2.sh
total20
drwxrwxrwx2bozobozo4096Mar1018:10.
drwx------72bozobozo4096Mar1017:58..
-rw-rw-r--1bozobozo78Mar1012:58File0
-rw-rw-r--1bozobozo42Mar1012:58File2
-rw-rw-r--1bozobozo103Mar1012:58t2.sh
进程替换可以比较两个目录的内容——来检查哪些文件在这个目录而不在那个目
录。
diff<(ls$first_directory)<(ls$second_directory)
进程替换的一些其他用法:
read-alist<<(od-Ad-w24-tu2/dev/urandom)
#/dev/urandom读取一个随机数列表
#+"od"处理
#+输送到"read"的标准输入...
#来自"insertion-sort.bash"示例脚本。
#致谢:JuanJoCiarlante
23.进程替换
308
PORT=6881#bittorrentBT端口)
#扫描端口,确保没有恶意行为
netcat-l$PORT|tee>(md5sum->mydata-orig.md5)|
gzip|tee>(md5sum-|sed's/-$/mydata.lz2/'>mydata-gz.md5)>myd
ata.gz
#检查解压缩结果:
gzip-d<mydata.gz|md5sum-cmydata-orig.md5)
#对原件的MD5校验用来检查标准输入,并且探测压缩当中出现的问题。
#BillDavidsen贡献了这个例子
#+ABS指南作者做了轻微修改)。
23.进程替换
309
cat<(ls-l)
#等价于ls-l|cat
sort-k9<(ls-l/bin)<(ls-l/usr/bin)<(ls-l/usr/X11R6/bin
)
#列出3个主要'bin'目录的文件,按照文件名排序。
#注意,有三个(数一下)单独的命令输送给了'sort'
diff<(command1)<(command2)#比较命令输出结果的不同之处。
tarcf>(bzip2-c>file.tar.bz2)$directory_name
#调用"tarcf/dev/fd/??$directory_name",然后"bzip2-c>file
.tar.bz2"
#
#因为/dev/fd/<n>系统特性
#不需要在两个命令之间使用管道符
#
#这个可以模拟
#
bzip2-c<pipe>file.tar.bz2&
tarcfpipe$directory_name
rmpipe
#或者
exec3>&1
tarcf/dev/fd/4$directory_name4>&1>&33>&-|bzip2-c>file
.tar.bz23>&-
exec3>&-
#致谢:StéphaneChazelas
在子shellecho命令用管道输送给while-read循环时会出现问题,下面是避免的
方法:
23-1不用fork的代码块重定向。
#!/bin/bash
#wr-ps.bash:使用进程替换的while-read循环。
23.进程替换
310
#示例由TomasPospisek贡献。
#ABS指南作者做了大量改动。)
echo
echo"randominput"|whilereadi
do
global=3D":Notavailableoutsidetheloop."
#...因为在子shell中运行。
done
echo"\$global(从子进程之外)=$global"
#$global(从子进程之外)=
echo;echo"--";echo
whilereadi
do
echo$i
global=3D":Availableoutsidetheloop."
#...因为没有在子shell中运行。
done<<(echo"randominput")
#^^
echo"\$global(使用进程替换)=$global"
#随机输入
#$global(使用进程替换)=3D:Availableoutsidetheloop.
echo;echo"##########";echo
#同样道理...
declare-ainloop
index=0
cat$0|whilereadline
do
23.进程替换
311
inloop[$index]="$line"
((index++))
#在子shell中运行,所以...
done
echo"OUTPUT="
echo${inloop[*]}#...什么也没有显示。
echo;echo"--";echo
declare-aoutloop
index=0
whilereadline
do
outloop[$index]="$line"
((index++))
#没有在子shell中运行,所以...
done<<(cat$0)
echo"OUTPUT="
echo${outloop[*]}#...整个脚本的结果显示出来。
exit$?
下面是个类似的例子。
23-2.重定向进程替换的输出到一个循环内
23.进程替换
312
#!/bin/bash
#psub.bash
#DiegoMolina启发(感谢!)。
declare-aarray0
whileread
do
array0[${#array0[@]}]="$REPLY"
done<<(sed-e's/bash/CRASH-BANG!/'$0|grepbin|awk'{pri
nt$1}')
#由进程替换来设置'read'默认变量($REPLY)。
#+然后将变量复制到一个数组。
echo"${array0[@]}"
exit$?
#======================================#
#运行结果:
bashpsub.bash
#!/bin/CRASH-BANG!done#!/bin/CRASH-BANG!
一个读者发来一个有趣的进程替换例子,如下:
#SuSE发行版中提取的脚本片段:
#--------------------------------------------------------------
#
whilereaddeswhatmaskiface;do
#一些命令...
done<<(route-n)
#^^第一个<是重定向,第二个是进程替换。
#为了测试,我们让它来做点儿事情。
whilereaddeswhatmaskiface;do
echo$des$what$mask$iface
done<<(route-n)
23.进程替换
313
#输出内容:
#KernelIProutingtable
#DestinationGatewayGenmaskFlagsMetricRefUseIface
#127.0.0.00.0.0.0255.0.0.0U000lo
#--------------------------------------------------------------
#
#正如StéphaneChazelas指出的,
#+一个更容易理解的等价代码如下:
route-n|
whilereaddeswhatmaskiface;do#通过管道输出设置的变量
echo$des$what$mask$iface
done#这段代码的结果更上面的相同。
#但是,UlrichGayer指出...
#+这段简化版等价代码在while循环里用了子shell
#+因此当管道终止时变量都消失了。
#--------------------------------------------------------------
#
#然而,FilipMoritz说上面的两个例子有一个微妙的区别,
#+见下面的代码
(
route-n|whilereadx;do((y++));done
echo$y#$yisstillunset
whilereadx;do((y++));done<<(route-n)
echo$y#$yhasthenumberoflinesofoutputofroute-n
)
#更通俗地说(译者注:原文本行少了注释符)
(
:|x=x
#似乎启动了子shell,就像
:|(x=x)
#
x=x<<(:)
#并没有。
)
23.进程替换
314
#这个方法在解析csv和类似格式时很有用。
#也就是在效果上,原始SuSE系统的代码片段就是做这个用的。
注解[1]这个与命名管道(使用临时文件)的效果相同,而且事实上,进程替换也
曾经用过命名管道。
23.进程替换
315
第二十六章.列表结构
and列表or列表结构提供了连续执行若干命令的方法,可以有效地替换复杂的
嵌套if/then,甚至case语句。
链接多个命令
and列表
command-1&&command-2&&command-3&&...command-n
只要前一个命令返回true(即0),每一个命令就依次执行。当第一个false(即
0)返回时,命令链条即终止(第一个返回false的命令是最后一个执行的)。
YongYe早期版本的俄罗斯方块游戏脚本里,一个有趣的双条件and列表用法:
equation()
{#corealgorithmusedfordoublingandhalvingthecoordinate
s
[[${cdx}]]&&((y=cy+(ccy-cdy)${2}2))
eval${1}+=\"${x}${y}\"
}
26-1.使用and列表来测试命令行参数
26.列表结构
316
#!/bin/bash
#andlist
if[!-z"$1"]&&echo"Argument#1=$1"&&[!-z"$2"]&&
\
#^^^^^^
echo"Argument#2=$2"
then
echo"Atleast2argumentspassedtoscript."
#链条内的所有命令都返回true
else
echo"Fewerthan2argumentspassedtoscript."
#链条内至少有一个命令返回false
fi
#注意:"if[!-z$1]"是好用的,但是宣传与之等同的
#"if[-n$1]"并不好用。
#不过,用引号就能解决问题,
#"if[-n"$1"]"好用(译者注:原文本行内第一个引号位置错了)。
#^^小心!
#被测试的变量放在引号内总是最好的选择。
#下面的代码功能一样,用的是纯粹if/then语句。
if[!-z"$1"]
then
echo"Argument#1=$1"
fi
if[!-z"$2"]
then
echo"Argument#2=$2"
echo"Atleast2argumentspassedtoscript."
else
echo"Fewerthan2argumentspassedtoscript."
fi
#比起用“and列表要更长、更笨重。
exit$?
26.列表结构
317
26-2.使用and列表来测试命令行参数2
#!/bin/bash
ARGS=1#预期的参数数量。
E_BADARGS=85#参数数量错误时返回的值。
test$#-ne$ARGS&&\
#^^^^^^^^^^^^条件#1
echo"Usage:`basename$0`$ARGSargument(s)"&&exit$E_BADARGS
#^^
#如果条件#1结果为true(传递给脚本的参数数量错误),
#+那么执行本行剩余的命令,脚本终止。
#下面的代码行只有在上面的测试失败时才执行。
echo"Correctnumberofargumentspassedtothisscript."
exit0
#如果要检查退出值,脚本终止后运行"echo$?"
当然,and列表也可以给变量设置默认值。
arg1=$@&&[-z"$arg1"]&&arg1=DEFAULT
#如果有命令行参数,则把参数值赋给$arg1
#但是...如果没有参数,则使用DEFAULT$arg1赋值。
or列表
command-1||command-2||command-3||...command-n
只要前一个命令返回false,每一个命令就依次执行。当第一个true返回时,命令链
条即终止(第一个返回true的命令是最后一个执行的)。很明显它与“and列表
反。
26-3.or列表and列表结合使用
26.列表结构
318
#!/bin/bash
#delete.sh,不那么巧妙的文件删除工具。
#用法:delete文件名
E_BADARGS=85
if[-z"$1"]
then
echo"Usage:`basename$0`filename"
exit$E_BADARGS#Noarg?Bailout.
else
file=$1#Setfilename.
fi
[!-f"$file"]&&echo"File\"$file\"notfound.\
Cowardlyrefusingtodeleteanonexistentfile."
#AND列表,如果文件不存在则显示出错信息。
#注意,echo消息内容分成了两行,中间通过转义符(\)连接。
[!-f"$file"]||(rm-f$file;echo"File\"$file\"deleted."
)
#OR列表,删除存在的文件。
#注意上面的逻辑颠倒。Notelogicinversionabove.
#“AND列表”在得到true时执行,“OR列表在得到false时执行。
exit$?
如果or列表第一个命令返回true,它会执行。
26.列表结构
319
#==>下面的代码段来自/etc/rc.d/init.d/single
#+==>作者MiquelvanSmoorenburg
#+==>说明了"and""or"列表。
#==>带箭头的注释是本文作者添加的。
[-x/usr/bin/clear]&&/usr/bin/clear
#==>如果/usr/bin/clear存在,则调用它。
#==>调用命令之前检查它是否存在,
#+==>可以避免出错消息和其他怪异的结果。
#==>...
#Iftheywanttorunsomethinginsingleusermode,mightasw
ellrunit...
foriin/etc/rc1.d/S[0-9][0-9]*;do
#检查脚本是否存在。
[-x"$i"]||continue
#==>如果对应的文件在$PWD*没有*找到,
#+==>则跳回到循环顶端继续运行
#丢弃备份文件和rpm生成的文件。
case"$1"in
*.rpmsave|*.rpmorig|*.rpmnew|*~|*.orig)
continue;;
esac
["$i"="/etc/rc1.d/S00single"]&&continue
#==>设置脚本名,但先不要执行
$istart
done
#==>...
and列表or列表退出状态就是最后一个执行的命令的退出状态。
聪明地结合and列表or列表是可能的,但是程序逻辑会很容易地变得令人费
解,需要密切注意操作符优先规则,而且,会带来大量的调试工作。
26.列表结构
320
false&&true||echofalse#false
#下面的代码结果相同
(false&&true)||echofalse#false
#但这个就不同了
false&&(true||echofalse)#(什么都不显示)
#注意语句是从左到右组合和解释的。
#通常情况下最好避免这种复杂性。
#感谢,S.C.
A-77-4解释了用and列表/or列表来测试变量。
26.列表结构
321
25.别名
Bash 别名本质上不外乎是键盘上的快捷键,缩写呢是避免输入很长的命令串的
一种手段.举个例子,~/.bashrc文件中包含别名lm="ls-l|more,而后每个
命令行输入的lm[1]将会自动被替换成ls-l|more.这可以节省大量的命令行
输入和避免记住复杂的命令和选项.设定别名rm="rm-i"(交互的删除模式)
止无意的删除重要文件,也许可以少些悲痛.
脚本中别名作用十分有限.如果别名可以有一些C预处理器的功能会更好,例如宏扩
,但不幸的是bash别名中没有扩展参数.[2]另外,脚本在"复合结构"中并不能扩
展自身的别名,例如if/then,循环和函数.另一个限制是,别名不能递归扩展.基本
上是我们无论怎么喜欢用别名都不如函数function来的更有效.
样例25-1.脚本中的别名
#!/bin/bash
#alias.sh
shopt-sexpand_aliases
#必须设置此选项,否则脚本不能别名扩展.
#首先来点好玩的东西.
aliasJesse_James='echo"\"AliasJesseJames\"wasa1959comedy
starringBobHope."'
Jesse_James
echo;echo;echo;
aliasll="ls-l"
#可以任意使用单引号(')或双引号(")把别名括起来.
echo"Tryingaliased\"ll\":"
ll/usr/X11R6/bin/mk*#*别名可以运行.
echo
25.别名
322
directory=/usr/X11R6/bin/
prefix=mk*#Seeifwildcardcausesproblems.
echo"Variables\"directory\"+\"prefix\"=$directory$prefix"
echo
aliaslll="ls-l$directory$prefix"
echo"Tryingaliased\"lll\":"
lll#所有/usr/X11R6/bin文件清单以mk开始.
#别名可以处理连续的变量--包含wildcard--o.k.
TRUE=1
echo
if[TRUE]
then
aliasrr="ls-l"
echo"Tryingaliased\"rr\"withinif/thenstatement:"
rr/usr/X11R6/bin/mk*#*结果报错!
#别名在复合的表达式中并没有生效.
echo"However,previouslyexpandedaliasstillrecognized:"
ll/usr/X11R6/bin/mk*
fi
echo
count=0
while[$count-lt3]
do
aliasrrr="ls-l"
echo"Tryingaliased\"rrr\"within\"while\"loop:"
rrr/usr/X11R6/bin/mk*#*这里的别名也没生效.
#alias.sh:57:rrr:命令未找到
letcount+=1
done
25.别名
323
echo;echo
aliasxyz='cat$0'#列出了自身.
#注意强引.
xyz
#这看起来能工作,
#+尽管bash文档不介意这么做.
#
#然而,SteveJacobson指出,
#+"$0"参数的扩展在上面的别名申明后立刻生效.
exit0
取消别名的命令删除之前设置的别名.
样例25-2.unalias:设置和取消一个别名
25.别名
324
#!/bin/bash
#unalias.sh
shopt-sexpand_aliases#开启别名扩展.
aliasllm='ls-al|more'
llm
echo
unaliasllm#取消别名.
llm
#'llm'不再被识别后的报错信息.
exit0
bash$./unalias.sh
total6
drwxrwxr-x2bozobozo3072Feb614:04.
drwxr-xr-x40bozobozo2048Feb614:04..
-rwxr-xr-x1bozobozo199Feb614:04unalias.
sh
./unalias.sh:llm:命令未找到
注意
[1]...作为命令行的第一个词.显然别名只在命令的开始有意义.[2]然而,别名可用
来扩展位置参数.
25.别名
325

Navigation menu