写出健壮的Bash脚本.doc_第1页
写出健壮的Bash脚本.doc_第2页
写出健壮的Bash脚本.doc_第3页
写出健壮的Bash脚本.doc_第4页
写出健壮的Bash脚本.doc_第5页
免费预览已结束,剩余1页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

摘要:许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让Bash脚本变得健壮的技术。许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让Bash脚本变得健壮的技术。使用set -u你因为没有对变量初始化而使脚本崩溃过多少次?对于我来说,很多次。1 chroot=$1 2 . 3 rm -rf $chroot/usr/share/doc 如果上面的代码你没有给参数就运行,你不会仅仅删除掉chroot中的文档,而是将系统的所有文档都删除。那你应该做些什么呢?好在bash提供了set -u,当你使用未初始化的变量时,让bash自动退出。你也可以使用可读性更强一点的set -o nounset。4 david% bash /tmp/shrink-chroot.sh 5 6 $chroot= 7 8 david% bash -u /tmp/shrink-chroot.sh 9 10 /tmp/shrink-chroot.sh: line 3: $1: unbound variable 11 12 david% 13 使用set -e你写的每一个脚本的开始都应该包含set -e。这告诉bash一但有任何一个语句返回非真的值,则退出bash。使用-e的好处是避免错误滚雪球般的变成严重错误,能尽早的捕获错误。更加可读的版本:set -o errexit使用-e把你从检查错误中解放出来。如果你忘记了检查,bash会替你做这件事。不过你也没有办法使用$?来获取命令执行状态了,因为bash无法获得任何非0的返回值。你可以使用另一种结构:14 command 15 16 if $?-ne 0; then echo command failed; exit 1; fi 17 可以替换成:18 command | echo command failed; exit 1; 19 或者使用:20 if ! command; then echo command failed; exit 1; fi 如果你必须使用返回非0值的命令,或者你对返回值并不感兴趣呢?你可以使用 command | true ,或者你有一段很长的代码,你可以暂时关闭错误检查功能,不过我建议你谨慎使用。21 set +e 22 23 command1 24 25 command2 26 27 set -e 相关文档指出,bash默认返回管道中最后一个命令的值,也许是你不想要的那个。比如执行 false | true 将会被认为命令成功执行。如果你想让这样的命令被认为是执行失败,可以使用 set -o pipefail程序防御 - 考虑意料之外的事你的脚本也许会被放到“意外”的账户下运行,像缺少文件或者目录没有被创建等情况。你可以做一些预防这些错误事情。比如,当你创建一个目录后,如果父目录不存在,mkdir 命令会返回一个错误。如果你创建目录时给mkdir命令加上-p选项,它会在创建需要的目录前,把需要的父目录创建出来。另一个例子是 rm 命令。如果你要删除一个不存在的文件,它会“吐槽”并且你的脚本会停止工作。(因为你使用了-e选项,对吧?)你可以使用-f选项来解决这个问题,在文件不存在的时候让脚本继续工作。准备好处理文件名中的空格有些人从在文件名或者命令行参数中使用空格,你需要在编写脚本时时刻记得这件事。你需要时刻记得用引号包围变量。28 if $filename = foo ; 当$filename变量包含空格时就会挂掉。可以这样解决:29 if $filename = foo ; 使用$变量时,你也需要使用引号,因为空格隔开的两个参数会被解释成两个独立的部分。30 david% foo() for i in $; do echo $i; done ; foo bar baz quux 31 32 bar 33 34 baz 35 36 quux 37 38 david% foo() for i in $; do echo $i; done ; foo bar baz quux 39 40 bar 41 42 baz quux 我没有想到任何不能使用$的时候,所以当你有疑问的时候,使用引号就没有错误。如果你同时使用find和xargs,你应该使用 -print0 来让字符分割文件名,而不是换行符分割。43 david% touch foo bar 44 45 david% find | xargs ls 46 47 ls: ./foo: No such file or directory 48 49 ls: bar: No such file or directory 50 51 david% find -print0 | xargs -0 ls 52 53 ./foo bar 设置的陷阱当你编写的脚本挂掉后,文件系统处于未知状态。比如锁文件状态、临时文件状态或者更新了一个文件后在更新下一个文件前挂掉。如果你能解决这些问题, 无论是删除锁文件,又或者在脚本遇到问题时回滚到已知状态,你都是非常棒的。幸运的是,bash提供了一种方法,当bash接收到一个UNIX信号时,运 行一个命令或者一个函数。可以使用trap命令。trap command signal signal .你可以链接多个信号(列表可以使用kill -l获得),但是为了清理残局,我们只使用其中的三个:INT,TERM和EXIT。你可以使用-as来让traps恢复到初始状态。信号描述INTInterrupt - 当有人使用Ctrl-C终止脚本时被触发TERMTerminate - 当有人使用kill杀死脚本进程时被触发EXITExit - 这是一个伪信号,当脚本正常退出或者set -e后因为出错而退出时被触发当你使用锁文件时,可以这样写:54 if ! -e $lockfile ; then 55 56 touch $lockfile 57 58 critical-section 59 60 rm $lockfile 61 62 else 63 64 echo critical-section is already running 65 66 fi 当最重要的部分(critical-section)正在运行时,如果杀死了脚本进程,会发生什么呢?锁文件会被扔在那,而且你的脚本在它被删除以前再也不会运行了。解决方法:67 if ! -e $lockfile ; then 68 69 trap rm -f $lockfile; exit INT TERM EXIT 70 71 touch $lockfile 72 73 critical-section 74 75 rm $lockfile 76 77 trap - INT TERM EXIT 78 79 else 80 81 echo critical-section is already running 82 83 fi 现在当你杀死进程时,锁文件一同被删除。注意在trap命令中明确地退出了脚本,否则脚本会继续执行trap后面的命令。竟态条件 (wikipedia)在上面锁文件的例子中,有一个竟态条件是不得不指出的,它存在于判断锁文件和创建锁文件之间。一个可行的解决方法是使用IO重定向和bash的noclobber(wikipedia)模式,重定向到不存在的文件。我们可以这么做:84 if ( set -o noclobber; echo $ $lockfile) 2 /dev/null; 85 86 then 87 88 trap rm -f $lockfile; exit $? INT TERM EXIT 89 90 critical-section 91 92 rm -f $lockfile 93 94 trap - INT TERM EXIT 95 96 else 97 98 echo Failed to acquire lockfile: $lockfile 99 100 echo held by $(cat $lockfile) 101 102 fi 更复杂一点儿的问题是你要更新一大堆文件,当它们更新过程中出现问题时,你是否能让脚本挂得更加优雅一些。你想确认那些正确更新了,哪些根本没有变化。比如你需要一个添加用户的脚本。103 add_to_passwd $user 104 105 cp -a /etc/skel /home/$user 106 107 chown $user /home/$user -R 当磁盘空间不足或者进程中途被杀死,这个脚本就会出现问题。在这种情况下,你也许希望用户账户不存在,而且他的文件也应该被删除。108 rollback() 109 110 del_from_passwd $user 111 112 if -e /home/$user ; then 113 114 rm -rf /home/$user 115 116 fi 117 118 exit 119 120 121 122 123 124 125 trap rollback INT TERM EXIT 126 127 add_to_passwd $user 128 129 130 131 132 cp -a /etc/skel /home/$user 133 134 chown $user /home/$user -R 135 136 137 trap - INT TERM EXIT 在脚本最后需要使用trap关闭rollback调用,否则当脚本正常退出的时候rollback将会被调用,那么脚本等于什么都没做。保持原子化又是你需要一次更新目录中的一大堆文件,比如你需要将URL重写到另一个网站的域名。你也许会写:138 for file in $(find /var/www -type f -name *.html); do 139 140 perl -pi -e s/// $file 141 142 done 如果修改到一半是脚本出现问题,一部分使用,而另一部分使用。你可以使用备份和trap解决,但在升级过程中你的网站URL是不一致的。解决方法是将这个改变做成一个原子操作。先对数据做一个副本,在副本中更新URL,再用副本替换掉现在工作的版本。你需要确认副本和工作版本目录在同一个磁盘分区上,这样你就可以利用Linux系统的优势,它移动目录仅仅是更新目录指向的inode节点。143 cp -a /var/www /var/www-tmp 144 145 for file in $(find /var/www-tmp -type -f -name *.html); do 146 147 perl -pi -e s/// $file 148 149 done 150 151 mv /var/www /var/www-old 152 153 mv /var/www-t

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论