Parsen von Argumenten und Parametern - getopts vs. getopt
12. August 2017Irgendwann 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