Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Currently I'm writing a Bash script which has the following requirements:

  • it should run on a wide variety of Unix/Linux platforms
  • it should support both short and (GNU) long options

I know that getopts would be the preferred way in terms of portability but AFAIK it doesn't support long options.

getopt supports long options but the BashGuide recommends strongly against it:

Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.

So, there still is the option of manual parsing. This is error-prone, produces quite some boilerplate code, and I need to handle errors by myself (I guess getopt(s) do error-handling by themselves).

So, what would be the preferred choice in this case?

share|improve this question
add comment

3 Answers 3

up vote 1 down vote accepted

If it has to be portable to a range of Unices, you'd have to stick to POSIX sh. And AFAIU there you just have no choice but rolling argument handling by hand.

share|improve this answer
add comment

getopt vs getopts seems to be a religious issue. As for the arguments against getopt in the Bash FAQ:

  • "getopt cannot handle empty arguments strings" seems to refer to a known issue with optional arguments, which it looks like getopts doesn't support at all (at least from reading help getopts for Bash 4.2.24). From man getopt:

    getopt(3) can parse long options with optional arguments that are given an empty optional argument (but can not do this for short options). This getopt(1) treats optional arguments that are empty as if they were not present.

I don't know where the "getopt cannot handle [...] arguments with embedded whitespace" comes from, but let's test it:

  • test.sh:

    #!/usr/bin/env bash
    set -o errexit -o noclobber -o nounset -o pipefail
    params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")"
    eval set -- "$params"
    
    while true
    do
        case "$1" in
            -a|--alpha)
                echo alpha
                shift
                ;;
            -b|--bravo)
                echo "bravo=$2"
                shift 2
                ;;
            -c|--charlie)
                echo charlie
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Not implemented: $1" >&2
                exit 1
                ;;
        esac
    done
    
  • run:

    $ ./test.sh -
    $ ./test.sh -acb '   whitespace   FTW   '
    alpha
    charlie
    bravo=   whitespace   FTW   
    $ ./test.sh -ab '' -c
    alpha
    bravo=
    charlie
    $ ./test.sh --alpha --bravo '   whitespace   FTW   ' --charlie
    alpha
    bravo=   whitespace   FTW   
    charlie
    

Looks like check and mate to me, but I'm sure someone will show how I completely misunderstood the sentence. Of course the portability issue still stands; you'll have to decide how much time is worth investing in platforms with an older or no Bash available. My own tip is to use the YAGNI and KISS guidelines - Only develop for those specific platforms which you know are going to be used. Shell code portability generally goes to 100% as development time goes to infinity.

share|improve this answer
2  
The OP mentioned the need to be portable to many Unix platforms while the getopt you're quoting here is Linux-specific. Note that getopt is not part of bash, it's not even a GNU utility and on Linux is shipped with the util-linux package. –  Stéphane Chazelas Jan 29 '13 at 12:45
    
I don't think the OP would be asking this if getopt was not available. –  l0b0 Jan 29 '13 at 12:53
    
Most platforms have getopt, only Linux AFAIK comes with one that supports long options or blanks in arguments. The other ones would only support the System V syntax. –  Stéphane Chazelas Jan 29 '13 at 12:55
    
@StephaneChazelas Also, I thought it was clear that getopt is not part of Bash since it's got a separate man page. –  l0b0 Jan 29 '13 at 12:56
    
@StephaneChazelas The original argument still stands: OP wouldn't be asking about pros and cons of a feature unless that feature is supported on the relevant platforms. It seems obvious that the choice is between a getopt which supports -l, getopts or something completely different. –  l0b0 Jan 29 '13 at 12:57
show 2 more comments

There's this getopts_long written as a POSIX shell function that you may embed inside your script.

Note that the Linux getopt (from util-linux) works correctly when not in traditional mode and supports long options, but is probably not an option for you if you need to be portable to other Unices.

Recent versions of ksh93 (getopts) and zsh (zparseopts) have built-in support for parsing long options which might be an option for you as those are available for most Unices (though often not installed by default).

Another option would be to use perl and its Getopt::Long module both of which should be available on most Unices nowadays, either by writing the whole script in perl or just call perl just to parse the option and feed the extracted information to the shell. Something like:

parsed_ops=$(
  perl -MGetopt::Long -le '

    @options = (
      "foo=s", "bar", "neg!"
    );

    Getopt::Long::Configure "bundling";
    $q="'\''";
    GetOptions(@options) or exit 1;
    for (map /(\w+)/, @options) {
      eval "\$o=\$opt_$_";
      $o =~ s/$q/$q\\$q$q/g;
      print "opt_$_=$q$o$q"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

See perldoc Getopt::Long for what it can do and how it differs from other option parsers.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.