Live data from Hacker News

Bash Debugging

wizardzines.com

11–20 of 78 posts

Re: Bash Debugging

#11

Is there a reason bash is still the de facto shell scripting language other than sheer momentum of legacy? I'm able to get what I need done in it, but it's clunky and the syntax is horrid. I guess it forces you to move to a proper language once scripts grow to a certain size/complexity, so perhaps it's by design?

Are you sure it's bash? Most scripts on FreeBSD's are written for sh, which I feel is much more widely supported due to being part of the POSIX standard. Bash is just popular I think.

Re: Bash Debugging

#12
post #2

Good stuff! I use set-x frequently and have used a similar thing to die (but Julia’s version is nicer). I’ll consider using the debugger thing but stepping through a bash script line by line sounds a bit tedious. Perhaps less so than having to reread a log and rerun the script a bunch.

I often add a fail-unless function: fail-unless() { local result "$@" result=$? if ((result != 0)); then echo >2&1 "Failed ${result} with command '$*'." exit ${result} fi } That way, I know exactly what failed in the script.

How does this work exactly? What calls that function and when?

Re: Bash Debugging

#13
Good info. You can improve your debugging by using exit codes like this:

    # die: print error message to stderr, then exit with error code.
    # example: die 69 "Service unavailable."
    die() {
            n="$1" ; shift ; >&2 printf %s\\n "$*" ; exit "$n"
    }
Many more shell script exit codes and helper functions:

https://github.com/SixArm/unix-shell-script-kit/blob/main/un...

Re: Bash Debugging

#15
post #8

It's actually possible to produce a stack trace of sorts, if you use a lot of bash functions. One possible implementation is: https://github.com/TritonDataCenter/sdc-headnode/blob/master...

Another stack trace implementation [1] that allows you to write:

  some-command || fail "message"
to produce a stack trace and exit the shell in case of non-zero exit status from some-command, or write

  some-command || softfail "message" || return $?
in case you want to produce a stack trace and return from the function.

[1]: https://github.com/runag/runag/blob/main/lib/fail.sh

Re: Bash Debugging

#16

Is there a reason bash is still the de facto shell scripting language other than sheer momentum of legacy? I'm able to get what I need done in it, but it's clunky and the syntax is horrid. I guess it forces you to move to a proper language once scripts grow to a certain size/complexity, so perhaps it's by design?

It really is just legacy and momentum. Recent additions build on sh/bash really well but in the end shell scripting is a means to an end that need to evolve much slower than standard programming languages.

I think bash/sh’s key feature is that they are anti-entropy, there’s no development or evolution so there’s no chance you need to mess with dependencies or new features, the stuff that worked 20 years ago will continue to be the “bread and butter”. By design, this results in a system that’s averse to change and incentivizes people to reach outside of its limits when they are met.

Re: Bash Debugging

#17
We use a couple nice home-grown functions in ZFSBootMenu to help debug things. We have a zdebug logging function that's peppered liberally throughout the code base - https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme...

Hitting ctrl-t on our main menu will, when booting with debug logging enabled, show a screen like this: https://i.imgur.com/Ge75zkP.png

We also have a flamegraph profiling mechanism that can be enabled with https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme... . That will dump data to a serial port, which when re-assembled, can be used to produce a graph like https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master...

Bash is suprisingly flexible.

Re: Bash Debugging

#18
The `die()` trick is good, but bash has an annoying quirk: if you try to `exit` while you're inside a subshell, then the subshell exits but the rest of the script continues. Example:

    #!/bin/bash
    
    die() { echo "$1" >&2; exit 1; }
    
    cat myfile | while read line; do
        if [[ "$line" =~ "information" ]]; then
            die "Found match"
        fi
    done
    echo "I don't want this line"
..."I don't want this line" will be printed.

You can often avoid subshells (and in this specific example, shellcheck is absolutely right to complain about UUOC, and fixing that will also fix the die-from-a-subshell problem).

But, sometimes you can't, or avoiding a subshell really complicates the script. For those occasions, you can grab the script's PID at the top of the script and then use that to kill it dead:

    #!/bin/bash
    
    MYPID=$$
    
    die() { echo "$1" >&2; kill -9 $MYPID; exit 1;  }
    
    cat myfile | while read line; do
        if [[ "$line" =~ "information" ]]; then
            die "Found match"
        fi
    done
    echo "I don't want this line"
...but, of course, there are tradeoffs here too; killing it this way is a little bit brutal, and I've found that (for reasons I don't understand) it's not entirely reliable either.

Re: Bash Debugging

#20

If I ever have to debug anything in bash. I stop using bash hah.

Doesn't make sense. What if you get a script someone else wrote? Printing every command and confirming as you run every command is a great idea.

And, unfortunately shell has become the norm in CI/CD environments, pipelines etc. Can be convenient at times but can also be inconvenient and confusing as these scripts don't run in interactive shells.

Post reply on HN