Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mass arguments (operands) at first place in command line argument passing

I use following lines (hope this is best practice if not correct me please) to handle command line options:

#!/usr/bin/bash

read -r -d '' HELP <<EOF
OPTIONS:
-c    Enable color output
-d    Enable debug output
-v    Enable verbose output
-n    Only download files (mutually exclusive with -r)
-r    Only remove files (mutually exclusive with -n)
-h    Display this help
EOF

# DECLARE VARIABLES WITH DEFAULT VALUES
color=0
debug=0
verbose=0
download=0
remove=0


OPTIND=1                # Reset in case getopts has been used previously in the shell
invalid_options=();     # Array for invalid options

while getopts ":cdvnrh" opt; do
  echo "Actual opt: $opt"
  case $opt in
    c)
      color=1
      ;;
    d)
      debug=1
      ;;
    v)
      verbose=1
      ;;
    n)
      download=1
      ;;
    r)
      remove=1
      ;;
    h)
      echo "$HELP"
      exit 1
      ;;
   \?)
      invalid_options+=($OPTARG)
      ;;
   *)
      invalid_options+=($OPTARG)
      ;;
  esac
done

# HANDLE INVALID OPTIONS
if [ ${#invalid_options[@]} -ne 0 ]; then
  echo "Invalid option(s):" >&2
  for i in "${invalid_options[@]}"; do
    echo $i >&2
  done
  echo "" >&2
  echo "$HELP" >&2
  exit 1
fi

# SET $1 TO FIRST MASS ARGUMENT, $2 TO SECOND MASS ARGUMENT ETC
shift $((OPTIND - 1))

# HANDLE CORRECT NUMBER OF MASS OPTIONS
if [ $# -ne 2 ]; then
  echo "Correct number of mass arguments are 2"
  echo "" >&2
  echo "$HELP" >&2
  exit 1
fi


# HANDLE MUTUALLY EXCLUSIVE OPTIONS
if [ $download -eq 1 ] && [ $remove -eq 1 ]; then
  echo "Options for download and remove are mutually exclusive" >&2
  echo "$HELP" >&2
  exit 1
fi



echo "color:    $color"
echo "debug:    $debug"
echo "verbose:  $verbose"
echo "download: $download"
echo "remove:   $remove"
echo "\$1:      $1"
echo "\$2:      $2"

If I call the script way that mass arguments (those that are not switches or arguments for switches) are last arguments everything is working correctly:

$ ./getopts.sh -c -d -v -r a b
Actual opt: c
Actual opt: d
Actual opt: v
Actual opt: r
color:    1
debug:    1
verbose:  1
download: 0
remove:   1
$1:      a
$2:      b

The problem is when I want to call the script so the mass arguments are first (or somewhere in the middle of switches that do not use arguments)

$ ./getopts.sh a b -c -d -v -r
Correct number of mass arguments are 2

OPTIONS:
-c    Enable color output
-d    Enable debug output
-v    Enable verbose output
-n    Only download files (mutually exclusive with -r)
-r    Only remove files (mutually exclusive with -d)
-h    Display this help

or

$ ./getopts.sh -c a b -d -v -r
Actual opt: c
Correct number of mass arguments are 2

OPTIONS:
-c    Enable color output
-d    Enable debug output
-v    Enable verbose output
-n    Only download files (mutually exclusive with -r)
-r    Only remove files (mutually exclusive with -d)
-h    Display this help

I think this should be OK according (POSIX) standards, because following syntax which is basically the same is working as expected on my system:

$ cp test1/ test2/ -r
$ cp test1/ -r test2/

I have search over the Internet but only thing that was close to my problem was this one related to C.

like image 916
Wakan Tanka Avatar asked Feb 18 '15 12:02

Wakan Tanka


2 Answers

getopts automatically breaks the while loop as soon as it detects a non-dash parameter (not including the argument given to dash parameters that take arguments). The POSIX standard is to have dashed parameters come first, and then have files. There's also none of this -- and + crap either. It's plain and simple.

However, Linux isn't Unix or POSIX compliant. It's just in the nature of the GNU utilities to be "better" than the standard Unix utilities. More features, more options, and handling things a bit differently.

On Linux, command line parameters can come after files in many GNU utilities.

For example:

$ cp -R foo bar

Work on my Unix certified Mac OS X and on Linux, However,

$ cp foo bar -R

Only works on Linux.

If you want getopts to work like a lot of Linux utilities, you need to do a wee bit of work.

First, you have to process your arguments yourself, and not depend upon $OPTIND to parse them. You also need to verify that you have an argument.

I came up with this as an example of doing what you want.

#! /bin/bash

while [[ $* ]]
do
    OPTIND=1
    echo $1
    if [[ $1 =~ ^- ]]
    then
        getopts :a:b:cd parameter
        case $parameter in
            a)  echo "a"
                echo "the value is $OPTARG"
                shift
                ;;
            b)  echo "b"
                echo "the value is $OPTARG"
                shift
                ;;
            c)  echo "c"
                ;;
            d)  echo "d"
                ;;
            *) echo "This is an invalid argument: $parameter"
                ;;
        esac
    else
        other_arguments="$other_arguments $1"
    fi
    shift
done
echo "$other_arguments"

I now loop as long as $* is set. (Maybe I should use $@?) I have to do a shift at the end of the loop. I also reset $OPTIND to 1 each time because I'm shifting the arguments off myself. $OPTARG is still set, but I have to do another shift to make sure everything works.

I also have to verify if a argument begins with a dash or not using a regular expression in my if statement.

Basic testing shows it works, but I can't say it's error free, but it does give you an idea how you have to handle your program.

There's still plenty of power you're getting from getopts, but it does take a bit more work.

like image 182
David W. Avatar answered Sep 28 '22 08:09

David W.


Bash provides two methods for argument parsing.

The built-in command getopts is a newer, easy to use mechanism how to parse arguments but it is not very flexible. getopts does not allow to mix options and mass arguments.

The external command getopt is an older and more complex mechanism to parse arguments. It allows long/short options and the gnu extension allow to mix options and mass arguments.

like image 40
Zaboj Campula Avatar answered Sep 28 '22 09:09

Zaboj Campula