I bash finns en inbyggd funktion som heter "caller", men den kan bara spåra lokalt i den process man just befinner sig (vilket den gör bra). Men ofta är det ju så att man har ett script, som kör ett annat och så vidare och varje script blir ju då en egen process i Linux och då klarar "caller" inte av att följa det hela bakåt.
Efter att ha tröttnat på detta, så kunde jag inte låta bli att hacka ihop ett script som spårar det hela bakåt.
Det enda som är lite sådär med det, är att det kan inte få tag på radnummer mer än en nivå bakåt. För att klara av det så skulle man behöva någon form av direktkommunikation mellan processerna, vilket går men inte är inbyggt i Linux. Visst hade det gått lätt att implementera med signal/catch men den funktionen finns ännu inte i Linux.
Scriptet som gör traceback anropas med "traceback" och vad som står i början av scriptet. Innan det måste man gjort en "source traceback", där alltså scriptet måste befinna sig i "PATH", annars får man ange fullständig sökväg...
Här är scriptet "traceback":
Kod: Markera allt
#! /bin/bash
#
function usage {
cat << EOD
# This is a function that may signal an error and show a traceback of the
# call-stack which shows how we came there we are.
#
# In opposite to the bash builtin "caller" function, this show across multiple
# scripts. However because of that, it can not show internal function calls in
# the calling script.
#
# The full syntax of the script is:
#
# traceback [ERROR-CODE] [MESSAGE] [LINENO]
#
# ERROR-CODE
# The code derived from a command, if "0" (zero) nothing is done,
# as this i success.
#
# MESSAGE
# The error-text you want to be written. If unspecified, traceback
# tries to find it out from "bperror", which is a complementary script.
#
# LINENO
# The line-number you want it to report. If not specified, the function
# can find it self. This option is only meaningful if you want it so
# report something other.
#
# Examples:
#
# traceback
# Performs a traceback, but writes no message.
#
# traceback ERROR-CODE
# Tries to find the meaning of the error-code via bperror and performs
# a traceback. The traceback tries to find out line number in scripts.
# The script bperror is needed for this to work.
#
# traceback ERROR-CODE MESSAGE
# writes your own MESSAGE and performs a traceback. The traceback tries
# to find out line number in scripts.
#
# traceback ERROR-CODE MESSAGE $LINENO
# Performs a traceback, but uses the specified LINENO and writes the
# specified MESSAGE.
#
# Note that "LINENO" is something you can specify as you like, but "$LINENO"
# is a bash builtin.
EOD
}
#
function usage {
echo "Learn to use..."
#usage
echo
exit
}
function getProc() {
SEARCH=$1
OUT=$2
PROCESSES=$(ps --no-header -o pid -o ppid -o comm -o args | grep -v grep | grep -v ps)
IFS=$'\n'
for PRC in $PROCESSES; do
XPID=$(echo $PRC | awk '{ print $1 }')
if [[ $XPID == $SEARCH ]]; then
COMM=$(echo $PRC | awk '{ print $3 }')
ARGS=$(echo $PRC | awk '{ $1=""; $2=""; $3=""; print $0 }' | xargs)
if [[ $COMM != "bash" ]]; then
echo "$COMM" >> $OUT
YPID=$(echo $PRC | awk '{ print $2 }')
getProc $YPID $OUT >> $OUT
break
else
break
fi
fi
done
}
function putOut {
tac $OUT | sponge $OUT
while read -r line; do
SPACE=" $SPACE"
echo "$SPACE$line" ;
done < $OUT
}
function showStack() {
FUNC=$1
SPACE=$2
#OUT=$(mktemp)
let COUNT=0
while caller $COUNT >> /dev/null; do
((COUNT++))
done
let I=$COUNT-1
let IX=$COUNT-1
let MAXLEN=0
while true; do
DATA=$(caller $IX)
LIN=$(echo $DATA | awk '{ print $1 }')
FNC=$(echo $DATA | awk '{ print $2 }')
STRLEN=$(expr length $FNC)
if [[ $STRLEN -gt $MAXLEN ]]; then
let MAXLEN=$STRLEN
fi
((I--))
if [[ $I -eq 0 ]]; then
break
fi
done
while true; do
DATA=$(caller $IX)
LIN=$(echo $DATA | awk '{ print $1 }')
FNC=$(echo $DATA | awk '{ print $2 }')
SRC=$(echo $DATA | awk '{ print $3 }')
if [[ $FNC == "main" ]]; then
let FNCLEN=$MAXLEN+4
printf "%s%-${FNCLEN}sline:%5d\n" $SPACE $FUNC $LIN
else
let FNCLEN=$MAXLEN+3
printf "%s-%-${FNCLEN}sline:%5d\n" $SPACE $FNC $LIN
fi
((IX--))
if [[ $IX -eq 0 ]]; then
break
fi
done
}
function traceback() {
SPACE=""
NO_ECHO=false
CODE=$1
MESSAGE=$2
LNUM=$3
echo
if [[ $CODE == "-h" || $CODE == "--help" ]]; then
usage
exit
elif [[ $CODE == 0 ]]; then
echo "<No valid error code>"
echo "Usage: traceback CODE [MESSAGE] [LINENO]"
elif [[ ! -z $MESSAGE && $MESSAGE =~ ^[0-9]+$ ]]; then
echo "<No valid error message>"
echo "Usage: traceback CODE [MESSAGE] [LINENO]"
elif [[ -z $MESSAGE ]]; then
if [[ ! -z $CODE ]]; then
echo "Possible error (from bperror):"
echo -n " "
bperror $CODE
else
# -No need to write any message here, traceback may be called
# -just to perform a traceback, without writing anything.
NO_ECHO=true
fi
else
echo "$MESSAGE" >> /dev/tty
fi
if [[ $NO_ECHO == false ]]; then
echo
fi
echo "Traceback follows:"
THIS=$$
OUT=$(mktemp)
PROCESSES=$(ps --no-header -o pid -o ppid -o comm -o args | grep -v grep | grep -v ps)
IFS=$'\n'
for PROCESS in $PROCESSES; do
PID=$(echo $PROCESS | awk '{ print $1 }')
pPID=$(echo $PROCESS | awk '{ print $2 }')
if [[ $PID == $THIS ]]; then
getProc $pPID $OUT $LNUM >> $OUT
fi
done
putOut $OUT
rm $OUT
SPACE=" $SPACE"
FUNC=$(caller | awk '{ print $2 }' | sed 's=./==g')
if [[ -z $(caller 1) ]]; then
if [[ -z $LNUM ]]; then
LIN=$(caller | awk '{ print $1 }')
fi
FNCLEN=$(expr length $FUNC)
let FNCLEN+=3
printf "%s%-${FNCLEN}sline:%4d\n" $SPACE $FUNC $LIN
else
showStack $FUNC $SPACE
fi
echo
if [[ $CODE != 0 && -z $MESSAGE ]]; then
exit
fi
}
Kod: Markera allt
# !/bin/bash
#
##########################################################################
#
# bperror [-b] [-d] [-h] [-l] [-q] [ERRNO]
#
# A bash version of the perror function in "C" and other languages
#
# -b|--brief
# Just write the error message, without error name etc.
#
# -d|--define
# Define all errors so they can be used in a script so check
# against an error. Available errors can be shown with the
# -l|--list option, or also often shown in the man-page for
# a command. The error can then be checked as:
#
# if [[ $? == $ERRNO ]]; then
# do-something
# fi
#
# ERRNO is the name of the possible error. For exemple EBUSY
# (16) "Device or resource busy".
#
# -h|--help
# Show this.
#
# -l|--list
# Simply list all existing errors. It is possible to search for
# the value of a specific error in this list by specifying its
# name:
#
# bperror -l | grep error-name
#
# Of course it is also possible to search for a part of the error-
# name, this way. Remember that all error-names in Linux is upper-
# case.
#
# -q|--quiet
# Do not write anything if the error has no error message.
# Otherwise "Unknown error" and the number is written.
#
# ERRNO The error code, a numerical value between 1 and 999. Not used
# when "-d|--define" och "-l|--list" is used.
#
##########################################################################
#
function usage {
cat << EOD
bperror [-b] [-d] [-h] [-l] [-q] ERRNO
A bash version of the perror function in "C" and other languages
-b|--brief
Just write the error message, without error name etc.
-d|--define
Define all errors so they can be used in a script so check
against an error. Available errors can be shown with the
-l|--list option, or also often shown in the man-page for
a command. The error can then be checked as:
if [[ \$? == \$ERRNO ]]; then
do-something
fi
ERRNO is the name of the possible error. For exemple EBUSY
(16) "Device or resource busy".
-h|--help
Show this.
-l|--list
Simply list all existing errors. It is possible to search for
the value of a specific error in this list by specifying its
name:
bperror -l | grep error-name
Of course it is also possible to search for a part of the error-
name, this way. Remember that all error-names in Linux is upper-
case.
-q|--quiet
Do not write anything if the error has no error message.
Otherwise "Unknown error" and the number is written.
ERRNO The error code, a numerical value between 1 and 999.
EOD
}
#
function stacktrace {
local i=1 line file func
while read -r line func file < <(caller $i); do
echo >&2 "[$i] $file:$line $func(): $(sed -n ${line}p $file)"
((i++))
done
}
print_usage () {
echo "It is $?"
stacktrace
echo "Usage: ${0##*/} ERRNO (ERRNO=1-999)"
}
#
TEMP=`getopt -obdhlq --long brief,define,help,list,quiet -n $(basename $0) -- "$@"`
if [[ $? -ne 0 ]]; then
exit
fi
eval set -- "$TEMP"
BP=false
LIST=false
BRIEF=false
QUIET=false
DEFINE=false
CMDNAME=$(basename $0)
while true; do
case $1 in
-b|--brief)
BRIEF=true
shift
;;
-d|--define)
DEFINE=true
shift
;;
-h|--help)
usage
exit
;;
-l|--list)
LIST=true
shift
;;
-q|--quiet)
QUIET=true
shift
;;
--)
shift
break
;;
*)
echo "-Oh, got a star... [$1]"
exit
;;
esac
done
#
ERRNO=$1
if [[ $ERRNO == 0 ]]; then
exit
fi
OLDIFS=$IFS
FOUND=false
HEADERS="/usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h"
for HEADER in $HEADERS; do
if [[ $DEFINE == false && $LIST == false ]]; then
BP=true
case $ERRNO in
[1-9]|[1-9][0-9]|[1-9][0-9][0-9])
IFS=$'\n'
ERRS=$(grep "\<$ERRNO\>" $HEADER | grep "^#define")
for ERR in $ERRS; do
NUM=$(echo $ERR | awk '{ print $3 }')
if [[ $NUM == $ERRNO ]]; then
FOUND=true
if [[ $BRIEF == false ]]; then
ERRMSG=$(echo $ERR | sed 's/#define//g' | xargs)
else
ERRMSG=$(echo $ERR | sed 's/#define//g' | xargs | awk -F \* '{ print $2 }' | xargs)
fi
echo "$ERRMSG"
break
fi
done
;;
(*)
usage
exit 1
;;
esac
if [[ $FOUND == true ]]; then
break
fi
elif [[ $DEFINE == true ]]; then
IFS=$'\n'
LINES=$(grep "^#define" $HEADER)
for LINE in $LINES; do
NAME=$(echo $LINE | awk '{ print $2 }')
ERR=$(echo $LINE | awk '{ print $3 }')
if [[ $ERR == [1-9] || $ERR == [1-9][0-9] || $ERR == [1-9][0-9][0-9] ]]; then
echo -ne "$NAME\r"
let $NAME=$ERR
fi
done
elif [[ $LIST == true ]]; then
IFS=$'\n'
LINES=$(grep "^#define" $HEADER)
for LINE in $LINES; do
echo $LINE
done
fi
done
IFS=$OLDIFS
if [[ $BP == true ]]; then
if [[ $FOUND == false && $QUIET == false ]]; then
echo "Unknown error $ERRNO"
fi
fi
