在bash脚本中习惯了在脚本头部加上"set -e"内置命令,使得脚本里任何一行命令的退出状态码为非零时,shell立即退出。然而最近发现一个bug,排查了很久才找到原因,记录一下。
需求是在for循环中进行一系列由管道连接的操作,大致如下:#!/bin/bash
set -e
...
for id in `something`;do
cmd1 | cmd2 | grep sth
done
...
看起来很简单,实际也很简单。但是我忽略了,或者说我根本不知道的一点是:grep程序在匹配失败时,退出状态码是非零的,虽然它并不会给出任何STDERR信息。而set -e内置命令有一个特点,当它作用于由管道连接的命令组合时,仅检查该组合最后一个命令的退出状态码。在这个例子中,某一次grep失败,则整个for循环,以及整个脚本都会退出。而恰好在Marchantia的Swiss-prot功能注释结果中,地钱本身有一个基因在其中是找不到的,导致for循环进行到该基因时,grep失败,触发ERREXIT,脚本退出。
如果仅仅是这样的话,应该还是不难排查出来的。实际上并没有这么简单,上面的脚本是一个简化版本,真实的脚本是这样的:#!/bin/bash
set -e
for species in `something`;do
...
for id in `something`;do
cmd1 | cmd2 | grep sth
done | cmd3 | cmd4 > somefile
done
cmd5
这个脚本会对每一个物种(species)生成一个文件(somefile)。当内层循环中某一次迭代的grep匹配失败时,整个脚本会退出吗?答案是否定的。因为内层循环的"done"后面还有管道操作,前面说过,只有组合的最后一个命令可能触发ERREXIT,所以这里的某一次grep失败并不会导致脚本退出。那这个脚本问题到底在哪呢?实际上,内层循环某一次grep失败,会导致整个内层循环退出,而由于内层循环与后面的管道形成了一个整体,这个整体的最后一个命令(重定向到somefile文件)不会失败,所以这个整体不会触发ERREXIT。外层循环可以顺利运行,遍历整个列表,而内层循环则会在迭代至列表中导致grep失败的那个基因处触发内层循环的ERREXIT,导致内层循环异常退出(如果后面没有接管道操作,则会继续导致外层循环异常退出,进而触发整个脚本的ERREXIT),而该异常并不会触发整个脚本的ERREXIT,所以导致了Marchantia这个物种生成的文件很小。
该问题的发现纯属偶然。给我的感受是,以后使用bash、perl等工具时要时刻谨慎。转载本文请联系原作者获取授权,同时请注明本文来自余进科学网博客。
链接地址:http://blog.sciencenet.cn/blog-3414436-1211293.html
下一篇:Linux命令行选项的三种风格