Swen Voigt

Parsen von Argumenten und Parametern - getopts vs. getopt

Irgendwann einmal trifft es jeden, der in der IT-Abteilung mit UNIX-Betriebssystemen zu tun hat: Es müsste mal eben ein kleines Skript geschrieben werden. Das Skript wird dann doch komplexer und und es sollen Optionen und Parameter übergeben werden. Zum Parsen der Kommandozeilen-Optionen empfehlen sich zwei Hilfsmittel: getopt und getopts. Der Buchstabe s macht den Unterschied.


Inhalt

Argumente, Optionen und Parameter

In der Befehlszeile einer Unix-Shell ist jede Zeichenkette ein Argument. So hat der Befehl “gzip –list datei” drei Argumente. Als Option bezeichnet man wiederum ein Argument, das das Verhalten des Programms ändert. Ein Parameter wiederum stellt eine Zusatzinformation entweder der Option oder dem Programm zur Verfügung. Zum Einleiten einer Option hat sich das Bindestrich-Minus eingebürgert, gefolgt von einem oder mehreren Buchstaben: “ls -l” oder “java -version”. Alternativ werden auch eine kurze und lange Variante der gleichen Option angeboten (GNU): “gzip -l datei” und “gzip –list datei”. Die kurze Variante der Option hat darüberhinaus den Vorteil, daß sich mehrere zusammenschreiben lassen. Statt “gzip -l -v datei” kann man auch “gzip -lv datei” schreiben. Das klappt mit der langen Variante natürlich nicht.

Damit sind diese drei Varianten von Kommandozeilen-Optionen möglich:

  • Option ohne Parameter
    Wie zum Beispiel in “ls -l”. Es ist ein einfaches Flag und bedeutet einfach “long format”
  • Option mit mandatorischem Parameter
    Wie hier: “find / -type d”. Hier werden ab dem Wurzelverzeichnis alle Directories ausgegeben. Mit “find / -type f” hingegen alle einfachen Dateien.
  • Option mit optionalem Parameter
    “grep –context=1 Liste” gibt jeweils die Zeile über und unter der Trefferzeile aus. Bei “grep –context Liste” sind es jeweils zwei Zeilen darüber und darunter, da dies der Default-Wert ist

Und natürlich sind auch Parameter ohne Option denkbar. Wie bei “ls verzeichnisname”

Vor- und Nachteile von getopt und getopts

Während getopts als inline-Befehl der ksh und bash immer dabei ist, ist das neuere getopt-Tool möglicherweise nicht installiert. Wenn bei der Eingabe von “getopt -T” keine 4 zurückgegeben wird, dann ist auch noch eine ältere Version installiert (wie z.B. bei MacOS).

Pro und cons von getopts:
Shell builtin-Befehl, in jeder POSIX-Shell dabei
unterstützt keine lange Schreibweise, wie bei den GNU-Tools
keine optionalen Parameter (,die aber ohnehin nie benötigt werden)

Pros und cons von getopt:
kein Shell-Builtin
Ältere Versionen unterstützen möglicherweise keine Whitespaces in Parametern (“script.sh -x ‘ abc 34’ -y ‘ ‘”)
unterstützt lange Optionen (gzip –list file)
optionale Parameter möglich

Habt ihr allgemein verwendbare Skripte, die auf andere Unix-Betriebssysteme portiert werden könnten, würde ich zur getopts-Variante raten. Insbesondere, wenn es ausreichend ist, die kurze Optionsschreibweise zu verwenden. Sind eure Skripte plattform-abhängig würde ich getopt (in Version 4) empfehlen.

Beispiel für getopt

Genug geschrieben. Wer direkt loslegen mag findet hier das Skript test_getopt.sh.

#!/bin/bash
GETOPT=/usr/bin/getopt
 
PROG=${0##*/}
 
function usage ()
{
cat <<EOF
usage: $PROG [options] <non-option parameter>
$PROG will just show the various options and parameters. The non-option parameter is not parsed by getopt,
but that kind of parameter is sometimes helpful as well.
 
  Options:
    -h,--help          print this help message.
    -f,--flag             option with no parameter, a flag
    -g,--gflag            another option with no parameter, a flag
    -o,--optional [param] option with an optional parameter. That could be useful if there is a default parameter.
    -r,--required [param} option with mandatory parameter.
EOF
}
 
# Test  if  your  getopt(1) is this enhanced version or an old version. This generates no output, and sets the error status to 4.
$GETOPT -T
if [ $? -ne 4 ]; then
  echo "Get getopt from http://software.frodo.looijaard.name/getopt or install package util-linux-ng."

  exit 1
fi
 
# process and assign command line arguments
_temp=$($GETOPT -o hfgr:o:: --long help,flag,gflag,required:,optional:: -n $PROG -- "$@")
if [ $? != 0 ] ; then echo "bad command line options" >&2 ; exit 1 ; fi
eval set -- "$_temp"
 
unset _OPT_FLAG
unset _OPT_FLAG2
unset _OPT_OPTIONAL
unset _OPT_REQUIRED
unset _OPT_PARAMETER
 
while true ; do
        case "$1" in
        -h|--help)
                        usage; exit 0 ;;
        -f|--flag)
                        _OPT_FLAG=1; shift; continue ;;
        -g|--gflag)
                        _OPT_FLAG2=1; shift; continue ;;
        -o|--optional)
                        if [[ -z "$2" ]]; then
                                _OPT_OPTIONAL="default"
                        else
                                _OPT_OPTIONAL=$2
                        fi
                        shift 2; continue ;;
        -r|--required)
                        _OPT_REQUIRED=$2; shift 2; continue ;;
        --)
                        shift; break ;;
        *)
                        if [[ -z "$_OPT_PARAMETER" ]]; then
                                _OPT_PARAMETER=$1
                                shift; break
                        else
                                echo "bad getopt-option $1"
                                usage
                                exit 1
                        fi
                        ;;
        esac
done
 
while [ $# -gt 0 ]; do
        if [[ -z "$_OPT_PARAMETER" ]]; then
                _OPT_PARAMETER=$1
                shift
        else
                echo "bad option $1"
                exit 1
        fi
done
 
echo _OPT_FLAG=$_OPT_FLAG
echo _OPT_FLAG2=$_OPT_FLAG2
echo _OPT_OPTIONAL=$_OPT_OPTIONAL
echo _OPT_REQUIRED=$_OPT_REQUIRED
echo _OPT_PARAMETER=$_OPT_PARAMETER

Und so schaut das ganze beim Testen aus:

test@localhost:~/scripts>./test_getopt.sh -f  -r reqarg --optional=optarg /path/to/file
_OPT_FLAG=1
_OPT_FLAG2=
_OPT_OPTIONAL=optarg
_OPT_REQUIRED=reqarg
_OPT_PARAMETER=/path/to/file
 
test@localhost:~/scripts>./test_getopt.sh --flag --gflag  --required reqarg --optional /path/to/file
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_OPTIONAL=default
_OPT_REQUIRED=reqarg
_OPT_PARAMETER=/path/to/file
 
test@localhost:~/scripts>./test_getopt.sh -fg /path/to/file -r reqarg -o=optarg
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_OPTIONAL==optarg
_OPT_REQUIRED=reqarg
_OPT_PARAMETER=/path/to/file
 
test@localhost:~/scripts>./test_getopt.sh -fg -r reqarg -o=optarg /path/to/file /wrong/arg
bad option /wrong/arg
 
test@localhost:~/scripts>./test_getopt.sh -flag -r reqarg -o=optarg /path/to/file
test_getopt.sh: invalid option -- 'l'
test_getopt.sh: invalid option -- 'a'
bad command line options

… und für getopts

Und alle, die “getopts” den Vorzug geben finden das Beispielprogramm test_getopts.sh hier.

#!/bin/bash
 
PROG=${0##*/}
 
function usage ()
{
cat <<EOF
usage: $PROG [options] <non-option parameter>
$PROG will just show the various options and parameters. The non-option parameter is not parsed by getopt,
but that kind of parameter is sometimes helpful as well.
 
  Options:
    -h         print this help message.
    -f         option with no parameter, a flag
    -g         another option with no parameter, a flag
    -r [param} option with mandatory parameter.
EOF
}
 
unset _OPT_FLAG
unset _OPT_FLAG2
unset _OPT_REQUIRED
unset _OPT_PARAMETER
 
# first colon diables the verbose error message
while getopts ":hfgr:" opt; do
  case $opt in
        h)
                        usage; exit 0 ;;
        f)
                        _OPT_FLAG=1 ;;
        g)
                        _OPT_FLAG2=1 ;;
        r)
                        _OPT_REQUIRED=$OPTARG ;;
        :)
                        echo "bad getopts-option $OPTARG."
                        usage
                        exit 1
                        ;;
        \?)
                        echo "bad getopts-option $1"
                        usage
                        exit 1
                        ;;
        esac
done
 
shift $((OPTIND-1))
 
while [ $# -gt 0 ]; do
        if [[ -z "$_OPT_PARAMETER" ]]; then
                _OPT_PARAMETER=$1
                shift
        else
                echo "bad option $1"
                # exit 1
                shift
        fi
done
 
echo _OPT_FLAG=$_OPT_FLAG
echo _OPT_FLAG2=$_OPT_FLAG2
echo _OPT_REQUIRED=$_OPT_REQUIRED
echo _OPT_PARAMETER=$_OPT_PARAMETER

Und hier das Ergebnis des Tests:

 
test@localhost:~/scripts>./test_getopts.sh -f -r 123 /path/to/file
_OPT_FLAG=1
_OPT_FLAG2=
_OPT_REQUIRED=123
_OPT_PARAMETER=/path/to/file
test@localhost:~/scripts>./test_getopts.sh -fg -r 123 /path/to/file
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_REQUIRED=123
_OPT_PARAMETER=/path/to/file
test@localhost:~/scripts>./test_getopts.sh -fg -r 123
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_REQUIRED=123
_OPT_PARAMETER=
test@localhost:~/scripts>./test_getopts.sh -fg /path/to/file -r 123
bad option -r
bad option 123
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_REQUIRED=
_OPT_PARAMETER=/path/to/file
test@localhost:~/scipts>./test_getopts.sh -fg /path/to/file -r
bad option -r
_OPT_FLAG=1
_OPT_FLAG2=1
_OPT_REQUIRED=
_OPT_PARAMETER=/path/to/file

Links

  1. Weil tatsächlich darüber diskutiert wird - bash oder ksh
  2. Parameter Expansion in BASH