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

bash, command-line-arguments, getopts, parameter-passing

Solution

`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.

Problem

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.

Original source