Instrumenting bash scripts
06 February 2023
Tracing gives a detailed breakdown of where time was spent, typically an HTTP request. We can do the same for shell scripts. If you find yourself in a CI or CD context where a few bash scripts are strung together, this could be useful.
Two places to add instrumentation:
- Wrap the shell script
- Within the shell script
1) Wrap the shell script with
Our example wrapper.sh
#!/bin/bash
START_TIME=$(date +%s)
"$@" # do the command
exit_code=$?
END_TIME=$(date +%s)
# record the results
echo "command: ${*}"
echo "exit: ${exit_code}"
echo "duration: $(($END_TIME - $START_TIME))"
This script can be used like ./wrapper.sh my_command -flag something
. We grab our start time with date
for getting the duration. Execute the arguments we passed to the script with $?
, and use $?
to grab the success code. At the end, we record the values.
We use echo here to print to stdout, but this could instead be sending data somewhere or even better - capturing stdout and stderr to forward in the error message.
2) Within the script
Using trap in our example script.sh
#!/bin/bash
START_TIME=$(date +%s)
function record() {
END_TIME=$(date +%s)
exit_code=$1
line_number=$2
echo "exit code: $1"
echo "line number: $2"
echo "duration: $(( $END_TIME - $START_TIME))"
line=$(awk "NR == n" n=$line_number $0)
echo "error at: $0:$line_number $line"
}
trap 'record $? $LINENO' EXIT
# regular script stuff goes here
Here we are using trap
on EXIT
to always run our function. This function takes two arguments, $?
has the exit value of the script when the function triggers, and $LINENO
has the line number that triggered the trap function. All business logic goes in this script, it is a bit more verbose than the wrapper method but can be put sourced from a utility script for reuse and has the advantage of being able to use $LINENO
to print out the bash line that caused the error.