I'm LIVE on now. Join the stream!

Debug, Perf and Handle errors in Bash

Are we talking about scripts, right? Then why it’s in them, that would require debugging, performance or even a better way to handle errors? Let’s find out.

User case

It’s interesting how lately knowing how to proper script/automate specific tasks can make my life and a few recurring tasks easier.

I was curious about how to monitor the CPU and memory usage of a process and most importantly, getting the exact line I wanted when something in my script tests breaks. Any of both situations were easy enough to get done without setting my machine in an additional state (using heavy and third-party software), for those simple requirements I had in my mind.

Debug the right error line

You can force Bash scripts to exit if there’s an error (that is if any command exits with a non-zero exit code) using:

set -e

You can manage this behavior way better:

  • exits on an error (-e, equivalent to -o errexit)
  • exits on an undefined variable (-u, equivalent to -o nounset)
  • exits on an error in piped-together commands (-o pipefail)
set -eu -o pipe

Sometimes is good to print each command before execution for testing purposes:

set -x}}

This process can be improved using trap binding the prt_err function to the ERR event and print out an error message and offending line of the script (extracted using sed) to STDERR.

function prt_err {
    read line file <<<$(caller)
    echo “Something happened in line $line of file $file:" >&2
    sed "${line}q;d" "$file" >&2
}
trap print_error ERR

Note: Note the use of a «< here string and the caller built-in to assign the line and filename of the error.

The final script would look like this:

#!/usr/bin/env bash
set -eu -o pipe

function prt_err {
    read line file <<<$(caller)
    echo “Something happened in line $line of file $file:" >&2
    sed "${line}q;d" "$file" >&2
}
trap prt_err ERR

false

You would see an output like the next time it fails:

$ ./debugError.sh
Something happened in line 1808 of file ./debugError.sh:
false

Note: Do not name your script test. test is the name of a UNIX command, and most likely built into your shell you won’t be able to run a script with the name test in a normal way.

Monitor on time

This is what starts the magic:

 # for macOS
htop -pid `{ unzip ~/data.zip > /dev/null & } && echo $!`
 # for Linux
htop -p `{ unzip ~/data.zip > /dev/null & } && echo $!`

Let’s dig into the command to know what exactly does and how to take advantage in different scenarios.

Here run the htop command in the background and pipe the output to /dev/null so that it doesn’t get printed on the command line while you are monitoring.

unzip ~/data.zip > /dev/null &

Then, echo the PID of this background process with:

echo $!

Combine and pass the pid of the process to htop:

 # for Linux
htop -p `{ <background-pipe> } && <get-pid>`
 # for macOS
htop -pid `{ <background-pipe> } && <get-pid>`

Note: I use htop instead of top just per clear personal preference. There’s no added value from htop in comparison with top. Feel free to replace and use the one you feel better comfortable.

The Bonus

Because would is better when you get more stuff you don’t even know you need it.

The pause command

At some point you would like to have a pause command, this is a nice implementation. Thanks Bash Hackers !

pause() {
  local holdUp
  read -s -r -p "Press any key to continue..." -n 1 holdUp
}

Linting you shell scripts

Have you ever thought that a linter should exist for your vast database of bash scripts? Guess what, there’s already one and looks pretty good, its name is ShellCheck . I haven’t found the time and didn’t have the situation that makes me include in one of my scripts, however, looks like it covers a lot of cases for Bash where a linter is a good idea and some other edge cases too. It has some integration with some editors; hopefully that makes easier to install and try.

This one is interesting too: bashstyle, if you are into getting your scripts under some rules and defining a kind of standard.

Thanks

This post is compiled from many sources after hours of research and trying to figure out things I don’t even think were possible in Bash. However, this time I found many other resources than previous times, so the process to figure out things were smoother. Some of the websites and docs I saw and helped me to craft this post.

Thank you: