Parsing command line arguments in Bash
Tags: bash
Published: 2020-07-24 09:29:48

**TL;DR**: [bargs](https://github.com/unfor19/bargs) - A featherweight Bash package, which enables an easy way to use command line arguments in your Bash scripts

Every time I need to write a Bash script which accepts command line arguments, I go back to this great answer in StackOverflow - [How do I parse command line arguments in Bash](https://stackoverflow.com/a/14203146/5285732)

Let's do a quick overview of the process!

## The usual way

So here I am, maintaining the command line arguments, which in turn, will be variables, in four different places. Why four? Here we go!

### 1 - usage function

The `usage` function should pop up whenever the user attempts to provide wrong or missing arguments

```bash
usage()
{
cat << EOF
usage: bash ./scripts/packndeploy -n service_name
-n    | --service_name      (Required)            Service to deploy
-b    | --branch            (master)              Source branch
-h    | --help                                    Brings up this menu
EOF
}
```

### 2 - Declaring Variables
To get the command line arguments, we need to declare variables that can store them, and also set the default values for these variables/arguments.

```bash
service_name=
branch=master # default value
```

### 3 - Arguments to Variables
Mapping arguments to variables with `while case shift` is a great trick to fetch variables! To learn more about it read this [great tutorial](http://linuxcommand.org/lc3_wss0120.php) or this great comment in [StackOverflow](https://stackoverflow.com/a/14203146/5285732)

```bash
while [ "$1" != "" ]; do
    case $1 in
        -n | --service_name )
            shift
            service_name=$1
        ;;
        -b | --branch )
            shift
            branch=$1              
        -h | --help )    usage
            exit
        ;;
        * )              usage
            exit 1
    esac
    shift
done
```

### 4 - Verifying required arguments
After the `while` loop above :point_up: we need to make sure that the required variables were provided. So here's a quick and dirty way to do it

```bash
if [ -z $service_name ]; then
    echo "Service name is required, provide it with the flag: -n service_name"
    exit
fi
```

*NOTE*: I'm sure that it's better to write a function that performs some of the tasks above dynamically, but it still means we need to write it or copy-paste it to our Bash script.

## Motivation
I'm writing a lot of Bash scripts, and most of them require command line arguments, so I thought it'd be best to write a "wrapper script" that performs all the above-mentioned steps.

The "wrapper script" should meet the following requirements
- **Manage all arguments**, including the variables that they become **in one place**
- **A beautiful usage or help message** that is based on the definitions of the arguments. We'll get to that, I call it `bargs_vars`
- **Adding the "wrapper script" by performing minimum steps** and without polluting my Bash script

## The Solution - bargs
Ladies and gentlemen, I present to you [bargs](https://github.com/unfor19/bargs) - a comfortable way to deal with command line arguments in Bash.

So how do you use it? Keep on reading or jump to the [bargs GitHub repository](https://github.com/unfor19/bargs) to get started!

## Requirements

- Bash v4.4+
- [Linux Utils](https://command-not-found.com/column) - We're printing beautiful stuff with the [column](https://linux.die.net/man/1/column) command, that is included in Linux Utils

### macOS

```bash
$ brew install util-linux
```

### Linux (Debian/Ubuntu)

```bash
$ sudo apt-get -y update && sudo apt-get install -y bsdmainutils
```

### Windows

Not supported (yet)

## Getting Started

1. Download the script (4 kilobytes) and put it in the same folder as your code

   ```bash
   curl -s -L bargs.link/bargs.sh --output bargs.sh
   ```

1. Creating bargs_vars - do one of the following
   - Create the file `bargs_vars`, put it in the same folder as `bargs.sh`
   - Download the existing `bargs_vars` template
     ```bash
     curl -s -L bargs.link/bargs_vars --output bargs_vars
     ```
1. Declaring arguments/variables

   - The delimiter `---` is required once at the beginning, and **twice** in the end
   - If the default is empty or not defined, the argument is required
   - You can't add comments to this file, use the description
   - Arguments values (including default) must not contain whitespace
   - Use the bargs description to set the `--help` message

   ```bash
    ---
    name=person_name
    short=n
    description=What is your name?
    default=Willy
    ---
    name=age
    short=a
    ---
    name=gender
    short=g
    ---
    name=location
    short=l
    description=insert your location
    default=chocolate-factory
    ---
    name=bargs
    description=bash example.sh -n Willy --gender male -a 99
    ---
    ---
   ```

1. Add the following line at the beginning of your application

   ```bash
   source bargs.sh "$@"
   ```

1. That's it! You can now reference to arguments that were declared in `bargs_vars`

### Usage

Using the `bargs_args` above in our application - `example.sh`

```bash
#!/bin/bash
source bargs.sh "$@"

echo -e \
"Name:~$person_name\n"\
"Age:~$age\n"\
"Gender:~$gender\n"\
"Location:~$location" | column -t -s "~"
```

#### Usage output

- Using the help flag

  ```bash
  bash example.sh -h

  Usage: bash example.sh -n Willy --gender male -a 99

        --person_name  |  -n  [Willy]              What is your name?
        --age          |  -a  [Required]
        --gender       |  -g  [Required]
        --location     |  -l  [chocolate-factory]  insert your location
  ```

- Using default values

  ```bash
  $ bash example.sh -a 99 --gender male

  Name:      Willy
  Age:       99
  Gender:    male
  Location:  chocolate-factory
  ```

- Providing all arguments

  ```bash
  $ bash example.sh -a 23 --gender male -l neverland -n meir

  Name:      meir
  Age:       23
  Gender:    male
  Location:  neverland
  ```

- Providing an empty required argument

  ```bash
  $ bash example.sh -a 99 --gender

  [ERROR] Empty argument: gender

  Usage: bash example.sh -n Willy --gender male -a 99

        --person_name  |  -n  [Willy]              What is your name?
        --age          |  -a  [Required]
        --gender       |  -g  [Required]
        --location     |  -l  [chocolate-factory]  insert your location
  ```

- Providing an unknown argument

  ```bash
  $ bash example.sh -a 99 -u meir

  [ERROR] Unknown argument: -u

  Usage: bash example.sh -n Willy --gender male -a 99

        --person_name  |  -n  [Willy]              What is your name?
        --age          |  -a  [Required]
        --gender       |  -g  [Required]
        --location     |  -l  [chocolate-factory]  insert your location
  ```

## Final words
I hope that using [bargs](https://github.com/unfor19/bargs) will assist you in writing a bash script and parsing command line arguments.

Feel free to ask questions, I've already answered a few in this [reddit](https://www.reddit.com/r/devops/comments/hwfvj4/bargs_parse_command_line_arguments_in_bash/) post, so keep 'em coming. The community feedback is what makes open-source projects better, so I urge you to doubt and ask questions! :)

Liked this blog post? Thank you! Don't forget to 👏/💟/🐴 and share!