Det händer ibland när man skriver script att man hamnar i ett läge där en felutskrift skrivs ut, men man har ingen aning om hur man hamnade där man är. Det vill säga det blir svårt att spåra vad som egentligen gick fel...
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 {
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
}
Man får då en utskrift som ser ut som:
- Kod: Markera allt
Felutskrift <-- Den man själv angivit, eller den som bperror hittar (eller inget).
Traceback follows:
x1
x2
x3 line: 38
-x3a line: 5
-x3b line: 9
-x3c line: 13
Här ser man då (i det här exemplet) att felet uppstod när:
- Scriptet "x1" kördes
- Scriptet "x1" körde i sin tur scriptet "x2"
- Scriptet "x2" körde i sin tur scriptet "x3"
- I "x3" har funktionen "x3a" anropats, på rad 38
- Funktionen "x3a" anropar i sin tur funktionen "x3b", på rad 5
- Funktionen "x3b" anropar i sin tur funktionen "x3c", på rad 9
- och... felet som uppstår, uppstår i funktionen "x3c", på rad nummer 13 (eller rättare sagt) - det är där "traceback" anropades...
- Kod: Markera allt
Felutskrift <-- Den man själv angivit, eller den som bperror hittar (eller inget).
Traceback follows:
x1
x2
x3 line: 38
Använd bash och upptäck hela tiden nya möjligheter...
Med det här så blir allting så mycket lättare och solen skiner...