许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让bash脚本变得健壮的技术。
使用set -u
你因为没有对变量初始化而使脚本崩溃过多少次?对于我来说,很多次。
chroot=$1
...
rm -rf $chroot/usr/share/doc如果上面的代码你没有给参数就运行,你不会仅仅删除掉chroot中的文档,而是将系统的所有文档都删除。那你应该做些什么呢?好在bash提供了set -u,当你使用未初始化的变量时,让bash自动退出。你也可以使用可读性更强一点的set -o nounset。
david% bash /tmp/shrink-chroot.sh
$chroot=
david% bash -u /tmp/shrink-chroot.sh
/tmp/shrink-chroot.sh: line 3: $1: unbound variable
david%
使用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" ];
使用$@变量时,你也需要使用引号,因为空格隔开的两个参数会被解释成两个独立的部分。
david% foo() { for i in $@; do echo $i; done }; foo bar "baz quux"
bar
baz
quux
david% foo() { for i in "$@"; do echo $i; done }; foo bar "baz quux"
bar
baz quux
我没有想到任何不能使用"$@"的时候,所以当你有疑问的时候,使用引号就没有错误。
如果你同时使用find和xargs,你应该使用 -print0 来让字符分割文件名,而不是换行符分割。
david% touch "foo bar"
david% find | xargs ls
ls: ./foo: No such file or directory
ls: bar: No such file or directory
david% 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 ]; then
touch $lockfile
critical-section
rm $lockfile
else
echo "critical-section is already running"
fi
当最重要的部分(critical-section)正在运行时,如果杀死了脚本进程,会发生什么呢?锁文件会被扔在那,而且你的脚本在它被删除以前再也不会运行了。解决方法:
if [ ! -e $lockfile ]; then
trap " rm -f $lockfile; exit" INT TERM EXIT
touch $lockfile
critical-section
rm $lockfile
trap - INT TERM EXIT
else
echo "critical-section is already running"
fi
现在当你杀死进程时,锁文件一同被删除。注意在trap命令中明确地退出了脚本,否则脚本会继续执行trap后面的命令。
竟态条件 (wikipedia)
在上面锁文件的例子中,有一个竟态条件是不得不指出的,它存在于判断锁文件和创建锁文件之间。一个可行的解决方法是使用IO重定向和bash的noclobber(wikipedia)模式,重定向到不存在的文件。我们可以这么做:
if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null;
then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
critical-section
rm -f "$lockfile"
trap - INT TERM EXIT
else
echo "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 $user
if [ -e /home/$user ]; then
rm -rf /home/$user
fi
exit
}
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"); do
perl -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"); do
perl -pi -e 's/www.example.net/www.example.com/' $file
done
mv /var/www /var/www-old
mv /var/www-tmp /var/www
这意味着如果更新过程出问题,线上系统不会受影响。线上系统受影响的时间降低为两次mv操作的时间,这个时间非常短,因为文件系统仅更新inode而不用真正的复制所有的数据。
这种技术的缺点是你需要两倍的磁盘空间,而且那些长时间打开文件的进程需要比较长的时间才能升级到新文件版本,建议更新完成后重新启动这些进程。对于apache服务器来说这不是问题,因为它每次都重新打开文件。你可以使用lsof命令查看当前正打开的文件。优势是你有了一个先前的备份,当你需要还原时,它就派上用场了。
进阶阅读
Classic Shell Scripting
Learning the Bash Shell
Bash website
Bash Manual
Advanced Bash-Scripting Guide
分享到:
相关推荐
需要注意的是,虽然`shc`可以提供一定程度的加密保护,但并不能完全防止反编译或逆向工程。转换后的C代码仍然是可读的,只是对普通用户而言,阅读和理解C代码比直接阅读Shell脚本更为困难。此外,转换过程并不改变...
在大多数情况下,Shell会自动处理数字运算,但需要注意的是,Shell并不支持所有高级数学运算,如开方、指数等。 3. 数组(Array):在Bash 4.0及以上版本中,数组成为可用的数据结构,允许你存储多个值在一个变量中...
在Android系统中,有时我们需要执行一些底层操作,如文件管理、系统调试或自动化测试,这时候就需要用到shell命令。本文将深入探讨如何在Android平台上执行shell命令,以及它在实际开发中的应用。 首先,Android...
- **软件限制**:在 ANSYS 13.0 中使用 SHELL281 元素时,应注意软件版本中可能存在的限制。 #### 结论 SHELL281 是 ANSYS 13.0 中一种非常强大的壳体元素,适用于多种线性和非线性问题。它能够处理大旋转、大应变...
### SHELL十三问知识点解析 #### 1. 为何叫做Shell? **定义与作用:** 在深入了解Shell是什么之前,我们首先要明确的是,Shell是连接用户与计算机操作系统(具体到此场景下指Linux)之间的桥梁。从宏观角度来看,...
脚本只需配置/etl/sql/sql_mb.txt模板中的SQL语句,以及配置/etl/sql/filename.txt文件中对应的文件名称即可将数据卸载到对应文件名称的文本文档中,配置自由。...注意:配置/etl/shell/config中的环境信息
2. 常用shell命令:介绍在编写shell脚本时会用到的一些基本命令和工具,包括文件操作命令(如echo、cat、cp、mv、rm)、文本处理工具(如grep、sed、awk)、权限管理(如chmod、chown)、进程管理(如ps、kill)等。...
下面是一些使用 FFmpeg 和 Shell 脚本进行批处理的关键知识点: 1. **FFmpeg 基本命令**:理解 FFmpeg 的基本操作,如 `-i` 参数用于指定输入文件,`-c` 用于选择编解码器,`-o` 用于指定输出文件。例如,`ffmpeg -...
总之,在使用SHELL63单元进行建模时,需要注意细节处理,特别是在处理复杂结构连接和与其他类型单元的组合应用时。正确使用命令和参数,结合适当的测试方法,可以有效提高模型的准确性和可靠性。
需要注意的是,当变量名与其他文字混合时,为了避免歧义,可以使用花括号明确变量边界: ```sh num=2 echo "this is the ${num}nd" ``` 这将正确地输出:“this is the 2nd”。 ##### 2.1.4 环境变量 环境变量是...
由于提供的内容中仅包含标题、描述、标签和下载链接的重复信息,并没有具体到Linux和UNIX Shell程序设计的技术细节,因此无法直接从这部分内容中提取出符合要求的知识点。为了满足您的要求,我将基于标题和标签中...
需要注意的是,由于内容是通过OCR扫描识别的,所以存在一些文字错误和遗漏,如“E2DNITDIONWICKEDCOOL”和“PRAISEFORTHEFIRSTEDITIONOFWICKEDCOOLSHELLSCRIPTS”等部分句子不完整或不准确。在实际的阅读和应用中,...
值得注意的是,虽然补全shell脚本极大地方便了光猫的管理,但不当的使用或未经充分测试的脚本也可能对光猫造成损害。因此,使用补全shell脚本时,建议由有经验的网络管理人员操作,或在执行前仔细阅读脚本内容,并在...
6. **Shell转场动画**:GNOME Shell引入了流畅的转场动画,使得在不同窗口和工作区间切换时更加平滑,提升了视觉体验。 7. **Mutter窗口管理器**:GNOME Shell依赖Mutter进行窗口管理,Mutter负责处理窗口的布局、...
#### 知识点六:注意事项 1. **文件权限**:确保Shell脚本具有适当的执行权限,并且脚本中涉及的文件是可写的。 2. **错误处理**:在实际应用中,应增加错误处理机制,比如检查文件是否存在等。 3. **性能考虑**:...
Ruby-wisepdf是一个Ruby gem,它...通过理解这些知识点,你可以有效地利用wisepdf在Ruby项目中生成高质量的PDF文件,提供更好的用户体验。在实际应用中,根据项目需求进行适当的调整和优化,以确保最佳的性能和安全性。
### Shell编程基础知识点详解 #### 一、Shell基础概述 - **定义与作用**:Shell是一种命令行解释器,提供用户与Linux内核交互的界面。它可以用来启动、挂起、停止程序,甚至用于编写程序。 - **特点**:Shell语言...
### zsh手册知识点详解 #### 一、zsh简介与文档概述 **zsh**(Z Shell)是一款功能强大的Unix命令解释器(shell),既可以用作交互式登录shell,也可以用作shell脚本命令处理器。在众多标准shell中,zsh最接近于...
需要注意的是,在升级过程中必须遵循正确的步骤,确保设备不会因不正确的操作而受损。同时,备份当前配置是良好实践,以防升级过程中出现问题,可以快速恢复到之前的配置。 总的来说,华为MA5671光猫的shell补全...
需要注意的是,Unix的命令往往有更多的选项和更强大的功能,例如`grep`命令可以用于在文件中查找字符串,`find`可以用于查找满足特定条件的文件。 转换过程并不复杂,尤其是当DOS批处理文件仅包含基础操作时。例如...