【发布时间】:2010-10-15 16:28:46
【问题描述】:
脚本行为异常。我需要知道谁调用了那个脚本,谁调用了调用脚本等等,只需要修改行为不端的脚本。
这类似于堆栈跟踪,但我不对单个 bash 脚本中的函数调用调用堆栈感兴趣。 相反,我需要由我的脚本启动的已执行程序/脚本链。
【问题讨论】:
脚本行为异常。我需要知道谁调用了那个脚本,谁调用了调用脚本等等,只需要修改行为不端的脚本。
这类似于堆栈跟踪,但我不对单个 bash 脚本中的函数调用调用堆栈感兴趣。 相反,我需要由我的脚本启动的已执行程序/脚本链。
【问题讨论】:
我几天前写的一个简单的脚本......
# 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]
...和一个简单的测试。
希望你喜欢。
【讨论】:
你可以使用 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.
【讨论】:
~$ 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.
【讨论】:
-x 很有用,但听起来它对 OP 没有帮助,因为这仅适用于同一脚本中的函数调用。
既然你说你可以编辑脚本本身,那就简单地写一个:
ps -ef >/tmp/bash_stack_trace.$$
在其中,问题发生的地方。
这将在您的tmp 目录中创建许多文件,显示发生时的整个进程列表。
然后,您可以通过检查此输出来确定哪个进程调用了哪个其他进程。这可以手动完成,也可以使用 awk 之类的东西自动完成,因为输出是常规的 - 您只需使用那些 PID 和 PPID 列来计算您感兴趣的所有进程之间的关系。
您需要密切关注文件,因为每个进程都会获得一个文件,因此可能需要对其进行管理。由于这应该只在调试期间完成,大多数情况下该行将被注释掉(前面是#),因此不会创建文件。
要清理它们,您只需执行以下操作:
rm /tmp/bash_stack_trace.*
【讨论】:
pstree -pal 或 ps -ef --forest 会更合适。set -x 之类的调试语句添加到各个脚本中,以获得更细粒度的跟踪。
更新: 下面的代码应该可以工作。现在我有一个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
}
【讨论】:
stack_size。从一个简单的本地测试来看,LINENO 对我来说是正确的,如果它不适合你,请告诉我。我还记得 stack_size 从文件运行脚本与从交互式 shell 调用某些函数时的区别。我认为它可以自动猜测它,但现在没有时间玩它。即在交互中,您需要stack_size + 1。
stack_size 或stack_size + 1 时,自动感应是一种改进。
在脚本中添加 pstree -p -u `whoami` >>output 可能会为您提供所需的信息。
【讨论】:
返回所有调用者的堆栈跟踪的最简单脚本:
i=0; while caller $i ;do ((i++)) ;done
【讨论】:
你可以试试
strace -f -e execve script.sh
【讨论】: