Named Arguments in a Bash Script

Let's face it, using positional arguments like $1 and $2 for your arguments is not very descriptive and not very flexible. There is a better way to supply arguments: with this simple trick you'll get --named arguments --in your-script, which is waaaay better 🤓.

Parsing script arguments

The following script parses all arguments and turns them into variables:

while [ $# -gt 0 ]; do
    if [[ $1 == "--"* ]]; then
        v="${1/--/}"
        declare "$v"="$2"
        shift
    fi
    shift
done

echo "\$this: '$this' \$a: '$a'"

When we execute the script with: ./test.sh --this is --a test, it returns:

$this: 'is' $a: 'test'

Note: this method is not very strict, it might skip parameters that are not adhering to the double dash format. If you want to support --single-parameters, check Support for --help?

Note 2: this method does not work with set -u 🤷

Argument validation

First, let's define a usage function to show how our script works:

programname=$0
function usage {
    echo ""
    echo "Deploys an ECR image to Atlas using GitOps and ArgoCD."
    echo ""
    echo "usage: $programname --service_name string --tag string --atlas string --env string "
    echo ""
    echo "  --service_name string   name of the service"
    echo "                          (example: blaze-search-term-redirect-service)"
    echo "  --tag string            tag of the image to deploy"
    echo "                          (example: 804-a325d6a)"
    echo "  --namespace string      namespace of the cluster"
    echo "                          (example: eos)"
    echo "  --env string            env to which to deploy the tag"
    echo "                          (example: dev)"
    echo ""
}

Next, let's define a die function:

function die {
    printf "Script failed: %s\n\n" "$1"
    exit 1
}

Now, let's check if the parameters are supplied. If one of the parameters is missing, display usage and terminate the script. We use the -z conditional expression to check if the variable is empty:

if [[ -z $service_name ]]; then
    usage
    die "Missing parameter --service_name"
elif [[ -z $tag ]]; then
    usage
    die "Missing parameter --tag"
elif [[ -z $namespace ]]; then
    usage
    die "Missing parameter --namespace"
elif [[ -z $env ]]; then
    usage
    die "Missing parameter --env"
fi

Support for --help?

The easiest way of adding --help to your script, is by adding it to the loop that turns the arguments into variables:

while [ $# -gt 0 ]; do
    if [[ $1 == "--help" ]]; then
        usage
        exit 0
    elif [[ $1 == "--"* ]]; then
        v="${1/--/}"
        declare "$v"="$2"
        shift
    fi
    shift
done

Any singular parameter can be added to this loop.

How about defaults?

Here you have 2 strategies:

  1. Declare the variables at the top of your script and initialize them with defaults.
  2. Use parameter expansion: my_parameter=${my_parameter:-default value}

Parameter expansion

If you use parameter expansion, you need to know that the empty string "" will also be replaced. Observe the following script:

my_parameter="${my_parameter:-default value}"
printf "%s\n\n" "$my_parameter"

It will give the following output:

$ ./test.sh
default value

$ ./test.sh --my_parameter "my value"
my value

$ ./test.sh --my_parameter ""
default value

So, settings the defaults at the top of the script has my preference.

Write better scripts: use ShellCheck

The article Please stop writing shell scripts makes some excellent points on why writing a bash script is hard for programmers: bash behavior is most likely not like the programming language you're familiar with. The ShellCheck extension for Visual Studio Code will help you catch some of the pitfalls. With every "offence", it links to a wiki article for education:

Show the explanation of error code SC2086.
Show the explanation of SC2086.

Conclusion

Adding named arguments to a bash script is pretty easy. There are some caveats, as you might "override" arguments in your script from the outside. If you declare the arguments at the beginning of your script and the reset of the parameters after the while section, you mitigate that risk.

Changelog

2022-04-04: made the parameter validation script a bit shorter by using elif.

expand_less