【问题标题】:Trace of executed programs called by a Bash script由 Bash 脚本调用的已执行程序的跟踪
【发布时间】:2010-10-15 16:28:46
【问题描述】:

脚本行为异常。我需要知道谁调用了那个脚本,谁调用了调用脚本等等,只需要修改行为不端的脚本。

这类似于堆栈跟踪,但我对单个 bash 脚本中的函数调用调用堆栈感兴趣。 相反,我需要由我的脚本启动的已执行程序/脚本链。

【问题讨论】:

    标签: linux bash scripting


    【解决方案1】:

    我几天前写的一个简单的脚本......

    # FILE       : sctrace.sh
    # LICENSE    : GPL v2.0 (only)
    # PURPOSE    : print the recursive callers' list for a script
    #              (sort of a process backtrace)
    # USAGE      : [in a script] source sctrace.sh
    #
    # TESTED ON  :
    # - Linux, x86 32-bit, Bash 3.2.39(1)-release
    
    # REFERENCES:
    # [1]: http://tldp.org/LDP/abs/html/internalvariables.html#PROCCID
    # [2]: http://linux.die.net/man/5/proc
    # [3]: http://linux.about.com/library/cmd/blcmdl1_tac.htm
    
    #! /bin/bash
    
    TRACE=""
    CP=$$ # PID of the script itself [1]
    
    while true # safe because "all starts with init..."
    do
            CMDLINE=$(cat /proc/$CP/cmdline)
            PP=$(grep PPid /proc/$CP/status | awk '{ print $2; }') # [2]
            TRACE="$TRACE [$CP]:$CMDLINE\n"
            if [ "$CP" == "1" ]; then # we reach 'init' [PID 1] => backtrace end
                    break
            fi
            CP=$PP
    done
    echo "Backtrace of '$0'"
    echo -en "$TRACE" | tac | grep -n ":" # using tac to "print in reverse" [3]
    

    ...和一个简单的测试。

    希望你喜欢。

    【讨论】:

    • 这在 Mac OS X 上不起作用。:( 并不是你声称它确实如此,但只是对于那些正在寻找 Mac 解决方案的人来说,这不是。
    • 是的,它使用 Linux 工具(毕竟问题是关于 Linux 的),抱歉...
    • 这最终成为我在 Mac OS X(和 Linux)上的工作:while caller $i do i=$((i+1)) done
    • 好!很高兴听您这么说。 :)
    【解决方案2】:

    你可以使用 Bash 调试器http://bashdb.sourceforge.net/

    或者,如前面的 cmets 中所述,caller bash 内置。见:http://wiki.bash-hackers.org/commands/builtin/caller

    i=0; while caller $i ;do ((i++)) ;done
    

    或作为 bash 函数:

    dump_stack(){
        local i=0
        local line_no
        local function_name
        local file_name
        while caller $i ;do ((i++)) ;done | while read line_no function_name file_name;do echo -e "\t$file_name:$line_no\t$function_name" ;done >&2
    }
    

    另一种方法是更改​​ PS4 并启用 xtrace:

    PS4='+$(date "+%F %T") ${FUNCNAME[0]}() $BASH_SOURCE:${BASH_LINENO[0]}+ '
    set -o xtrace    # Comment this line to disable tracing.
    

    【讨论】:

    • 我特别喜欢这个:i=0;而调用者 $i ;do ((i++)) ;done
    【解决方案3】:
    ~$ help caller
    caller: caller [EXPR]
        Returns the context of the current subroutine call.
    
        Without EXPR, returns "$line $filename".  With EXPR,
        returns "$line $subroutine $filename"; this extra information
        can be used to provide a stack trace.
    
        The value of EXPR indicates how many call frames to go back before the
        current one; the top frame is frame 0.
    

    【讨论】:

    • 虽然 this 和 -x 很有用,但听起来它对 OP 没有帮助,因为这仅适用于同一脚本中的函数调用。
    • 见上面 Mircea Vutcovici 的回答。这正是我所需要的:i=0;而调用者 $i ;do ((i++)) ;done
    【解决方案4】:

    既然你说你可以编辑脚本本身,那就简单地写一个:

    ps -ef >/tmp/bash_stack_trace.$$
    

    在其中,问题发生的地方。

    这将在您的tmp 目录中创建许多文件,显示发生时的整个进程列表。

    然后,您可以通过检查此输出来确定哪个进程调用了哪个其他进程。这可以手动完成,也可以使用 awk 之类的东西自动完成,因为输出是常规的 -​​ 您只需使用那些 PIDPPID 列来计算您感兴趣的所有进程之间的关系。

    您需要密切关注文件,因为每个进程都会获得一个文件,因此可能需要对其进行管理。由于这应该只在调试期间完成,大多数情况下该行将被注释掉(前面是#),因此不会创建文件。

    要清理它们,您只需执行以下操作:

    rm /tmp/bash_stack_trace.*
    

    【讨论】:

    • 这不是堆栈跟踪。充其量这将是一个执行跟踪。但是对于那个 pstree -palps -ef --forest 会更合适。
      它不显示函数调用 stack i>,也不显示当前的代码文件和行。这通常是堆栈跟踪的全部内容。
    • 是的,但 OP 表示他们只想知道哪些脚本调用了哪些脚本,因此不需要脚本中哪些行的详细信息。一旦了解了 exec-stack,就可以开始将 set -x 之类的调试语句添加到各个脚本中,以获得更细粒度的跟踪。
    • 我不认为这是问题的答案,至少不是堆栈跟踪的常识。
    • akostadinov,它提供了问题所需的每一条信息,顺便说一句,要求“某事 like 堆栈跟踪”并且 OP 接受了它,所以他们显然不同意:-)
    • @paxdiablo,很公平:)
    【解决方案5】:

    更新: 下面的代码应该可以工作。现在我有一个newer answer,它的代码版本较新,允许在堆栈跟踪中插入一条消息。

    IIRC 我当时只是找不到这个答案来更新它。但现在决定的代码最好保存在 git 中,所以上面的最新版本应该在这个 gist 中。

    以下原始代码更正答案:

    在某个地方对此有另一个答案,但这是一个用于获取堆栈跟踪的函数,例如在 java 编程语言中使用的意义。您调用该函数,它将堆栈跟踪放入变量 $STACK。它显示了导致get_stack 被调用的代码点。这对于单个 shell 源多个脚本 sn-ps 和嵌套的复杂执行非常有用。

    function get_stack () {
       STACK=""
       # to avoid noise we start with 1 to skip get_stack caller
       local i
       local stack_size=${#FUNCNAME[@]}
       for (( i=1; i<$stack_size ; i++ )); do
          local func="${FUNCNAME[$i]}"
          [ x$func = x ] && func=MAIN
          local linen="${BASH_LINENO[(( i - 1 ))]}"
          local src="${BASH_SOURCE[$i]}"
          [ x"$src" = x ] && src=non_file_source
    
          STACK+=$'\n'"   "$func" "$src" "$linen
       done
    }
    

    【讨论】:

    • 开始时有用的代码,但它有错误。例如${#FUNCNAME[1]} 是第一个函数名的长度而不是 FUNCNAME 数组中的条目数,并且 lineno 的“i - 1”似乎是错误的。
    • @Eric,谢谢你,我记得在我的生产版本中进行了一些调整,但由于很久以前我就停止使用了,所以现在找不到了。不幸的是错过了在这里更新代码。我修复了stack_size。从一个简单的本地测试来看,LINENO 对我来说是正确的,如果它不适合你,请告诉我。我还记得 stack_size 从文件运行脚本与从交互式 shell 调用某些函数时的区别。我认为它可以自动猜测它,但现在没有时间玩它。即在交互中,您需要stack_size + 1
    • @Eric,多亏了你,我最终可以链接我关于这个主题的两个答案。请参阅上面的更新。欢迎就要点进行合作。我认为当我们需要停在stack_sizestack_size + 1 时,自动感应是一种改进。
    【解决方案6】:

    在脚本中添加 pstree -p -u `whoami` >>output 可能会为您提供所需的信息。

    【讨论】:

      【解决方案7】:

      返回所有调用者的堆栈跟踪的最简单脚本:

      i=0; while caller $i ;do ((i++)) ;done
      

      【讨论】:

        【解决方案8】:

        你可以试试

        strace -f -e execve script.sh
        

        【讨论】:

          猜你喜欢
          • 2021-11-22
          • 1970-01-01
          • 1970-01-01
          • 2020-12-14
          • 2021-03-14
          • 2011-07-07
          • 1970-01-01
          • 2016-12-10
          • 1970-01-01
          相关资源
          最近更新 更多