your daily cup of tea™

powered by

Bash is glue, bash is tape, bash is my friend

For the last five years I have been using bash more and more. Whilst most ninja-black-ops-rockstars-part-time-pro-surfers moved into writing cool shit in rust or go, I found out most of my day to day problems with technology could be solved with just bash, probably to the detriment of my hireability. Well, I guess it really sucks that my problems are the equivalent of fixing stuff at home and the reason why you do not need a CNC machine for most household mainteinance operations.

My toolbox has a random selection of things I have accumulated over the years; my favourite tool at the moment is a staple gun. “Bash is my staple-gun, bash is my teflon tape and my zip tie” did not have the same ring to it though. Arguably using glue sucks and taping things is not the most elegant way of solving problems, and yet we have all seen cars that are kept together for years with just duc[kt] tape.

What I want to express forward is that it is possible to write useful scripts in bash. If I was a better writer I would try to convince you that these scripts can also be elegant. But I will pass on that for now, since words would fail me. I am no writer but a grunt.

Going back to the original point: why is bash glue, tape, or my friend?

Ken Thompson and Dennis Ritchie, key proponents of the Unix philosophy

I live in this little world of command line tools that I use every day. These tools receive arguments, flags, stdin, do some useful thing and exit with a code (0 meaning all was good, anything else is an error). Most times these also stdout and stderr things too. This is my system, there are other systems, but I like this one. Call it the UNIX way. The beauty of it all was eloquently described in 1999 by Neal Stephenson’s essay In the Beginning… Was the Command Line that can be read online.

Most of the time as a grunt I find that the real work has already been done by people more clever than me. Ah, but the interface. That’s my issue. When a task is simple enough that you do not need anything else, some piping is all you need. But other times you need to sprinkle a bit more magic to it. And here is when bash comes into play.

I use bash to glue together all these clever pieces of code that output exit codes, and as long as these parts do their thing I can move on to doing other stuff. What I find is that bash is unparalleled for that task. And that’s because bash is the shell itself and bash is ubiquitous. With bash I can construct better interfaces to build my own tools.

Sadly I think this puts me way in the middle of the bell curve, but who cares.

The other day I wrote a little snippet to quickly open remote-viewer (SPICE) on VMs that roam on a proxmox instance. The proxmox interface allows you to click through submenus and download a config file to be opened with remote-viewer. That works, but is not the interface I want. Here is my snippet

remote-viewer <(
    curl "https://$HOST:8006/api2/spiceconfig/nodes/$NODE/qemu/$VMID/spiceproxy" \
         -H "Authorization: PVEAPIToken=$TOKEN" \
         -d "proxy=$HOST" \
         -f -s -S -k
)

And I could have left it there. But instead I wrote px. Because I wanted a better way of declaring these variables, and possibly to continue exploring the proxmox api at my will.

This is not the only tool I have written using bash. While working at Kong, together with kidd, we wrote a nice tool called gojira. This time it was not SPICE configs and proxmoxes, but declaring a better interface for docker [compose]. I recommend reading this very useful Shell Field Guide that kidd wrote, based on the experiences we had writing gojira.

  * .    .   *   ___   .    +    .
 .     .   +    /  /  \   .   .
  + .          / /| - - |         *
       *   .   * | - - - |   *   .
   +     .      |---------|   .  +
               _|(O\_ _/O)
             _|/  (__''__)
           _|\/    WVVVVW    ゴジラ!
          \ _\     \MMMM/_
        _|\_\     _ '---; \_
   /\   \ _\/      \_   /   \
  / (    _\/     \   \  |'VVV
 (  '-,._\_.(      'VVV /
  \         /   _) /   _)
   '....--''\__vvv)\__vvv)      ldb

At another time, tired of blindly punching ANSI escape codes by hand I made vtmft and wrote a post about it.

The gist of it was, not only a better interface for a tool but a better interface for ANSI escape codes itself!

Related to this, I golfed a bit over a minimal progress bar solution and made a self documenting bash tool called barsh. And again, wrote a post about it.

Once I was angry at the complex interface of most tools that manage git hook scripts, and wrote hooka. This time I broke my ethos and wrote a full fledged tool instead of relying on the intelligence of others. The fact that I am not using it much tells me it was an error, but I had fun writing it. Look at this crazy linting function I put together.

Not content with my little toys, I again wrote a snippet to center ascii art here. Although this would be one of the examples where you do not need bash but just clever piping.

Finally

Trying to go for my grunt magnus opus, I have written this example scaffolding script for reaching out every other time I want to start a tool.

#!/usr/bin/env bash

# Some logging functions I like to having around

function inf  { >&2 printf "\033[34m[+]\033[0m %b\n" "$@" ; }
function warn { >&2 printf "\033[33m[!]\033[0m %b\n" "$@" ; }
function err  { >&2 printf "\033[31m[!]\033[0m %b\n" "$@" ; }
function err! { err "$@" && exit 1; }

# Tool name, useful for not having to mess with naming early on and support
# consistency with aliases

EXAMPLE=$(basename $0)

# Anything that can be configured on your script should be also configurable
# through environment variables. Sometimes I use a prefix for outside envs
# and proxy them to whatever name inside the script

SOMETHING=${EXAMPLE_SOMETHING:-some-default}
SMTH_ELSE=${EXAMPLE_SMTH_ELSE:-another-default}

# Usage of the script. Single text and no clever tricks here. I have found that
# having some ascii art representing my script makes me happy.

function usage {
  cat << EOF

                              .     '     ,
                                _________
                             _ /_|_____|_\ _
                               '. \   / .'
                                 '.\ /.'
                                   '.'

                $EXAMPLE: an example single-line description

Usage: $EXAMPLE action [options...]

Options:
  -s, --something   set something to anything
  -e, --smth-else   set something else
  -V, --verbose     echo every command that gets executed
  -h, --help        display this help

Commands:
  help                      Show usage

  some-action               Do some action

  another-action <thing>    Another action, takes a thing argument

  yaa <foo> <bar> [...]     Yet another action, takes multiple args

                            Example:
                              $ $EXAMPLE yaa hello world what is up

  ls <foo>                  Foo action, takes an argument. Extra np args get
                            passed to whatever it's doing

                            Example:
                              $ $EXAMPLE ls foobar
                              $ $EXAMPLE ls foobar -- -al

EOF
}


# Parse arguments. I do not use getopts because I can't remember any tar flags
# Important to note that unparsed arguments get stored into ARGS and to stop
# parsing after --  , storing it into _NP_ARGS. Also accept passing arguments
# as stdin with -

function parse_args {
  _ARGS=()
  _NP_ARGS=()

  ! [[ $1 =~ ^- ]] && ACTION=$1 && shift
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -V|--verbose)
        set -x
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      -s|--something)
        SOMETHING=$2
        shift
        ;;
      -e|--smth-else)
        SMTH_ELSE=$2
        shift
        ;;
      -)
        _ARGS+=("$(cat "$2")")
        shift
        ;;
      --)
        shift
        _NP_ARGS+=("$@")
        break
        ;;
      *)
        _ARGS+=("$1")
        ;;
    esac
    shift
  done
}


# Maybe some function that does things
function something-more-complex {
  local foo=$1
  local bar=$2

  set -- "${@:3:$#}"
  inf "Foo: $foo" "Bar: $bar" "Stuff: $*"
}


function main {
    parse_args "$@"

    # re-set action arguments after parsing. Now we can access action arguments
    # in their $1, $2, ... order
    set -- "${_ARGS[@]}"

    warn "SOMETHING=$SOMETHING" "SMTH_ELSE=$SMTH_ELSE"

    case $ACTION in
        some-action)
          echo "Hello World"
          ;;
        another-action)
          [[ -z $1 ]] && err! "Please provide a thing"
          echo "Here is the thing: $1"
          ;;
        yaa)
          something-more-complex "$@"
          ;;
        ls)
          # Some example that uses _NP_ARGS
          inf "The first argument was: $1"
          inf "And this is the result of 'ls ${_NP_ARGS[@]}'"
          ls ${_NP_ARGS[@]}
          ;;
        help)
          usage
          ;;
        *)
          # Display help on unrecognized action but err exit code
          usage
          exit 1
          ;;
    esac
}


main "$@"

Thanks for reading. And thank you bash for being my glue, my tape and my friend.

Note to friends: being glue or tape is not the only thing that’s holding our friendship together. Although, now that I think about it, maybe it is.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.