• shell语言打印异常栈


    bash shell 能不能打印出类似Java的异常栈(Stack Trace)呢?

    Java的异常栈会打印出调用方法链的具体位置,具体信息是<文件,函数,行号>三元组。类似下面:

    StudentException: Error finding students
            at StudentManager.findStudents(StudentManager.java:13)
            at StudentProgram.main(StudentProgram.java:9)
    
    • 1
    • 2
    • 3

    BASH_SOURCE变量

    BASH_SOURCE是一个数组变量,存储元素文件名。
    FUNCNAME也是一个数组变量,存储元素是函数名。

    这两个数组是按索引对应的,${BASH_SOURCE[0]}代表的是异常栈底部当前文件名,${FUNCNAME[0]}代表的异常栈底部的当前函数。

    BASH_SOURCE
    An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}

    FUNCNAME
    An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is “main”.

    例子

    打印异常栈小demo

    写个demo验证一下

    • test.sh
    source test2.sh
    
    function fun_b() {
        echo "Info in ${BASH_SOURCE[0]}:${FUNCNAME[0]}:${BASH_LINENO[0]} "
        fun_c
    }
    
    function fun_a() {
        echo "Info in ${BASH_SOURCE[0]}:${FUNCNAME[0]}:${BASH_LINENO[0]} "
        fun_b
    }
    fun_a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • test.sh
    function fun_c() {
      echo "Info in ${BASH_SOURCE[0]}:${FUNCNAME[0]}:${BASH_LINENO[0]} "
    }
    
    • 1
    • 2
    • 3

    其中${BASH_LINENO[0]}代表函数调用语句的行号

    执行test.sh查看异常栈内容:

    Info in test.sh:fun_a:14 
    Info in test.sh:fun_b:11 
    Info in test2.sh:fun_c:5 
    
    • 1
    • 2
    • 3

    成熟的异常栈函数

    把上面那个demo稍微润一下,可以实现一个很好的生产级函数:

    • test2.sh
    function errexit() {
      local err=$?
      set +o xtrace
      local code="${1:-1}"
      echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}. '${BASH_COMMAND}' exited with status $err"
      # Print out the stack trace described by $function_stack  
      if [ ${#FUNCNAME[@]} -gt 2 ]
      then
        echo "Call tree:"
        for ((i=1;i<${#FUNCNAME[@]}-1;i++))
        do
          echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
        done
      fi
      echo "Exiting with status ${code}"
      exit "${code}"
    }
    function errexit() {
      local err=$?
      set +o xtrace
      local code="${1:-1}"
      echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}. '${BASH_COMMAND}' exited with status $err"
      # Print out the stack trace described by $function_stack  
      if [ ${#FUNCNAME[@]} -gt 2 ]
      then
        echo "Call tree:"
        for ((i=1;i<${#FUNCNAME[@]}-1;i++))
        do
          echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
        done
      fi
      echo "Exiting with status ${code}"
      exit "${code}"
    }
    
    function func_c() {
        cat hello  # some danger
        res=$?
        if [ $res -ne 0 ]
        then
          errexit $res
        fi
    }
    # trap ERR to provide an error handler whenever a command exits nonzero
    #  this is a more verbose version of set -o errexit
    # If  a  sigspec  is  ERR, the command arg is executed whenever a simple command has a non-zero exit status
    trap 'errexit' ERR
    # setting errtrace allows our ERR trap handler to be propagated to functions,
    #  expansions and subshells
    set -o errtrace
    function func_c() {
        cat hello  # bad command exit with code 1
    }
    function func_a() {
        func_c
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • test.sh
    source test2.sh
    
    function func_b() {
        func_a
    }
    func_b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试一下:

    调用链是 func_b ->func_a -> func_c ,打印出的内容函数前的文件名是调用语句所在的文件名,行号是调用语句所在的行号

    [root@localhost utiltest]# bash test.sh 
    cat: hello: No such file or directory
    Error in test2.sh:21. 'cat hello' exited with status 1
    Call tree:
     1: test2.sh:33 func_c(...)
     2: test.sh:5 func_a(...)
     3: test.sh:7 func_b(...)
    Exiting with status 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    分析

    上面有几个符号还是注释一下

    • 关闭debug信息
    # set +o 代表关闭选项,set -o 代表启动选项
    # set -x (long notation: set -o xtrace) traces commands before executing them.
    set +o xtrace
    
    • 1
    • 2
    • 3

    这个是关闭debug信息,要不然会打印出类似这种(这是bash shell的运行时值计算):

    Call tree:
    + (( i=1 ))
    + (( i<5-1 ))
    + echo ' 1: test2.sh:28 func_c(...)'
     1: test2.sh:28 func_c(...)
    + (( i++ ))
    + (( i<5-1 ))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 数组长度

    ${#FUNCNAME[@]}代表FUNCNAME数组长度。

    • 条件赋值
    # code赋值为函数第一个参数(占位符),否则赋值为1.(前缀用了'-'字符,这里不是-1)
     local code="${1:-1}"
    
    • 1
    • 2
    • 异常处理句柄

    trap机制是当发生异常时,也就是命令以非0状态退出时,需要调用异常处理程序。

    trap defines and activates handlers to run when the shell receives signals or other special conditions.

    # 当有命令非零退出时,执行errexit句柄(函数)
    trap 'errexit' ERR 
    
    • 1
    • 2

    执行errexit时,要打印出哪个命令是异常退出了,使用了BASH_COMMAND变量。这个变量在trap条件下读取时,值为异常退出语句,也就是我们想要的。

    BASH_COMMAND
        The  command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap
    
    • 1
    • 2

    另外为了能够将这个触发机制传播到子shell(bash每次执行函数也是执行一个子shell):

    -E, equal to set -o errtrace   
    If  set,  any trap on ERR is inherited by shell functions, command substitutions, and commands executed 
    in a subshell environment.  The ERR trap is normally not inherited in such cases.
    
    • 1
    • 2
    • 3

    否则trap机制要在每个可能引起错误的shell函数中定义一次,才能实现相同的效果。比如:

    function func_c() {
        trap 'errexit' ERR
        cat hello  # some danger
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    计数问题(动态规划 + 数位dp )
    深度学习推荐系统--协同过滤推荐算法+实现代码
    JVM概述及类加载器
    vue+leaflet : 从0 到1 搭建开发环境
    软考系统架构师倒计时第6天
    【C++】STL 标准模板库 ① ( STL 简介 | STL 基本概念 | STL 主要内容 )
    9.DesignForManufacture\CreateArtwork...
    「学习笔记」树链剖分
    Mysql 约束,基本查询,复合查询与函数
    activiti开源工作流集成示例
  • 原文地址:https://blog.csdn.net/qq_33745102/article/details/127123094