DietMountain的个人博客分享 http://blog.sciencenet.cn/u/DietMountain

博文

Bash脚本笔记:set -e 和一个陷阱

已有 5129 次阅读 2019-12-24 14:10 |个人分类:计算机相关|系统分类:科研笔记

在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等工具时要时刻谨慎。


参考:Bash 脚本 set 命令教程

How can I get this script to error exit based on result of for loop?

Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?



https://blog.sciencenet.cn/blog-3414436-1211293.html


下一篇:Linux命令行选项的三种风格
收藏 IP: 120.234.31.*| 热度|

0

该博文允许注册用户评论 请点击登录 评论 (1 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-4-19 14:36

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部