Ich analysiere Optionen mit, getopts
möchte aber auch lange Optionen verarbeiten.
print-args ()
{
title="$1" ; shift
printf "\n%s\n" "${title}: \$@:"
for arg in "$@"; do
(( i = i + 1 ))
printf "%s |%s|\n" "${i}." "$arg"
done
}
getopts_test ()
{
aggr=()
for arg in "$@"; do
case $arg in
("--colour"|"--color") aggr+=( "-c" ) ;;
("--colour="*|"--color="*) aggr+=( "-c" "${arg#*=}" ) ;;
(*) aggr+=( "$arg" ) ;;
esac
done
print-args "print" "$@"
eval set -- "${aggr[@]}"
print-args "eval" "$@"
set -- "${aggr[@]}"
print-args "set" "$@"
local OPTIND OPTARG
local shortopts="C:"
while getopts "$shortopts" arg; do
case $arg in
("c") context="$OPTARG" ;;
(*) break ;;
esac
done
shift $(( OPTIND - 1 ))
}
Ich frage mich jedoch, ob die Verwendung von set -- "${aggr[@]}"
korrekt ist.
Oder ist Folgendes (mit eval
) angemessener?
eval set -- "${aggr[@]}"
Ich habe einen unten gezeigten Test durchgeführt. Mit eval wird die Zeichenfolge „Gunga Din“ aufgeteilt, während set -- "${aggr[@]}"
sie mit korrekt als einzelne Zeichenfolge analysiert wird.
getopts_test -f -g 130 --colour="170 20" "Gunga Din"
print: $@:
1. |-f|
2. |-g|
3. |130|
4. |--colour=170 20|
5. |Gunga Din|
eval: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170|
6. |20|
7. |Gunga|
8. |Din|
set: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170 20|
6. |Gunga Din|
Dann habe ich eine andere Funktion ausgeführt, die das Nicht-GNU verwendet getopt
.
getopt_test ()
{
shortopts="Vuhv::H::w::e::n::l::C:"
shortopts="${shortopts}bgcrmo"
longopts="version,usage,help,verbosity::"
longopts="${longopts},heading::,warning::,error::"
longopts="${longopts},blu,grn,cyn,red,mgn,org"
opts=$( getopt -o "$shortopts" -l "$longopts" -n "${0##*/}" -- "$@" )
print-args "\$@:" "$@"
print-args "opts:" "$opts"
set -- "$opts"
print-args "set -- \"$opts\"" "$@"
eval set -- "$opts"
print-args "eval set -- \"$opts\"" "$@"
}
Daraus ergab sich folgendes
getopt_test --warning=3 "foo'bar" "Gunga Din"
$@:
1. |--warning=3|
2. |foo'bar|
3. |Gunga Din|
opts:
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
set -- "$opts"
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
eval set -- "$opts"
1. |--warning|
2. |3|
3. |--|
4. |foo'bar|
5. |Gunga Din|
Wie gezeigt ist das Ergebnis von getopt ein einzelner Eintrag mit neu angeordneten Positionsargumenten. Dies zeigt, dass eval set -- "$opts"
die Positionsargumente in der opts
Zeichenfolge für die Optionenanalyse und -verarbeitung in fünf Einträge aufgeteilt werden müssen.
Antwort1
Die Idee besteht darin, die Argumente vorzuverarbeiten und jedes --context
in zu ändern -C
, was getopts
dann verarbeitet werden kann? Ich nehme an, das würde funktionieren, aber beachten Sie, dass lange Optionen im GNU-Stil auch Argumente im Format annehmen können --context=foobar
, und Ihre Konstruktion hier unterstützt das nicht. Der Benutzer müsste wissen, dass dieses spezielle Tool hier --context
foobar
zwei verschiedene Argumente erfordert. Oder Sie müssten die Vorverarbeitung komplexer gestalten.
Möglicherweise möchten Sie auch alle Argumente überprüfen, die mit beginnen --
, da sonst beispielsweise ein Tippfehler --cotnext
unverändert bleibt getopts
und Sie Beschwerden über unbekannte Optionen erhalten. (Oder schlimmer noch, es würden falsche Optionen aktiviert.)
Ich frage mich jedoch, ob die Verwendung von
set -- "${aggr[@]}"
korrekt ist.Oder ist Folgendes (mit eval) angemessener?
set -- "${aggr[@]}"
erweitert die Elemente des Arrays in einzelne Wörter und weist diese Wörter dann den Positionsparametern zu. Jedes Array-Element wird ohne Änderungen zu genau einem Positionsparameter.
eval set -- "${aggr[@]}"
würde alle Elemente des Arrays erweitern, sie dann mit Leerzeichen zusammenfügen, voranstellen set --
und das Ergebnis als Shell-Befehl auswerten. Das heißt, wenn Sie die Array-Elemente abc def
, $(date >&2)
, haben ghi'jkl
, wäre der Befehl
set -- abc def $(date >&2) ghi'jkl
abc
Dies würde mit und als zwei unterschiedlichen Parametern enden def
und das Datum würde auf stderr gedruckt, außer dass das einzelne einfache Anführungszeichen einen Syntaxfehler verursachen würde.
Die Verwendung eval
ist angemessen, wenn Sie über etwas verfügen, das für die Erzeugung einer Ausgabe in Anführungszeichen für die Shell-Eingabe konzipiert ist.
Wenn Sie Linux verwenden (und Portabilität nicht wichtig ist), können Sie das tun, was roaima in den Kommentaren vorgeschlagen hat, und die util-linux-Version von getopt
(ohne s
) verwenden. Es unterstützt auch lange Optionen, es gibt Antworten, die zeigen, wie man es ingetopt, getopts oder manuelles Parsen – was verwende ich, wenn ich sowohl kurze als auch lange Optionen unterstützen möchte?und indiese SO-Antwortund auchmeine Antwort hier.
Übrigens, getopt
damitwürdeverwenden eval
, da es als Befehl darauf beschränkt ist, als Ausgabe nur einen einzelnen String zu produzieren und keine Liste wie ein Array. Daher werden Shell-Anführungszeichen verwendet, um das Problem zu umgehen.
Antwort2
Sie können --foo
lange Optionen im -Stil mit dem getopts
integrierten Parser analysieren, indem Sie -
als kurze Option ein Argument für die Optionszeichenfolge hinzufügen und die eigentliche lange Option von abrufen $OPTARG
. Einfaches Beispiel:
while getopts :sc:-: o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument"; continue;;
'?') echo >&2 "unknown option -$OPTARG"; continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; OPTARG=${OPTARG#=};;
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
die Sie dann entweder als script -c foo
oder verwenden können script --context=foo
.
Wenn Sie möchten, dass die langen Optionen genauso wie die kurzen validiert werden und auch abgekürzte Formen akzeptiert werden, benötigen Sie etwas Komplexeres. Es ist nicht sehr sinnvoll, ein so schlechtes Shell-Skript zu überentwickeln, aber wenn Sie ein Beispiel möchten, hier ist es:
short_opts=sc:
long_opts=silent/ch/context:/check/co # those who take an arg END with :
# override via command line for testing purposes
# if [ "$#" -ge 2 ]; then
# short_opts=$1; long_opts=$2; shift 2
# fi
while getopts ":$short_opts-:" o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument" ;continue;;
'?') echo >&2 "bad option -$OPTARG" ;continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; lo=/$long_opts/
case $lo in
*"/$o"[!/:]*"/$o"[!/:]*) echo >&2 "ambiguous option --$o"; continue;;
*"/$o"[:/]*) ;;
*) o=$o${lo#*"/$o"}; o=${o%%[/:]*} ;;
esac
case $lo in
*"/$o/"*) OPTARG= ;;
*"/$o:/"*)
case $OPTARG in
'='*) OPTARG=${OPTARG#=};;
*) eval "OPTARG=\$$OPTIND"
if [ "$OPTIND" -le "$#" ] && [ "$OPTARG" != -- ]; then
OPTIND=$((OPTIND + 1))
else
echo >&2 "option --$o needs an argument"; continue
fi;;
esac;;
*) echo >&2 "unknown option --$o"; continue;;
esac
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
Dann
$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument