Search code examples
bashgetopt

Invalid option -- ' ' and '-'


I'm using getopt to parse my command line arguments. My script is for starting, stopping, restarting, and getting the status of a daemon. I know I could write an init script, but I need my script to be largely portable on Linux machines.

My script takes one command and a few options. Usage is like this:

activemq start -f
activemq start --foreground
activemq stop -k
activemq stop --kill
activemq restart
activemq start

Here's my parsing logic for my options, ie $2 and onward:

options="${@:$(($1+2))}"

set -- $(getopt -o fk --long foreground,kill -- "$options")

foreground=no
killprocess=no

while [ $# -gt 0 ] ; do
    case "$1" in 
        -f | --foreground) 
            foreground=yes
            shift
            ;;
        -k | --kill)
            killprocess=yes
            shift
            ;;
        --)
            shift
            ;;
        *)
            echo "Error: unrecognized option: $1" >&2
            usage
            ;;
    esac
done

echo "command=$command, foreground=$foreground, killprocess=$killprocess"

Everything works fine with the following invocation:

activemq restart -fk

However, if I try separating them out into individual arguments, it breaks

$ activemq restart -f -k
getopt: invalid option -- ' '
getopt: invalid option -- '-'

What am I missing here?


Solution

  • options needs to be an array. Replace this:

    options="${@:$(($1+2))}"
    set -- $(getopt -o fk --long foreground,kill -- "$options")
    

    With this:

    options=("${@:$(($1+2))}")
    set -- $(getopt -o fk --long foreground,kill -- "${options[@]}")
    

    getopt expects that each option string is a separate argument. By using an array, separate arguments are kept separate.

    Minimal example

    First, let's set some positional parameters:

    $ set restart -- -k -f
    

    Now, let's use bash arrays:

    $ options=("${@:$(($1+2))}")
    $ printf '>>%s\n' "${options[@]}"
    >>--
    >>-k
    >>-f
    

    The printf statement demonstrates that each positional parameter remains separate.

    Now, try without arrays:

    $ options="${@:$(($1+2))}"
    $ printf '>>%s\n' "$options"
    >>-- -k -f
    

    printf shows that "$options" creates a single argument where three were expected.