29

A script is misbehaving. I need to know who calls that script, and who calls the calling script, and so on, only by modifying the misbehaving script.

This is similar to a stack-trace, but I am not interested in a call stack of function calls within a single bash script. Instead, I need the chain of executed programs/scripts that is initiated by my script.

codeforester
  • 39,467
  • 16
  • 112
  • 140
flybywire
  • 261,858
  • 191
  • 397
  • 503

8 Answers8

20

A simple script I wrote some days ago...

# 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]

... and a simple test.

test

I hope you like it.

Community
  • 1
  • 1
Gian Paolo Ghilardi
  • 1,065
  • 13
  • 20
15

You can use Bash Debugger http://bashdb.sourceforge.net/

Or, as mentioned in the previous comments, the caller bash built-in. See: http://wiki.bash-hackers.org/commands/builtin/caller

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

Or as a bash function:

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
}

Another way to do it is to change PS4 and enable xtrace:

PS4='+$(date "+%F %T") ${FUNCNAME[0]}() $BASH_SOURCE:${BASH_LINENO[0]}+ '
set -o xtrace    # Comment this line to disable tracing.
Mircea Vutcovici
  • 1,894
  • 19
  • 27
9
~$ 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.
Juliano
  • 39,173
  • 13
  • 67
  • 73
  • 1
    While this and `-x` are useful, it sounds like it wouldn't help OP, as this only works on function calls within the same script. – ephemient Mar 26 '09 at 14:52
  • See Mircea Vutcovici answer above. This one is right what I needed: i=0; while caller $i ;do ((i++)) ;done – Konrad Kleine Oct 08 '14 at 07:25
7

Since you say you can edit the script itself, simply put a:

ps -ef >/tmp/bash_stack_trace.$$

in it, where the problem is occurring.

This will create a number of files in your tmp directory that show the entire process list at the time it happened.

You can then work out which process called which other process by examining this output. This can either be done manually, or automated with something like awk, since the output is regular - you just use those PID and PPID columns to work out the relationships between all the processes you're interested in.

You'll need to keep an eye on the files, since you'll get one per process so they may have to be managed. Since this is something that should only be done during debugging, most of the time that line will be commented out (preceded by #), so the files won't be created.

To clean them up, you can simply do:

rm /tmp/bash_stack_trace.*
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • That is not a stack trace though. at best it would be a exec trace. But for that pstree -pal or ps -ef --forest would be better suited.
    It doesn’t show the function call *stack*, nor does it show the current code file and line. Which is usually the whole point of a stack trace.
    – Evi1M4chine May 04 '13 at 00:02
  • Yes but OP stated they only wanted to know which scripts call which scripts, so the detail on what lines within the scripts is not necessary. Once you know the exec-stack, you can start adding debug statements like `set -x` to individual scripts for a more fine-grained trace. – paxdiablo May 04 '13 at 03:16
  • I don't consider this an answer to the question, at least not in the common sense of stack trace. – akostadinov Sep 18 '13 at 13:44
  • akostadinov, it gives every piece of information required by the question which, by the way, called for "something _like_ a stacktrace" and the OP accepted it so they obviously disagree :-) – paxdiablo Sep 18 '13 at 14:33
  • @paxdiablo, fair enough :) – akostadinov May 29 '14 at 10:00
4

UPDATE: The code below should work. Now I have a newer answer with a newer code version that allows a message inserted in the stacktrace.

IIRC I just couldn't find this answer to update it as well at the time. But now decided code is better kept in git so latest version of the above should be in this gist.

original code-corrected answer below:

There was another answer about this somewhere but here is a function to use for getting stack trace in the sense used for example in the java programming language. You call the function and it puts the stack trace into the variable $STACK. It show the code points that led to get_stack being called. This is mostly useful for complicated execution where single shell sources multiple script snippets and nesting.

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
}
codeforester
  • 39,467
  • 16
  • 112
  • 140
akostadinov
  • 17,364
  • 6
  • 77
  • 85
  • Useful bit of code to start with, but it has bugs. e.g. ${#FUNCNAME[1]} is the length of the first function name not the number of entries in the FUNCNAME array, and the "i - 1" for lineno seems wrong. – Eric Sep 06 '16 at 16:52
  • @Eric, thank you, I remember doing some tune-ups in my production version that I can no longer find because I stopped using the thing long time ago. And unfortunately missed to update code here. I fixed `stack_size`. LINENO looks correct to me from a simple local test, let me know if it doesn't work for you. I also remember a difference with `stack_size` when running script from file vs calling some function from interactive shell. I think it can be made auto-guess it but no time to play with it right now. i.e. in interactive, you need `stack_size + 1`. – akostadinov Sep 06 '16 at 21:21
  • @Eric, thanks to you and I could eventually link my two answers on the subject. See update above. Feel welcome to collaborate on the gist. I think that an improvement would be to auto-sense when we need to stop at `stack_size` or `stack_size + 1`. – akostadinov Sep 20 '16 at 10:08
1

The simplest script which returns a stack trace with all callers:

i=0; while caller $i ;do ((i++)) ;done
Dominik
  • 11
  • 1
1

adding pstree -p -u `whoami` >>output in your script will probably get you the information you need.

Brian Mitchell
  • 2,280
  • 14
  • 12
-2

You could try something like

strace -f -e execve script.sh
sigjuice
  • 28,661
  • 12
  • 68
  • 93