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 hereHere 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.