The problem

I’m a lazy developer, who tries to create scripts to automatize most of my daily mechanical activities. And because of that I have many many scripts on my dotfiles.

Some of my scripts require arguments and often, due to my lack of memory, I had to research the best way to part arguments in bash. Usually I need to parse arguments in the following format:

$ ./whatever
option_a:  | option_b: DEFAULT | option_c:

$ ./whatever -a A
option_a: A | option_b: DEFAULT | option_c:

$ ./whatever -b B
option_a:  | option_b: B | option_c:

$ ./whatever -b B -a A -c C
option_a: A | option_b: B | option_c: C

$ ./whatever -b B -a A -c C other values right here
option_a: A | option_b: B | option_c: C
other values right here

The usual solution

When researching usually I found solutions like

#!/bin/bash
PARAMS=""
while (( "$#" )); do
  case "$1" in
    -f|--flag-with-argument)
      FARG=$2
      shift 2
      ;;
    --) # end argument parsing
      shift
      break
      ;;
    -*|--*=) # unsupported flags
      echo "Error: Unsupported flag $1" >&2
      exit 1
      ;;
    *) # preserve positional arguments
      PARAMS="$PARAMS $1"
      shift
      ;;
  esac
done
# set positional arguments in their proper place
eval set -- "$PARAMS"

This is a great solution, it basically loops through the arguments checking if the first one matches with any expected argument key (-key), fetch its value and remove it from the list of arguments and move to the next.

The recursive solution

The example above is a very common pattern when working with linked lists. When studying Elixir I saw this pattern many times in different examples using recursion. That’s when I thought that maybe I could use recursion to parse also my scripts list arguments too.

So, now that you know my motivation, this is the code

#!/usr/bin/env bash

option_a=""
option_b="DEFAULT"
option_c=""
message=""

function parse_args() {
  case $1 in
    -a|--option-a)
      option_a=$2
      shift 2
      parse_args $@
      ;;

    -b|--option-b)
      option_b=$2
      shift 2
      parse_args $@
      ;;

    -c|--option-c)
      option_c=$2
      shift 2
      parse_args $@
      ;;

    *)
      message="$@"
      ;;
  esac
}

function whatever() {
  parse_args $@

  echo "option_a: ${option_a} | option_b: ${option_b} | option_c: ${option_c}"
  echo "$message"
}

whatever $@

I think this is neat solution because having a function to parse the arguments makes your code more explicit and better split. Your main function doesn’t know how the arguments are parsed and parsing the arguments doesn’t have any other dependency. This way, each of the functions, on this example, has a single responsibility, following the Single Responsibility Principle.