Estoy analizando opciones getopts
pero también me gustaría manejar opciones largas.
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 ))
}
Pero me pregunto si el uso de set -- "${aggr[@]}"
es correcto.
¿O es más apropiado lo siguiente (usar eval
)?
eval set -- "${aggr[@]}"
He realizado una prueba que se muestra a continuación. Con eval, la cadena "Gunga Din" se divide, mientras que con set -- "${aggr[@]}"
, se analiza correctamente como una sola cadena.
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|
Luego ejecuté otra función que usa no GNU 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\"" "$@"
}
Esto resultó en lo siguiente
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|
Como se muestra, el resultado de getopt es una entrada única con argumentos posicionales reorganizados. Esto muestra la necesidad de utilizar eval set -- "$opts"
para dividir los argumentos posicionales de la opts
cadena en cinco entradas para el análisis y procesamiento de opciones.
Respuesta1
¿La idea es preprocesar los argumentos y cambiar cada uno --context
de -C
los cuales getopts
luego se pueden procesar? Supongo que eso funcionaría, pero tenga en cuenta que las opciones largas de estilo GNU también pueden tomar argumentos en el formato --context=foobar
y su construcción aquí no lo admite. El usuario necesitaría saber que esta herramienta en particular requiere --context
foobar
dos argumentos distintos. O necesitarías hacer el preprocesamiento más complejo.
También es posible que desee verificar todos los argumentos que comienzan con --
, ya que de lo contrario, por ejemplo, un error escrito --cotnext
quedaría getopts
tal cual y recibiría quejas sobre opciones desconocidas. (O peor aún, se habilitarían opciones incorrectas).
Pero me pregunto si el uso de
set -- "${aggr[@]}"
es correcto.¿O es más apropiado lo siguiente (usando eval)?
set -- "${aggr[@]}"
expande los elementos de la matriz a palabras distintas y luego asigna esas palabras a los parámetros posicionales. Cada elemento de la matriz se convertirá exactamente en un parámetro posicional, sin cambios.
eval set -- "${aggr[@]}"
expandiría todos los elementos de la matriz, luego los uniría con espacios, antepondría set --
y evaluaría el resultado como un comando de shell. Es decir, si tiene los elementos de la matriz ,,, abc def
el comando sería$(date >&2)
ghi'jkl
set -- abc def $(date >&2) ghi'jkl
lo que terminaría con abc
y def
como dos parámetros distintos, e imprimiría la fecha en stderr, excepto que la única comilla simple provocará un error de sintaxis.
Usar eval
sería apropiado si tiene algo diseñado para producir una salida citada para la entrada del shell.
Si está en Linux (y no le importa la portabilidad), puede hacer lo que roaima sugirió en los comentarios y usar la versión util-linux de getopt
(sin s
). También admite opciones largas, hay respuestas que muestran cómo usarlas engetopt, getopts o análisis manual: ¿qué usar cuando quiero admitir opciones cortas y largas?y enesta respuesta SOy tambiénmi respuesta aquí.
Por cierto, con eso getopt
, túharíause eval
, ya que como comando, se limita a producir una sola cadena como salida, no una lista como una matriz, por lo que utiliza comillas de shell para solucionar el problema.
Respuesta2
Puede analizar --foo
opciones largas de estilo con la getopts
función incorporada agregando -
como opción corta, tomando un argumento a la cadena de opciones y recuperando la opción larga real de $OPTARG
. Ejemplo sencillo:
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 $*"
que luego puedes usar como script -c foo
o script --context=foo
.
Si además quieres tener validadas las opciones largas al igual que las cortas, y además aceptar formularios abreviados, necesitas algo más complejo. No hay mucha sabiduría en sobre-diseñar un script de shell pobre como ese, pero si quieres un ejemplo, aquí lo tienes:
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 $*"
entonces
$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument