编写过大量bash脚本的人都知道bash有很多陷阱。其实bash本身并不是一门非常严谨的语言,但是在很多情况下不得不用到它。下面总结了一些编写可靠的bash脚本的小技巧。0.set-x-e-u-opipefail写脚本的时候在开头加上下面这句话(Shebang之后),或者它的缩写版,这样可以避免很多问题,更重要的是可以把很多问题隐藏起来:set-xeuopipepefail下面解释各个参数的作用,以及一些异常处理方法:-x:在执行每条命令之前打印出变量扩展后的命令。这对于调试脚本和输出日志非常有用。正式运行的脚本也可以省略。-e:遇到命令失败立即退出(非零返回码)。bash和其他脚本语言最大的区别之一就是它应该在遇到异常时继续运行下一条命令。这样往往会遇到意想不到的问题。添加-e将导致bash在命令失败时立即退出。如果有时确实需要忽略单个命令的返回码,可以使用||真的。如:some_cmd||true#即使some_cmd失败了,也会继续运行some_cmd||RET=$?#或者可以通过这种方式收集some_cmd的返回码,方便后面的逻辑判断,但是在多条命令的情况下在管道接下来,只有最后一个命令失败才会退出。如果要在管道中的任何命令失败时退出,则需要使用后面提到的-opipefail。加上-e有时可能会不方便,动不动就退出。但是你还是应该坚持所谓的fail-fast原则,也就是出现异常时停止正常运行,而不是继续尝试运行一个可能有缺陷的进程。如果有可以显式忽略异常的命令,可以使用||显式忽略。上面说的是真的。-u:如果您尝试使用未定义的变量,则立即退出。如果在bash中使用了未定义的变量,默认情况下它将扩展为一个空字符串。有时这种行为会导致问题,例如:rm-rf$MYDIR/data如果MYDIR变量由于某种原因没有被赋值,这个命令将变成rm-rf/data。这个更搞笑。.使用-u来避免这种情况。但有时设置了-u后,有些地方还是希望将未定义的变量展开成空字符串,可以这样写:${SOME_VAR:-}#bash变量展开语法,可以参考:https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html-opipefail:管道中的一个子命令一旦失败,整个管道命令就会失败。如果pipefail与-e结合使用,则可以在管道中的子命令失败时退出脚本。1.防止重叠运行在某些场景下,我们通常不希望一个脚本的多个实例同时运行。例如,当使用crontab定期运行脚本时,有时您不希望下一轮在上一轮结束之前开始运行。这时候可以使用flock命令来解决。flock使用文件锁来保证独占操作,还有一个好处是当进程退出时,文件锁会自动释放,不需要额外处理。用法一:假设你的入口脚本是myscript.sh,你可以新建一个脚本,通过flock运行:#flock--waittimeout-elockfile-c"commandtobeexecuted"#例如:flock--wait5-e"lock_myscript"-c"bashmyscript.sh"用法二:也可以在原脚本中使用flock。您可以将文件作为文件描述符打开并使用flock锁定它(flock可以接受文件描述符参数)。exec123<>lock_myscript#Openlock_myscriptasafiledescriptor123flock--wait5123||{echo'cannotgetlock,exit';exit1;}2.意外退出时杀死所有子进程我们的脚本通常会启动很多下标和子进程,当父脚本意外退出,子进程并没有真正退出,而是继续运行。如果定期运行脚本,可能会出现一些意想不到的问题。在stackoverflow上找到的一个方法,原理是在脚本退出的时候用trap命令杀掉它的整个进程组。在脚本开头加入如下代码,实际测试有效:trap"trap-SIGTERM&&kill---$$"SIGINTSIGTERMEXIT但如果用SIGKILL(kill-9)杀死父进程,则无效。因为当SIGKILL时,进程没有机会运行任何代码。3.超时限制运行时间有时需要给命令设置一个超时时间。这时候可以使用timeout命令。使用方法很简单:当timeout600ssome_commandarg1arg2命令在超时时间内运行完毕,返回码为0,否则返回非零返回码。timeout超时默认会发送TERM信号,也可以通过-s参数让它发送其他信号。4、使用连续流水线时,可以考虑使用tee将中间结果放在磁盘上,方便检查问题。有时候我们会用到很多命令通过管道连接在一起的情况。如cmd1|命令2|命令3|...这会使问题难以排查,因为我们看不到中间数据。如果改成这样的格式:cmd1>out1.datcatout1|cmd2>out2.datcatout2|cmd3>out3.dat,性能不是很好,因为cmd1,cmd2,cmd3是串行运行的,那么可以使用tee命令:cmd1|teeout1.dat|cmd2|teeout2.dat|cmd3>out3.dat
