许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让bash脚本变得健壮的技术。

使用set -u

你因为没有对变量初始化而使脚本崩溃过多少次?对于我来说,很多次。

chroot=$1
...
rm -rf $chroot/usr/share/doc

如果上面的代码你没有给参数就运行,你不会仅仅删除掉chroot中的文档,而是将系统的所有文档都删除。那你应该做些什么呢?好在bash提供了set -u,当你使用未初始化的变量时,让bash自动退出。你也可以使用可读性更强一点的set -o nounset。

#bash /tmp/shrink-chroot.sh
$chroot=
#bash -u /tmp/shrink-chroot.sh
/tmp/shrink-chroot.sh: line 3: $1: unbound variable
#

使用set -e

你写的每一个脚本的开始都应该包含set -e。这告诉bash一但有任何一个语句返回非真的值,则退出bash。使用-e的好处是避免错误滚雪球般的变成严重错误,能尽早的捕获错误。更加可读的版本:set -o errexit

使用-e把你从检查错误中解放出来。如果你忘记了检查,bash会替你做这件事。不过你也没有办法使用$?来获取命令执行状态了,因为bash无法获得任何非0的返回值。你可以使用另一种结构:

command

if [ "$?"-ne 0]; then echo "command failed"; exit 1; fi

可以替换成:

command || { echo "command failed"; exit 1; }

或者使用:

if ! command; then echo "command failed"; exit 1; fi

如果你必须使用返回非0值的命令,或者你对返回值并不感兴趣呢?你可以使用 command || true ,或者你有一段很长的代码,你可以暂时关闭错误检查功能,不过我建议你谨慎使用。

set +e
command1
command2
set -e

相关文档指出,bash默认返回管道中最后一个命令的值,也许是你不想要的那个。比如执行 false | true 将会被认为命令成功执行。如果你想让这样的命令被认为是执行失败,可以使用 set -o pipefail

程序防御 - 考虑意料之外的事

你的脚本也许会被放到“意外”的账户下运行,像缺少文件或者目录没有被创建等情况。你可以做一些预防这些错误事情。比如,当你创建一个目录后,如果父目录不存在,mkdir 命令会返回一个错误。如果你创建目录时给mkdir命令加上-p选项,它会在创建需要的目录前,把需要的父目录创建出来。另一个例子是 rm 命令。如果你要删除一个不存在的文件,它会“吐槽”并且你的脚本会停止工作。(因为你使用了-e选项,对吧?)你可以使用-f选项来解决这个问题,在文件不存在的时候让脚本继续工作。

准备好处理文件名中的空格

有些人从在文件名或者命令行参数中使用空格,你需要在编写脚本时时刻记得这件事。你需要时刻记得用引号包围变量。

if [ $filename = "foo" ];

当$filename变量包含空格时就会挂掉。可以这样解决:

if [ "$filename" = "foo" ];

使用$@变量时,你也需要使用引号,因为空格隔开的两个参数会被解释成两个独立的部分。

# foo() { for i in $@; do echo $i; done }; foo bar "baz quux"
bar
baz
quux
# foo() { for i in "$@"; do echo $i; done }; foo bar "baz quux"
bar
baz quux

我没有想到任何不能使用"$@"的时候,所以当你有疑问的时候,使用引号就没有错误。

如果你同时使用find和xargs,你应该使用 -print0 来让字符分割文件名,而不是换行符分割。

#touch "foo bar"
#find | xargs ls
ls: ./foo: No such file or directory
ls: bar: No such file or directory
#find -print0 | xargs -0 ls
./foo bar

设置的陷阱

当你编写的脚本挂掉后,文件系统处于未知状态。比如锁文件状态、临时文件状态或者更新了一个文件后在更新下一个文件前挂掉。如果你能解决这些问题,无论是 删除锁文件,又或者在脚本遇到问题时回滚到已知状态,你都是非常棒的。幸运的是,bash提供了一种方法,当bash接收到一个UNIX信号时,运行一个 命令或者一个函数。可以使用trap命令。

trap command signal [signal ...]

你可以链接多个信号(列表可以使用kill -l获得),但是为了清理残局,我们只使用其中的三个:INT,TERM和EXIT。你可以使用-as来让traps恢复到初始状态。

信号描述

INT

Interrupt - 当有人使用Ctrl-C终止脚本时被触发

TERM

Terminate - 当有人使用kill杀死脚本进程时被触发

EXIT

Exit - 这是一个伪信号,当脚本正常退出或者set -e后因为出错而退出时被触发

当你使用锁文件时,可以这样写:

if [ ! -e $lockfile ]; thentouch $lockfilecritical-sectionrm $lockfile
elseecho "critical-section is already running"
fi

当最重要的部分(critical-section)正在运行时,如果杀死了脚本进程,会发生什么呢?锁文件会被扔在那,而且你的脚本在它被删除以前再也不会运行了。解决方法:

if [ ! -e $lockfile ]; thentrap " rm -f $lockfile; exit" INT TERM EXITtouch $lockfilecritical-sectionrm $lockfiletrap - INT TERM EXIT
elseecho "critical-section is already running"
fi

现在当你杀死进程时,锁文件一同被删除。注意在trap命令中明确地退出了脚本,否则脚本会继续执行trap后面的命令。

竟态条件 (wikipedia)

在上面锁文件的例子中,有一个竟态条件是不得不指出的,它存在于判断锁文件和创建锁文件之间。一个可行的解决方法是使用IO重定向和bash的noclobber(wikipedia)模式,重定向到不存在的文件。我们可以这么做:

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null;
thentrap 'rm -f "$lockfile"; exit $?' INT TERM EXITcritical-sectionrm -f "$lockfile"trap - INT TERM EXIT
elseecho "Failed to acquire lockfile: $lockfile"echo "held by $(cat $lockfile)"
fi

更复杂一点儿的问题是你要更新一大堆文件,当它们更新过程中出现问题时,你是否能让脚本挂得更加优雅一些。你想确认那些正确更新了,哪些根本没有变化。比如你需要一个添加用户的脚本。

add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R

当磁盘空间不足或者进程中途被杀死,这个脚本就会出现问题。在这种情况下,你也许希望用户账户不存在,而且他的文件也应该被删除。

rollback() {del_from_passwd $userif [ -e /home/$user ]; thenrm -rf /home/$userfiexit
}trap rollback INT TERM EXIT
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
trap - INT TERM EXIT

在脚本最后需要使用trap关闭rollback调用,否则当脚本正常退出的时候rollback将会被调用,那么脚本等于什么都没做。

保持原子化

又是你需要一次更新目录中的一大堆文件,比如你需要将URL重写到另一个网站的域名。你也许会写:

for file in $(find /var/www -type f -name "*.html"); doperl -pi -e 's/www.example.net/www.example.com/' $file
done

如果修改到一半是脚本出现问题,一部分使用www.example.com,而另一部分使用www.example.net。你可以使用备份和trap解决,但在升级过程中你的网站URL是不一致的。

解决方法是将这个改变做成一个原子操作。先对数据做一个副本,在副本中更新URL,再用副本替换掉现在工作的版本。你需要确认副本和工作版本目录在同一个磁盘分区上,这样你就可以利用Linux系统的优势,它移动目录仅仅是更新目录指向的inode节点。

cp -a /var/www /var/www-tmp
for file in $(find /var/www-tmp -type -f -name "*.html"); doperl -pi -e 's/www.example.net/www.example.com/' $file
done
mv /var/www /var/www-old
mv /var/www-tmp /var/www

linux shell 语句出错自动退出 调试 检查 脚本相关推荐

  1. shell 语句出错自动退出

    许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分.不幸的是,shell脚本在运行异常时会受到非常大的影响.在写脚本时将这类问题最小化是十分必要的.本文中我将介绍一些让bash脚本变得 ...

  2. linux shell 调试 检查 脚本

    shell脚本的三种调试方法: -n 读一遍脚本中的命令但不执行,用来检查脚本中的语法错误 -v 一边执行脚本,一边将执行过的脚本命令打印到标准输出端 -x 提供跟踪执行信息,将执行的每一条命令和结果 ...

  3. linux shell编程从入门到精通pdf_SHELL脚本编程入门

    一. SHELL入门 1.1 变量 1.1.1 变量名规范 变量是由任何字母.数字.下划线组成的字符串,且不能以数字开头. 区分字母大小写 变量.等号.值中间不能出现任何空格 实例 注:$var1+$ ...

  4. linux shell 三种自动交互 方法

    方法一 自动交互最关键的就是交互信息的自动输入,首先联想到文件重定向,在shell编程中有这样一种用法(参考LINUX与UNIX SHELL编程指南 chapt 5.7):"command ...

  5. linux shell提示文件不存在,Bash检查是否显示文件不存在?

    倚天杖 Martin Tournoij的答案和DevSolar的答案都提供了正确的解决方案和有用的背景信息:就[ ... ]一种情况而言,在另一种情况下[[ ... ]].因为它可能不是很明显,如果当 ...

  6. editplus配置 linux shell 语法高亮 自动补全

    1.文件下载 我们可以从EditPlus官网的User Files获得不同语言的语法高亮和自动补全文件. Bash版本下载地址为:http://www.editplus.com/dn.php?n=ba ...

  7. linux嵌套字幕工具,Linux(NAS通用)下自动匹配射手字幕脚本

    2014/02/17更新:加入因超时致使获取字幕不成功的情况. 2014/02/15更新:优化了脚本,充分考虑获取字幕失败后的情况,保证在下次运行时还能再次获取上次失败的字幕. 从头学起,用了一天的时 ...

  8. ubuntu 9.10下linux qq 自动退出的问题

    ubuntu 9.10下linux qq 自动退出的问题 2009年11月25日 阅读评论 发表评论 升级到ubuntu 2009以后, linux qq经常会自动退出,不清楚什么问题,导致我用了一段 ...

  9. 【shell】Linux Shell远程执行命令

    目录 shell远程执行 前提条件: 对于简单的命令: 对于脚本的方式: SSH命令格式 主要参数说明 ssh控制远程主机,远程执行命令步骤 准备工作 基于公私钥认证远程登录可能存在的不足 ssh 执 ...

最新文章

  1. 头文件的包含以及命名空间的引入尽量写在cpp里
  2. android 点击退出账号,Android应用退出登录的实现方法
  3. GDCM:gdcm::Tag的测试程序
  4. 学习jvm,关于MAT an internal error occurred during:Parsing heap dump from问题
  5. c++ 不能分配给为0的数组_【嵌入式C】你有想过quot;数组下标quot;为何从0开始吗?...
  6. hnu 暑期实训之相同生日
  7. UNIX文件系统结构
  8. navcat定时备份mysql_Navicat for MySQL定时备份数据库及数据恢复
  9. 基于UML的人事管理系统
  10. FDDB人脸数据集/python图像批量处理
  11. 34亿骗局!如何“杀死”萝卜章?
  12. 解决因多网卡导致dubbo注册到ZK的IP错误问题,dubbox(当当,2.8.4)升级至dubbo(Apache,2.7.15)并集成
  13. C——识别键盘按下模拟键盘按下
  14. windows10 20H2版本微软账户登录不上解决方法
  15. IE浏览器设置代理及例外批处理脚本
  16. 裸辞3个月,空白期啥都没干,怎么办?
  17. 学历对于程序员找工作重要吗?我来分享一下看法
  18. 千锋逆战1903班Days13上课代码以及笔记
  19. DIAC-WOZ数据集(二)---Visual signals
  20. DPVS适配博通100G网卡

热门文章

  1. AMBA、AHB、APB、AXI总线介绍和对比
  2. PowerShell批量设置PATH环境变量
  3. CentOS7 编译安装 Mariadb
  4. 升级10.11后使用CocoaPod出现-bash: pod: command not found 解决办法
  5. 《Linux From Scratch》第三部分:构建LFS系统 第六章:安装基本的系统软件- 6.47. Gawk-4.1.1...
  6. Constraint4:default约束
  7. Caused by: java.lang.ClassNotFoundException: javax.persistence.Entity
  8. html中a标签中的onclick和href的使用(转)
  9. PLSQL_解析过程及硬解析和软解析的区别(案例)
  10. html字体颜色代码表