写出健壮的 Bash 脚本(2)

当最重要的部分(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

如果修改到一半是脚本出现问题,一部分使用,而另一部分使用。你可以使用备份和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而不用真正的复制所有的数据。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wywzfy.html