Shell编程经验总结之―编程习惯
时间:2005-11-16 来源:xwfang
养成好的编程习惯将一辈子受益,shell编程也是如此.
在上篇Shell编程经验总结之-重定义和跨平台一文中提到的内容对于大型shell系统的构建,以及平台命令存在差异的问题都能很好的解决,同样也可以简单的实现多语言的支持,你一定想到了只要把语言信息定义成资源文件作为变量引入到系统中即可.如果你没有构建过大型系统对于以上好处你不一定能体会到J.
好了,如果你开始因为我的影响慢慢改变,开始了习惯新的方法来组织你的shell脚本,那么你会遇到很多新的问题。现在我就从一些细小的地方讲述一下shell编程应该注意的一些习惯。
首先,从脚本的入口讲起.建议每个sh文件(即用户一般直接执行的)都有一个main()函数,然后在main函数里面统一写执行主体.然后再在shell中调用main函数.如下:
#!/bin/bash
###############################################################################
#function:
#input:
#output:
#return:
###############################################################################
main()
{
#根据需要进行参数处理
get_my_log_all
return $?
}
###############################################################################
. /home/away/common/tools.inc
cd ${0%/*}
#echo `pwd`
main "$@"
exit $?
上述代码定义了一个main函数,然后引入我们需要的功能函数,存放在tools.inc中,这些都已经很熟悉了,接下来的内容 cd ${0%/*} 可能很多人没见过。干什么用的呢?如果你对shell还算熟悉的话,应该知道这是进入本脚步所在的目录,即把当前的工作目录设置成本脚本所在的目录。
${}相关的知识可以通过man bash (随便找个shell都可以)查询到,下面我通过一些例子说明 ${} 的一些特异功能:
假设我们定义了一个路径变量为:
file=/dir1/dir2/dir3/my.file.txt
我们可以用 ${ } 分別替换获得不同的值:
${file#*/}:拿掉第一条 / 及其左边的字串:dir1/dir2/dir3/my.file.txt
${file##*/}:拿掉最后一条 / 及其左边的字串:my.file.txt
${file#*.}:拿掉第一个 . 及其左边的字串:file.txt
${file##*.}:拿掉最后一个 . 及其左边的字串:txt
${file%/*}:拿掉最后条 / 及其右边的字串:/dir1/dir2/dir3
${file%%/*}:拿掉第一条 / 及其右边的字串:(空值)
${file%.*}:拿掉最后一个 . 及其右边的字串:/dir1/dir2/dir3/my.file
${file%%.*}:拿掉第一个 . 及其右边的字串:/dir1/dir2/dir3/my
简单来说就是
两个#(或者%)表示最大匹配
一个#(或者%)表示最小匹配
#从左边开始匹配,%从右边开始匹配
但是是找到匹配的被切掉.
这样做有什么好处呢?第一,你始终能通过pwd准确的知道你脚本所在的绝对路径(如果系统中其他地方需要的话可以用全局变量保存起来)。第二可以避免一个因为工作目录的问题而导致你的系统不能正常运行的问题。举个简单的例子说明一下。
你可能会有一个目录结构如下的系统:
bin/test.sh
cfg/test.cfg
一个可执行文件test.sh 一个配置文件test.cfg,test.sh要用到此配置文件中的配置信息。
Test.sh的内容如下:
#!/bin/bash
#具体代码省略
……
#你因为不知道系统所在的绝对路径而使用相对路径访问配置文件。
cat ../cfg/test.cfg | grep …… #后面进行一些其他操作,都省略
……
上面的代码看起来是没什么问题,并且如果你平时都进入test.sh所在的bin目录执行次脚本你不会发现有任何问题。但是有一天你心血来潮J,不想cd到bin目录执行此脚本。而是在bin所在的父目录执行bin/test.sh时,你就会惊讶,天哪,我的程序以前都运行的很好的, 为什么现在不行了呢?你可能还在抱怨谁动了我的奶酪J。其实是你自己没有好好构建这个系统。因为当前工作目录是bin的父目录,而cat ../cfg/test.cfg当然不是你想要的那个文件了,一般是不会存在的,除非有巧合。
再往下看程序 main "$@" 和 exit $?。这个exit $?我相信大家都很清楚就不多说了(注:bash不支持在脚本中直接return,只能在函数中进行return,而ksh支持在脚本中也可以return)。现在主要说说main “$@”(如果你了解$@和$*的区别,你可以跳过这一段不看,不过我认识的大多数人是不知道这二者的区别的)你在我的文章和脚本中应该看到过很多次了,我每次都会这样写的,把用户输入的参数原样传递给main函数进行处理。很多人都知道$*代表着所有的位置参数,并且要引用所有参数时一般会用$*,大多数情况这个是能运行良好的,但是如果你给出的参数有一个是有空格分开的字符串,但是你又想把他作为一个参数,你会用””引起来,举例如下:
test.sh内容
#!/bin/bash
……
#!/bin/bash
main()
{
if [ $# -ne 3 ]
then
{
echo $#
echo "Usage:$0 prara1 para2 para3"
return 1
}
fi
echo "ok"
}
main $*
exit $?
那么执行参数如下时你会得到这样的结果:
away@DXX-3900:~/test$ ./test.sh a b "c d"
4
Usage:./test prara1 para2 para3
为什么会这样呢?这就是$*的缺点,你可以在程序中把$#和$1 $2 $3 $4打印出来看看此时他的值是什么,为了弥补这个缺陷,shell提供了$@解决这个问题,他能把原有的包含在””里面的参数继续包含在””,但是必须在$@外面加””,再传递给接受此参数列表的函数。
以下是一些常用的shell特殊内置变量:
$@ 所有的位置参数,在””中能把包含在””中的字符串仍然作为一个整体
$* 所有的位置参数,默认以空格作为分割
$? 上一个命令的返回值
$# 位置参数的个数
$$ 本进程的进程id
$! 最近的一个后台进程的id
$0 本进程名
$1 ~ $9 各个位置参数
讲完了脚本的整体架构,现在我们进入到函数里面,根据shell的特点讲述函数编写注意的一些事项。
顺便说一下编码风格方面的问题,其他语言的编码风格的资料也很多,但是shell因为有自己的特点我在此基础上补充一些。
第一,为了你程序的健壮性和安全性,请检查参数个数符合你的要求并尽量检查各个参数的有效性。
第二,用有意义的局部变量保存位置参数,不要直接使用位置参数,不然你的程序不但晦涩难懂,对于以后的扩展和修改也是非常不利的。
作为一个程序员,特别是为公司开发的系统让别人无法理解和很难维护都是不可接受的,shell中的技巧又特别多,建议尽量少用,如果不能不用也最好给出简明的注释,以备后人维护。具体的要求可以根据具体情况调整,比如你是给自己用的可以先简单的给出,如果给大家用的,在接口上至少能给出比较清晰明了的注释说明,在一些很晦涩的代码上最好能给出详细的说明。
例如下面的代码一眼能看出具备那些功能sed的水平可以说是很不错了:)
echo "dfsdi4563462342fj[123]"| sed -n 's/[0-9][0-9]*([^0-9a-zA-Z]*)[0-9][0-9]
*([^0-9a-zA-Z]*)[0-9][0-9]*([^0-9a-zA-Z]*)$/010203/p'|
sed -n 's/[0-9][0-9]*([^0-9a-zA-Z]*)[0-9][0-9]*([^0-9a-zA-Z]*)$/0102/p'|
sed -n 's/[0-9][0-9]*([^0-9a-zA-Z]*)$/01/p'
其他建议如下:
命名:
1 shell变量和函数名都基本统一用有意义的单词的小写加下划线的形式
2 全局的变量和常量用大写,用下划线进行连接
注释:
如果是真的给自己看的,没有时间写注释是可以原谅的,但是写给大家用的请尽量说明程序的用途