Bash script that accepts short, long, and positional arguments

I asked ChatGPT to "Write me a Bash script that accepts short, long, and positional arguments" and it came up with the following:

#!/bin/bash

# Define short options
options=":s:l:"

# Define long options
long_options=("short-option:" "long-option:")

# Parse options and arguments
while getopts "$options" opt; do
  case $opt in
    s)
      short_opt_arg="$OPTARG"
      ;;
    l)
      long_opt_arg="$OPTARG"
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done

# Shift parsed options to access positional arguments
shift $((OPTIND-1))

# Display parsed options and positional arguments
echo "Short option argument: $short_opt_arg"
echo "Long option argument: $long_opt_arg"
echo "Positional arguments: $@"

It may look impressive but the code is wrong because getopts does not support long option names. The usage ChatGPT suggested is shown below and if you save the code above in script.sh and try to run the example, it'll return an error.

./script.sh -s value1 --long-option=value2 arg1 arg2 arg3
Invalid option: --

One way to accept short, long, and positional arguments in a Bash script is to use GNU getopt (without the s). Here's a template:

#!/usr/bin/env bash
#
# Adapted from https://www.shellscript.sh/examples/getopt/
#
set -euo pipefail

alpha=unset
beta=unset
gamma=unset
delta=unset

usage(){
>&2 cat << EOF
Usage: $0
   [ -a | --alpha ]
   [ -b | --beta ]
   [ -g | --gamma input ] 
   [ -d | --delta input ]
   <infile> [infiles]
EOF
exit 1
}

args=$(getopt -a -o abhc:d: --long alpha,beta,help,gamma:,delta: -- "$@")
if [[ $? -gt 0 ]]; then
  usage
fi

eval set -- ${args}
while :
do
  case $1 in
    -a | --alpha)   alpha=1    ; shift   ;;
    -b | --beta)    beta=1     ; shift   ;;
    -h | --help)    usage      ; shift   ;;
    -c | --gamma)   gamma=$2   ; shift 2 ;;
    -d | --delta)   delta=$2   ; shift 2 ;;
    # -- means the end of the arguments; drop this, and break out of the while loop
    --) shift; break ;;
    *) >&2 echo Unsupported option: $1
       usage ;;
  esac
done

if [[ $# -eq 0 ]]; then
  usage
fi

>&2 echo "alpha   : ${alpha}"
>&2 echo "beta    : ${beta} "
>&2 echo "gamma   : ${gamma}"
>&2 echo "delta   : ${delta}"
>&2 echo "Parameters remaining are: $@"
exit 0

If you run the script without any arguments/options, you'll get the usage:

./getopt.sh
Usage: ./getopt.sh
   [ -a | --alpha ]
   [ -b | --beta ]
   [ -g | --gamma input ]
   [ -d | --delta input ]
   <infile> [infiles]

The -a or --alpha and -b or --beta are switches; if you specify them they become 1. -g or --gamma and -d or --delta accept an input. Positional inputs are provided after the optional parameters. Below is an example:

./getopt.sh -a --beta -g gamma_input --delta delta_input test.txt test2.txt
# alpha   : 1
# beta    : 1
# gamma   : gamma_input
# delta   : delta_input
# Parameters remaining are: test.txt test2.txt

How does the script work? It all relies on the following line of code:

args=$(getopt -a -o abhc:d: --long alpha,beta,help,gamma:,delta: -- "$@")

$@ is a special Bash variable that contains all the command line arguments passed to the script. In the example above, $@ would contain:

-a --beta -g gamma_input --delta delta_input test.txt test2.txt

The command line arguments are provided to getopt along with the short (-o) and long (--long) options to be recognised. The -a argument to getopt allows us to specify long options with a single dash, so the following works as well.

./getopt.sh -a -beta -g gamma_input -delta delta_input test.txt test2.txt
# alpha   : 1
# beta    : 1
# gamma   : gamma_input
# delta   : delta_input
# Parameters remaining are: test.txt test2.txt

The rest of the code simply parses the output of getopt (with a Bash case statement), which is stored in the variable args and contains the following for our example:

-a --beta --gamma 'gamma_input' --delta 'delta_input' -- 'test.txt' 'test2.txt'

You may have used command line tools in the past where you included a double-dash (--). This is typically used to demarcate the end of the optional parameters. In the Bash script above, when -- is encountered the while loop breaks out (exits) and what is left in the stack ($@) are the positional arguments.

So there you have it: +1 to humanity.

Print Friendly, PDF & Email



Creative Commons License
This work is licensed under a Creative Commons
Attribution 4.0 International License
.
2 comments Add yours
  1. It’s always really nice to find great and the same time very productive contributions like yours.
    sh code works smoothly nice and good.

  2. This is an age-old issue for developers. Really hope bash will provide native support for both short and long arguments.

Leave a Reply

Your email address will not be published. Required fields are marked *

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