admin 管理员组

文章数量: 1184232


2024年3月19日发(作者:ajaxsend)

实践与经验 

文章编号:1007—1423(2014)30—0028—04 DOI:10.39696.issn.1007-1423.2014.30.009 

小结shell脚本编程注意事项 

石庆冬 

(英特尔移动通信技术(北京)有限公司,北京100015) 

摘要: 

Bash是目前UNIX/Linux操作系统中流行的shell之一.它是Bourne shell的超集。Bash简单易学,没有复杂的语法结 

构。但与其他编程语言一样,Bash有很多需要编程者注意之处。通过举例,对常见的容易被忽视的地方进行讲解,无 

论对于Bash的初学者还是中高级用户都有很好的借鉴意义。 

关键词: 

Linux shell;Bash;脚本编程;UNIX 

O 引言 

Linux平台下的shell有很多种.Bash是Linux的缺 

省she1l Android是一种以Unux为基础的开源操作系 

文件名可以使用短线(也叫减号或连字符),但首 

字母不要使用短线。首字母为短线时,短线后面的字母 

会被误认为是命令的选项。虽然有办法让命令规避该 

误解。但最好的选择是:避免短线出现在文件名之首。 

统.随着Android的兴起.需要学习和使用Linux shell 

尤其是Bash的人越来越多。Bash是一门脚本语言。像 

其他语言一样.Bash也有“陷阱”.需要引起用户的注 

意。 

2 注意命令退出状态值是否被掩盖 

在脚本中.每行只放一条单独的命令。尽量少用组 

合命令,否则可能会掩盖命令退出状态值。例如,将命 

令date的输出赋给变量var.同时将其定义为只读变 

量.如果不小心输入了data,整条命令肯定会不成功。 

但如果仅通过查看退出状态¥?的值.却显示整条命令 

是成功的: 

¥readonly va ̄-¥(data) 

bash:data:command not found 

1 文件命名 

文件名不要使用空格、星号和问号等特殊字符,尽 

可能使用字母数字下画线。例如,批量备份文件,可以 

对文件名进行循环。假设其中有一个带空格的文件,名 

字为name list.txt.当对其进行备份时.看看会发生什 

么: 

¥foo=”name list.txt” 

¥echo¥? 

0 

¥cp¥foo¥{f0o}—backup 

cp:target、list.txt

backup is not a directory 

_

命令data失败了.但将变量定义为只读的命令是 

成功的。¥?记录的是命令readonly的运行结果。 

使用管道的时候注意.整条命令的退出状态决定 

于管道之后命令的退出状态。例如,用echo命令显示 

出错了。将cp命令展开后是:cp name list.txt 

name list.txt

backup.该命令试图将名字为name和list. 

_

txt的文件复制到目录list.txt_backup里面.这显然不 

对。在文件名两边加上引号,即用cp”¥foo…’¥{fool 

_

句话.再用管道接Wc命令显示这句话的字节数。如 

backup”就可以避免上述错误.但最好的选择是:不在 

文件名中使用空格 

果将echo中的字母0误输人为数字0.整条命令会失 

败.但¥?的值表现为成功: 

¥echO”he110 Mike”l WC—c 

回 现代计算机2014.10下 

bash:echO:command not found 

¥echo¥? 

O 

从以上的例子可知.在脚本中。对组合命令的退出 

状态值进行判断。以决定其后的逻辑如何进行时.可能 

会掩盖错误 

3 充分利用shelI选项.让脚本更健壮 

用set ̄lshopt命令可以设置很多选项。弄清楚各 

个选项的默认值,并恰当地设置它们,能够使得脚本更 

健壮。下面举几个与选项相关的例子。 

脚本,其实是一条条的命令排在一起.按照逻辑顺 

序先后执行。如果某条命令失败了,根据默认规则,其 

后的命令仍可继续运行。这对于用户及时发现问题是 

不利的。用命令set—e或者set一0 errexit打开选项er. 

rexit之后,某条命令失败了的话,脚本将立刻退出。 

Bash允许不声明变量.也没有对变量赋初值而直 

接使用变量,不像有的编译语言那样严格。脚本中如果 

引用了一个没有赋初值的变量是危险的.虽然在Bash 

中对未赋初值的变量可以等同于值为空的变量.但还 

是不一样的:有值的变量在变量表里面可以查询到.未 

定义的变量是查不到的。用命令set—U或者set—o 

n叫nset打开选项n0unset之后.当引用未定义的变量 

时,Bash将给出错误提示。例如: 

¥set—u #打开选项nounset 

¥echo Sx#因为x无初值.提示出错 

bash:x:unbound v able 

用shitf[N】命令左移位置参数时,有时候左移的个 

数会大于剩余位置参数的个数,而用户又很难注意到。 

看下面这个脚本。显然.它的功能是打印编号为奇数的 

脚本参数: 

¥cat sfi.sh 

#!/bin/bash 

whileⅡ¥#>0】] 

do 

echo¥1 

shift 2 

done 

运行一下试试.虽能打印出编号为奇数的参数.但 

却显示了无穷多个a5: 

¥sft.sh al a2 a3 a4 a5 

a1 

a3 

a5 

a5 #显示无穷个a5 

在显示a5之前,参数仅剩1个,¥#等于1,却左移 

2个参数(shitf 2),于是发生异常。打开选项shift

_

ver 

bose,当左移参数的个数大于剩余的参数个数时,会提 

示出错,脚本修改如下: 

¥cat sft

_

a.sh 

#!/bin/bash 

shopt—s shift

_

verbose 

while[【¥#>0】] 

do 

echo¥1 

shitf 2 

【【¥?!=0】]&&exit 1 

done 

这时运行脚本,显示完编号为奇数的参数后,Bash 

给出了“移动个数超限”的提示,然后脚本正常结束: 

¥sft

_

a.sh al a2 a3 a4 a5 

a1 

a3 

a5 

/sfi

_

a.sh:line 6:shitf:2:shitf count out of range 

4 脚本中的命令并非都在同一个子shelI中 

个脚本可以调用其他脚本,可以使用管道,脚本 

中的命令并非都在同一个子shell中。尤其是有变量值 

传递时,更需要注意这一点。下面的脚本对文件 

name

_

list.txt(假设该文件一共有3行)进行处理,打印 

每一行的行号和总行数: 

¥cat file

lines.sh 

_

#!/bin/bash 

count=O 

cat name

_

list.txt 1 while read line 

do 

((count++)) 

echo”Line¥count” 

done 

echo”This file has¥count lines” 

铜 {+笛如 a^¨-^ 

国 

实践与经验 

表面看,脚本没有问题。运行后发现,总行数显示 

为0: 

¥file

_

lines.sh 

Line 1 

Line 2 

Line 3 

Thisfile has 0lines 

因为管道之后的while循环运行在新的子shell之 

中.不同子shell中的同名变量并不是同一个变量。改 

用输入重定向来读取文件.就不存在这样的问题了: 

¥cat ifle linesenhance.sh 

_

#!/bin/bash 

count=0 

while read line 

do 

((count++)) 

echo”Line¥count” 

done<name list.txt 

echo”This file has¥count lines” 

运行该脚本.可见总行数是正确的: 

¥file

_

lines

_

enhance.sh 

Line 1 

Line 2 

iLne 3 

This file has 3 lines 

5 使用判断命令时的注意事项 

使用Bash中括号形式的判断命令时,有时容易忘 

记中括号与条件表达式之间需要有空格。Bash脚本中 

的每一行都是一条命令.而不是像C语言那样每一行 

是一条语句。命令test 8一gt 6用来判断8大于6是否 

成立.test与参数即条件表达式8一gt 6之间需要空格 

是很容易理解的.一对中括号是命令test的替代,参数 

与左右中括号之间需要有空格就容易理解了。缺少空 

格时,命令[8一gt 6】运行出错,加上空格就没有问题了: 

¥[8一gt 6】 

[8:command not f0und 

¥【8-gt 6】 

在对字符串进行判断时.如果字符串含有空白字 

符,一定要用引号括起来,或者用双中括号: 

现代计算机2014.10下 

¥st[=--”we al'e friends” 

¥【一n¥str] 

bash:[:too many arguments 

¥【一n”¥str”】 

¥[【一n¥str】] 

在用双中括号来判断两个字符串是否相等时,当 

等号右边没有用引号时.右边的字符串会被当作模式 

处理.如果出现星号和问号则被当作通配符。例如: 

¥a=Wednesday 

¥[[Sa=Wed 11&&[[Sa=Wed???day】] 

¥echo 57 

0 

如果想判断a是否等于Wed*.不希望星号被解释 

为通配符.将等号右边的字符串加上引号即可。 

if cmdl;then cmd2;else cmd3;fi可以用cmdl&& 

cmd2 ll cmd3代替.特别当命令cmdl、cmd2和cmd3比 

较简短时.很多用户喜欢使用这种替代形式。多数情况 

下,这样替代没有问题。但是,如果cmdl成功但cmd2 

失败了.1I后面的cmd3就会执行。看来,这两种写法的 

效果是不一样的,使用 else结构是严谨的,尽可能不 

要使用替代形式。 

6 脚本中做好充分的判断 

有些命令.特别是用通配符删除多个文件时,一定 

要做好充分判断.否则很可能造成无法挽回的后果。例 

如,cd dir_foo;1TI1一fr ,如果目录dir_foo不存在,则cd 

命令失败.当前目录下面的所有的文件和子目录将被 

删除.这会导致灾难性的结果。应该改为cd dir_foo 

&&liB—fr}。或者用如下判断: 

if cd dir

_

foo;then 

1-in—fr 

fi 

7 注意sed的退出状态值 

命令grep用于模式过滤查找,找到了则退出,状态 

为0.没找到为非0。用流编辑工具sed也可以模式查 

找.或者模式查找并替换,无论找到还是没找到,sed的 

退出状态都为0,有语法错误时,为非0。习惯了使用 

grep的用户.在学习sed时需要注意这一点。 

8 不在函数和脚本中使用别名 

如果在一个函数内定义别名,在函数被调用之前. 

变短,但函数可以带参数,别名不能带参数。综上所述, 

尽可能不要在函数和脚本中使用别名。 

别名是不生效的。在一个脚本内使用别名时注意.脚本 

内的别名默认不能展开.因为在非交互shell中.选项 

aliases t 

expand 默认是关闭的。在脚本中先运行shoD

9 结语 

虽然Bash是--H较简单的脚本语言.但并非没有 

“陷阱”。Bash脚本编程当中需要注意的细节还有很多, 

S expand

_

aliases开启该选项。别名才可以展开。别名 

仅仅一篇短文难免挂一漏万。希望Bash的学习者多总 

结,多分享心得。 

的主要作用就是将长命令缩短.函数也可以将长命令 

参考文献: 

[1】唐华.Linux操作系统高级教程[M].北京:电子工业出版社,2008 

[2]Mike Jonkman.Bash Pifalls[OL].http://mywiki.wooledge.org/BashPifalls/ 

作者简介: 

石庆冬(1971一),男,江苏扬州人,博士,高级软件工程师,从事领域为Linux下的软件开发与集成工作 

收稿日期:2014—09—26 修稿13期:2014—09—26 

Summary of shell Script PrOgramming Notes 

SHI Qing-dong 

(Intel Mobile Communications Technology(Bering)Ltd.,Beijing 100015) 

Absttact: 

Bash is one of the popular shell scripts on UNIX/Linux operating system at present,it is a superset of Bourne sheH.Bash is simple and 

easy to learn,the grammar is not complicated.But as other programming languages,Bash programmers need to pay attention a lot.By 

several examples,explains some aspects which are easily overlooked,provides very good summary for both Bash beginners and advanced 

users. 

Keywords: 

Linux shell;Bash;Script Programming;UNIX 

硼 *笛加 on1^1n 


本文标签: 命令 脚本 使用 变量