# Named Arguments in a Bash Script

**Date:** 2022-03-28  
**Author:** Kees C. Bakker  
**Categories:** bash  
**Original:** https://keestalkstech.com/named-arguments-in-a-bash-script/

![Named Arguments in a Bash Script](https://keestalkstech.com/wp-content/uploads/2022/03/gabriel-heinzer-4Mw7nkQDByk-unsplash.jpg)

---

Let's face it, positional arguments such as `$1` and `$2` are neither descriptive nor 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 declares its argument variables and lists their names in a readonly whitelist. Before assigning a value, `is_allowed_argument` checks that the name is both whitelisted and declared:

```sh
declare service_name="" tag="" namespace="" env=""
declare -ar allowed_arguments=(service_name tag namespace env)

is_allowed_argument() {
    local candidate=$1
    local allowed

    for allowed in "${allowed_arguments[@]}"; do
        if [[ $candidate == "$allowed" ]]; then
            declare -p -- "$candidate" &>/dev/null
            return
        fi
    done

    return 1
}

die() {
    printf "Script failed: %s\n" "$1" >&2
    exit 1
}

while [ $# -gt 0 ]; do
    if [[ $1 == "--"* ]]; then
        if ! is_allowed_argument "${1#--}"; then
            die "Unknown parameter: $1"
        fi

        if (( $# < 2 )); then
            die "Missing value for $1"
        fi

        if [[ $2 == "--"* ]]; then
            die "Missing value for $1"
        fi

        printf -v "${1#--}" "%s" "$2"
        shift 2
    else
        die "Unexpected positional argument: $1"
    fi
done

echo "service_name: '$service_name' env: '$env'"
```

When we execute the script with `./test.sh --service_name catalog --env test`, it returns:

```sho
$ ./test.sh --service_name catalog --env test
service_name: 'catalog' env: 'test'
```

The parser accepts only `--name value` arguments whose names are declared and included in `allowed_arguments`. It rejects unknown options, missing values, and positional arguments. Values cannot start with `--`, because the parser treats them as another option. If you want to support flags without values, check [Support for --help?](#support-for-help)

## Argument validation

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

```sh
function usage {
    echo ""
    echo "Deploys an ECR image to Atlas using GitOps and ArgoCD."
    echo ""
    echo "usage: $0 --service_name string --tag string --namespace 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 ""
}
```

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](https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html) to check if the variable is empty:

```sh
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 to add `--help` is to handle it in the loop that turns arguments into variables:

```sh
while [ $# -gt 0 ]; do
    if [[ $1 == "--help" ]]; then
        usage
        exit 0
    elif [[ $1 == "--"* ]]; then
        if ! is_allowed_argument "${1#--}"; then
            die "Unknown parameter: $1"
        fi

        if (( $# < 2 )); then
            die "Missing value for $1"
        fi

        if [[ $2 == "--"* ]]; then
            die "Missing value for $1"
        fi

        printf -v "${1#--}" "%s" "$2"
        shift 2
    else
        die "Unexpected positional argument: $1"
    fi
done
```

Any flag that does not take a value can be added to this loop.

## Write better scripts: use ShellCheck

The article [*Please stop writing shell scripts*](https://pythonspeed.com/articles/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](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck) will help you catch some of the pitfalls. ShellCheck reports warnings with codes such as SC2086; its wiki explains what each code means:

![Show the explanation of error code SC2086.](https://keestalkstech.com/wp-content/uploads/2022/03/bash-SC2086.png)
*Show the explanation of SC2086.*

## Complete example

Putting everything together gives us the following complete script:

```sh
#!/usr/bin/env bash
set -euo pipefail

declare service_name="" tag="" namespace="" env=""
declare -ar allowed_arguments=(service_name tag namespace env)

is_allowed_argument() {
    local candidate=$1
    local allowed

    for allowed in "${allowed_arguments[@]}"; do
        if [[ $candidate == "$allowed" ]]; then
            declare -p -- "$candidate" &>/dev/null
            return
        fi
    done

    return 1
}

usage() {
    echo ""
    echo "Deploys an ECR image to Atlas using GitOps and ArgoCD."
    echo ""
    echo "usage: $0 --service_name string --tag string --namespace 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 "  --help                  show this help"
    echo ""
}

die() {
    printf "Script failed: %s\n" "$1" >&2
    exit 1
}

while [ $# -gt 0 ]; do
    if [[ $1 == "--help" ]]; then
        usage
        exit 0
    elif [[ $1 == "--"* ]]; then
        if ! is_allowed_argument "${1#--}"; then
            die "Unknown parameter: $1"
        fi

        if (( $# < 2 )); then
            die "Missing value for $1"
        fi

        if [[ $2 == "--"* ]]; then
            die "Missing value for $1"
        fi

        printf -v "${1#--}" "%s" "$2"
        shift 2
    else
        die "Unexpected positional argument: $1"
    fi
done

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

printf "Deploying %s:%s to %s/%s\n" \
    "$service_name" "$tag" "$env" "$namespace"
```

## Conclusion

Adding named arguments to a bash script is pretty easy. Declaring the argument variables and listing their names in an explicit whitelist prevents mistyped options and unrelated shell variables from being changed.

## Changelog

- 2026-07-19: Restricted named arguments to declared variables in an explicit allowlist, added argument validation and a strict-mode complete example, clarified parser limitations, and corrected references.
- 2022-12-22: Moved the reusable `lib.sh` example to [Bash script with a lib for named parameters](https://keestalkstech.com/bash-script-with-a-lib-for-named-parameters/).
- 2022-04-04: Shortened parameter validation using `elif`.
- 2022-03-28: Initial article.
