71

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?

3
  • 1
  • getopts supports long options. You need to use the Korn Shell or the Bourne Shell.
    – schily
    Jul 24, 2021 at 16:17
  • While not your direct question, give thought to Perl 5, it's options modules are super easy to use and very effective, and allow full customization as well as behaving precisely how you expect options to work, they also allow for much better error handling. To me there are several red flags I use now to tell me to stop using shell script and switch to perl, options handling is one, length of script is another (> 50 lines or so, perl): Sample: use Getopt::Long qw(GetOptions); Getopt::Long::Configure ('bundling', 'no_ignore_case', 'no_getopt_compat', 'no_auto_abbrev','pass_through');
    – Lizardx
    Mar 8, 2023 at 21:07

5 Answers 5

42

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.

Many "traditional" implementations of getopt indeed cannot parse arguments with embedded whitespace, but that's not the case with the one from util-linux or the one included in Busybox:

  • test.sh:

    #!/usr/bin/env bash
    getopt -T
    if [ "$?" != 4 ]; then
        echo 2>&1 "Wrong version of 'getopt' detected, exiting..."
        exit 1
    fi
    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
    

Of course the portability issue still stands; you'll have to decide how much time is worth investing in platforms without that version of getopt. 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.

12
  • 17
    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. Jan 29, 2013 at 12:45
  • 4
    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. Jan 29, 2013 at 12:55
  • 16
    getopt is a traditional command that comes from System V long before Linux was ever released. getopt was never standardised. None of POSIX, Unix, or Linux (LSB) ever standardized the getopt command. getopts is specified in all three but without support for long options. Jan 29, 2013 at 13:08
  • 3
    Just to add to this discussion: this is not the traditional getopt. It is the linux-utils flavor as indicated by @StéphaneChazelas. It has legacy options that will disable the syntax described above, in particular the manpage states "GETOPT_COMPATIBLE forces getopt to use the first calling format as specified in the SYNOPSIS". However if you can expect target systems to have this package installed this is totally the way to go, as the original getopt is awful and Bash's getopt is very limited
    – n.caillou
    May 18, 2017 at 15:15
  • 2
    OP's link that claims "Traditional versions of getopt cannot handle empty argument strings, or arguments with embedded whitespace." and that the util-linux version of getopt shouldn't be used has no evidence and is no longer accurate. A quick survey (5+ years later) shows that the default version of getopt available from ArchLinux, SUSE, Ubuntu, RedHat, Fedora, and CentOS (as well as most derivative variants) all support optional arguments and arguments with whitespace.
    – mtalexan
    Aug 21, 2018 at 16:51
13

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.

2
  • 3
    getopts is a POSIX standard. I agree though if scripts also need to run on Solaris etc. that do not have GNU bash and tools installed. Feb 12, 2020 at 16:43
  • All shells that are decently written and call the C library function getopt(3c) for getopts include support for long options on Solaris.
    – schily
    Jul 24, 2021 at 16:18
12

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.

5

Every discussion on this matter highlight the option to write the parsing code manually - only then you can be sure about the functionality and portability. I advise you not to write code that you can have generated and re-generated by easy-to-use open-source code generators. Use Argbash, which has been designed to provide the definitive answer to your problem. It is a well-documented code generator available as a command-line application, online or as a Docker image.

I advise against bash libraries, some of them use getopt (which makes quite unportable) and it is a pain to bundle a gigantic unreadable shell blob with your script.

1
1

You could use getopt on systems that support it and use a fall back for systems that don't.

For instance pure-getopt is implemented in pure Bash to be a drop-in replacement for GNU getopt.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .