Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to parse command line args in Bash?

After several days of research, I still can't figure out the best method for parsing cmdline args in a .sh script. According to my references the getopts cmd is the way to go since it "extracts and checks switches without disturbing the positional parameter variables.Unexpected switches, or switches that are missing arguments, are recognized and reportedas errors."

Positional params(Ex. 2 - $@, $#, etc) apparently don't work well when spaces are involved but can recognize regular and long parameters(-p and --longparam). I noticed that both methods fail when passing parameters with nested quotes ("this is an Ex. of ""quotes""."). Which one of these three code samples best illustrates the way to deal with cmdline args? The getopt function is not recommended by gurus, so I'm trying to avoid it!

Example 1:

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
    ;;
    -l=*|--lib=*)
    DIR=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
exit 0

Example 2:

#!/bin/bash
echo ‘number of arguments’
echo "\$#: $#"
echo ”

echo ‘using $num’
echo "\$0: $0"
if [ $# -ge 1 ];then echo "\$1: $1"; fi
if [ $# -ge 2 ];then echo "\$2: $2"; fi
if [ $# -ge 3 ];then echo "\$3: $3"; fi
if [ $# -ge 4 ];then echo "\$4: $4"; fi
if [ $# -ge 5 ];then echo "\$5: $5"; fi
echo ”

echo ‘using $@’
let i=1
for x in $@; do
echo "$i: $x"
let i=$i+1
done
echo ”

echo ‘using $*’
let i=1
for x in $*; do
echo "$i: $x"
let i=$i+1
done
echo ”

let i=1
echo ‘using shift’
while [ $# -gt 0 ]
do
echo "$i: $1"
let i=$i+1
shift
done

[/bash]

output:

bash> commandLineArguments.bash
number of arguments
$#: 0

using $num
$0: ./commandLineArguments.bash

using $@

using $*

using shift
#bash> commandLineArguments.bash  "abc def" g h i j*

Example 3:

#!/bin/bash

while getopts ":a:" opt; do
  case $opt in
    a)
      echo "-a was triggered, Parameter: $OPTARG" >&2
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done

exit 0
like image 278
LogicalConfusion Avatar asked Feb 09 '13 10:02

LogicalConfusion


People also ask

How do I parse args bash?

We can use the getopts program/ command to parse the arguments passed to the script in the command line/ terminal by using loops and switch-case statements. Using getopts, we can assign the positional arguments/ parameters from the command line to the bash variables directly.

How do I parse a command-line in bash?

To parse short command-line options, we can use bash's built-in function getopts. It parses positional parameters as options.

How do you read an argument in shell script?

The special character $# stores the total number of arguments. We also have $@ and $* as wildcard characters which are used to denote all the arguments. We use $$ to find the process ID of the current shell script, while $? can be used to print the exit code for our script.


2 Answers

I find the use of getopt to be the easiest. It provides correct handling of arguments which is tricky otherwise. For example, getopt will know how to handle arguments to a long option specified on the command line as --arg=option or --arg option.

What is useful in parsing any input passed to a shell script is the use of the "$@" variables. See the bash man page for how this differs from $@. It ensures that you can process arguments that include spaces.

Here's an example of how I might write s script to parse some simple command line arguments:

#!/bin/bash

args=$(getopt -l "searchpath:" -o "s:h" -- "$@")

eval set -- "$args"

while [ $# -ge 1 ]; do
        case "$1" in
                --)
                    # No more options left.
                    shift
                    break
                   ;;
                -s|--searchpath)
                        searchpath="$2"
                        shift
                        ;;
                -h)
                        echo "Display some help"
                        exit 0
                        ;;
        esac

        shift
done

echo "searchpath: $searchpath"
echo "remaining args: $*"

And used like this to show that spaces and quotes are preserved:

user@machine:~/bin$ ./getopt_test --searchpath "File with spaces and \"quotes\"."
searchpath: File with spaces and "quotes".
remaining args: other args

Some basic information about the use of getopt can be found here

like image 83
Austin Phillips Avatar answered Oct 19 '22 16:10

Austin Phillips


If you want to avoid using getopt you can use this nice quick approach:

  • Defining help with all options as ## comments (customise as you wish).
  • Define for each option a function with same name.
  • Copy the last five lines of this script to your script (the magic).

Example script: log.sh

#!/bin/sh
## $PROG 1.0 - Print logs [2017-10-01]
## Compatible with bash and dash/POSIX
## 
## Usage: $PROG [OPTION...] [COMMAND]...
## Options:
##   -i, --log-info         Set log level to info (default)
##   -q, --log-quiet        Set log level to quiet
##   -l, --log MESSAGE      Log a message
## Commands:
##   -h, --help             Displays this help and exists
##   -v, --version          Displays output version and exists
## Examples:
##   $PROG -i myscrip-simple.sh > myscript-full.sh
##   $PROG -r myscrip-full.sh   > myscript-simple.sh
PROG=${0##*/}
LOG=info
die() { echo $@ >&2; exit 2; }

log_info() {
  LOG=info
}
log_quiet() {
  LOG=quiet
}
log() {
  [ $LOG = info ] && echo "$1"; return 1 ## number of args used
}
help() {
  grep "^##" "$0" | sed -e "s/^...//" -e "s/\$PROG/$PROG/g"; exit 0
}
version() {
  help | head -1
}

[ $# = 0 ] && help
while [ $# -gt 0 ]; do
  CMD=$(grep -m 1 -Po "^## *$1, --\K[^= ]*|^##.* --\K${1#--}(?:[= ])" log.sh | sed -e "s/-/_/g")
  if [ -z "$CMD" ]; then echo "ERROR: Command '$1' not supported"; exit 1; fi
  shift; eval "$CMD" $@ || shift $? 2> /dev/null
done

Testing

Running this command:

./log.sh --log yep --log-quiet -l nop -i -l yes

Produces this output:

yep
yes

By the way: It's compatible with posix!

like image 24
Tet Avatar answered Oct 19 '22 15:10

Tet