scripts script resueltos programas pasar parametros español ejercicios ejemplos comandos comando shell unix getopt getopts

resueltos - Uso de getopts en el script bash shell para obtener opciones de línea de comandos largas y cortas



scripts linux ejercicios resueltos (29)

Deseo que se invocen formas largas y cortas de opciones de línea de comandos utilizando mi script de shell.

Sé que se pueden usar getopts , pero como en Perl, no he podido hacer lo mismo con shell.

Cualquier idea sobre cómo se puede hacer, para que pueda usar opciones como:

./shell.sh --copyfile abc.pl /tmp/ ./shell.sh -c abc.pl /tmp/

En lo anterior, ambos comandos significan lo mismo para mi shell, pero al utilizar getopts , ¿no he podido implementar estos?


Usando getopts con opciones y argumentos cortos / largos

Funciona con todas las combinaciones, eG:

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --arguments = longhorn
  • foobar -fA "text shorty" -B --arguments = "text longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Algunas declaraciones para estos ejemplos.

Options=$@ Optnum=$# sfoo=''no '' sbar=''no '' sfoobar=''no '' sbarfoo=''no '' sarguments=''no '' sARG=empty lfoo=''no '' lbar=''no '' lfoobar=''no '' lbarfoo=''no '' larguments=''no '' lARG=empty

Cómo se vería la función de uso

function _usage() { ###### U S A G E : Help and ERROR ###### cat <<EOF foobar $Options $* Usage: foobar <[options]> Options: -b --bar Set bar to yes ($foo) -f --foo Set foo to yes ($bart) -h --help Show this message -A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG) -B --barfoo Set barfoo to yes ($barfoo) -F --foobar Set foobar to yes ($foobar) EOF } [ $# = 0 ] && _usage " >>>>>>>> no options given "

getops con banderas largas / cortas, así como argumentos largos

while getopts '':bfh-A:BF'' OPTION ; do case "$OPTION" in b ) sbar=yes ;; f ) sfoo=yes ;; h ) _usage ;; A ) sarguments=yes;sARG="$OPTARG" ;; B ) sbarfoo=yes ;; F ) sfoobar=yes ;; - ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND eval OPTION="/$$optind" OPTARG=$(echo $OPTION | cut -d''='' -f2) OPTION=$(echo $OPTION | cut -d''='' -f1) case $OPTION in --foo ) lfoo=yes ;; --bar ) lbar=yes ;; --foobar ) lfoobar=yes ;; --barfoo ) lbarfoo=yes ;; --help ) _usage ;; --arguments ) larguments=yes;lARG="$OPTARG" ;; * ) _usage " Long: >>>>>>>> invalid options (long) " ;; esac OPTIND=1 shift ;; ? ) _usage "Short: >>>>>>>> invalid options (short) " ;; esac done

Salida

################################################################## echo "----------------------------------------------------------" echo "RESULT short-foo : $sfoo long-foo : $lfoo" echo "RESULT short-bar : $sbar long-bar : $lbar" echo "RESULT short-foobar : $sfoobar long-foobar : $lfoobar" echo "RESULT short-barfoo : $sbarfoo long-barfoo : $lbarfoo" echo "RESULT short-arguments: $sarguments with Argument = /"$sARG/" long-arguments: $larguments and $lARG"

Combinando lo anterior en un guión cohesivo

#!/bin/bash # foobar: getopts with short and long options AND arguments function _cleanup () { unset -f _usage _cleanup ; return 0 } ## Clear out nested functions on exit trap _cleanup INT EXIT RETURN ###### some declarations for these example ###### Options=$@ Optnum=$# sfoo=''no '' sbar=''no '' sfoobar=''no '' sbarfoo=''no '' sarguments=''no '' sARG=empty lfoo=''no '' lbar=''no '' lfoobar=''no '' lbarfoo=''no '' larguments=''no '' lARG=empty function _usage() { ###### U S A G E : Help and ERROR ###### cat <<EOF foobar $Options $* Usage: foobar <[options]> Options: -b --bar Set bar to yes ($foo) -f --foo Set foo to yes ($bart) -h --help Show this message -A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG) -B --barfoo Set barfoo to yes ($barfoo) -F --foobar Set foobar to yes ($foobar) EOF } [ $# = 0 ] && _usage " >>>>>>>> no options given " ################################################################## ####### "getopts" with: short options AND long options ####### ####### AND short/long arguments ####### while getopts '':bfh-A:BF'' OPTION ; do case "$OPTION" in b ) sbar=yes ;; f ) sfoo=yes ;; h ) _usage ;; A ) sarguments=yes;sARG="$OPTARG" ;; B ) sbarfoo=yes ;; F ) sfoobar=yes ;; - ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND eval OPTION="/$$optind" OPTARG=$(echo $OPTION | cut -d''='' -f2) OPTION=$(echo $OPTION | cut -d''='' -f1) case $OPTION in --foo ) lfoo=yes ;; --bar ) lbar=yes ;; --foobar ) lfoobar=yes ;; --barfoo ) lbarfoo=yes ;; --help ) _usage ;; --arguments ) larguments=yes;lARG="$OPTARG" ;; * ) _usage " Long: >>>>>>>> invalid options (long) " ;; esac OPTIND=1 shift ;; ? ) _usage "Short: >>>>>>>> invalid options (short) " ;; esac done


Aquí hay un ejemplo que realmente usa getopt con opciones largas:

aflag=no bflag=no cargument=none # options may be followed by one colon to indicate they have a required argument if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@") then # something went wrong, getopt will put out an error message for us exit 1 fi set -- $options while [ $# -gt 0 ] do case $1 in -a|--along) aflag="yes" ;; -b|--blong) bflag="yes" ;; # for options with required arguments, an additional shift is required -c|--clong) cargument="$2" ; shift;; (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done


De alguna manera resolví esto:

# A string with command options options=$@ # An array with all the arguments arguments=($options) # Loop index index=0 for argument in $options do # Incrementing index index=`expr $index + 1` # The conditions case $argument in -a) echo "key $argument value ${arguments[index]}" ;; -abc) echo "key $argument value ${arguments[index]}" ;; esac done exit;

¿Estoy siendo tonto o algo así? getopts y getopts son tan confusos.


De otra manera...

# translate long options to short for arg do delim="" case "$arg" in --help) args="${args}-h ";; --verbose) args="${args}-v ";; --config) args="${args}-c ";; # pass through anything else *) [[ "${arg:0:1}" == "-" ]] || delim="/"" args="${args}${delim}${arg}${delim} ";; esac done # reset the translated args eval set -- $args # now we can process with getopt while getopts ":hvc:" opt; do case $opt in h) usage ;; v) VERBOSE=true ;; c) source $OPTARG ;; /?) usage ;; :) echo "option -$OPTARG requires an argument" usage ;; esac done


Eche un vistazo a shFlags que es una biblioteca de shell portátil (es decir, sh, bash, dash, ksh, zsh en Linux, Solaris, etc.).

Hace que agregar nuevos indicadores sea tan simple como agregar una línea a su script, y proporciona una función de uso generado automáticamente.

Aquí hay un simple Hello, world! utilizando shFlag :

#!/bin/sh # source shflags from current directory . ./shflags # define a ''name'' command-line string flag DEFINE_string ''name'' ''world'' ''name to say hello to'' ''n'' # parse the command-line FLAGS "$@" || exit 1 eval set -- "${FLAGS_ARGV}" # say hello echo "Hello, ${FLAGS_name}!"

Para los sistemas operativos que tienen el getopt mejorado que admite opciones largas (por ejemplo, Linux), puede hacer:

$ ./hello_world.sh --name Kate Hello, Kate!

Para el resto, debes usar la opción corta:

$ ./hello_world.sh -n Kate Hello, Kate!

Agregar una nueva bandera es tan simple como agregar una nueva DEFINE_ call .


El comando getopts de getopts sigue siendo, AFAIK, limitado a las opciones de un solo carácter solamente.

Hay (o solía ser) un programa externo getopt que reorganizaría un conjunto de opciones para que fuera más fácil analizar. Usted podría adaptar ese diseño para manejar opciones largas también. Ejemplo de uso:

aflag=no bflag=no flist="" set -- $(getopt abf: "$@") while [ $# -gt 0 ] do case "$1" in (-a) aflag=yes;; (-b) bflag=yes;; (-f) flist="$flist $2"; shift;; (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done # Process remaining non-option arguments ...

Podría usar un esquema similar con un comando getoptlong .

Tenga en cuenta que la debilidad fundamental del programa externo getopt es la dificultad de manejar los argumentos con espacios en ellos y de preservar esos espacios con precisión. Esta es la razón por la cual el getopts es superior, aunque limitado por el hecho de que solo maneja opciones de una sola letra.


El incorporado bash getopts no admite nombres largos de opción con el prefijo de doble guión. Solo soporta opciones de un solo carácter.

Hay una herramienta de shell getopt que es otro programa, no un basin builtin. La implementación de GNU de getopt(3) (utilizada por la línea de comandos getopt(1) en Linux) admite el análisis de opciones largas.

Pero la implementación BSD de getopt (por ejemplo, en Mac OS X) no admite opciones largas.

Algunas otras respuestas muestran una solución para usar los elementos getopts bash para imitar opciones largas. Esa solución realmente hace una opción corta cuyo carácter es "-". Así que obtienes "-" como la bandera. Luego, todo lo que sigue se convierte en OPTARG, y prueba el OPTARG con un case anidado.

Esto es inteligente, pero viene con advertencias:

  • getopts no puede hacer cumplir la especificación de opción. No puede devolver errores si el usuario proporciona una opción no válida. Debe realizar su propia comprobación de errores al analizar OPTARG.
  • OPTARG se usa para el nombre largo de la opción, lo que complica el uso cuando su propia opción larga tiene un argumento. Usted termina teniendo que codificar eso como un caso adicional.

Entonces, si bien es posible escribir más código para evitar la falta de soporte para opciones largas, esto es mucho más trabajo y anula parcialmente el propósito de usar un analizador getopt para simplificar su código.


La función incorporada Bop getopts se puede usar para analizar opciones largas colocando un carácter de guión seguido de dos puntos en optspec:

#!/usr/bin/env bash optspec=":hv-:" while getopts "$optspec" optchar; do case "${optchar}" in -) case "${OPTARG}" in loglevel) val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) echo "Parsing option: ''--${OPTARG}'', value: ''${val}''" >&2; ;; loglevel=*) val=${OPTARG#*=} opt=${OPTARG%=$val} echo "Parsing option: ''--${opt}'', value: ''${val}''" >&2 ;; *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 fi ;; esac;; h) echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2 exit 2 ;; v) echo "Parsing option: ''-${optchar}''" >&2 ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: ''-${OPTARG}''" >&2 fi ;; esac done

Después de copiar al archivo ejecutable nombre = getopts_test.sh en el directorio de trabajo actual , se puede producir una salida como

$ ./getopts_test.sh $ ./getopts_test.sh -f Non-option argument: ''-f'' $ ./getopts_test.sh -h usage: code/getopts_test.sh [-v] [--loglevel[=]<value>] $ ./getopts_test.sh --help $ ./getopts_test.sh -v Parsing option: ''-v'' $ ./getopts_test.sh --very-bad $ ./getopts_test.sh --loglevel Parsing option: ''--loglevel'', value: '''' $ ./getopts_test.sh --loglevel 11 Parsing option: ''--loglevel'', value: ''11'' $ ./getopts_test.sh --loglevel=11 Parsing option: ''--loglevel'', value: ''11''

Obviamente, getopts no realiza la comprobación OPTERR ni el análisis de argumentos de opción para las opciones largas. El fragmento de script anterior muestra cómo se puede hacer esto manualmente. El principio básico también funciona en el shell de Debian Almquist ("dash"). Tenga en cuenta el caso especial:

getopts -- "-:" ## without the option terminator "-- " bash complains about "-:" getopts "-:" ## this works in the Debian Almquist shell ("dash")

Tenga en cuenta que, como señala GreyCat en http://mywiki.wooledge.org/BashFAQ , este truco explota un comportamiento no estándar del shell que permite la opción-argumento (es decir, el nombre de archivo en "-f nombre de archivo") para concatenar a la opción (como en "-filename"). El estándar POSIX dice que debe haber un espacio entre ellos, que en el caso de "- longoption" terminaría el análisis de opciones y convertiría todas las opciones en argumentos no opcionales.


Las opciones largas pueden ser analizadas por el estándar getopts incorporado como "argumentos" a la - "opción"

Este es un shell POSIX portátil y nativo, no se necesitan programas externos ni bashismos.

Esta guía implementa opciones largas como argumentos para la opción - , por lo que --alpha es vista por getopts como - con argumento alpha y --bravo=foo se ve como - con argumento bravo=foo . El verdadero argumento se puede recopilar con un simple reemplazo: ${OPTARG#*=} .

En este ejemplo, -b (y su forma larga, --bravo ) tiene una opción obligatoria (tenga en cuenta la reconstrucción manual de hacer cumplir eso para la forma larga). Las opciones no booleanas para los argumentos largos vienen después de los signos de igual, por ejemplo, --bravo=foo (sería difícil implementar delimitadores de espacio para opciones largas).

Debido a que este utiliza getopts , esta solución admite el uso como cmd -ac --bravo=foo -d FILE (que tiene opciones combinadas -a y - c e intercala opciones largas con opciones estándar) mientras que la mayoría de las demás respuestas aquí no tienen éxito. ese.

while getopts ab:c-: arg; do case $arg in a ) ARG_A=true ;; b ) ARG_B="$OPTARG" ;; c ) ARG_C=true ;; - ) LONG_OPTARG="${OPTARG#*=}" case $OPTARG in alpha ) ARG_A=true ;; bravo=?* ) ARG_B="$LONG_OPTARG" ;; bravo* ) echo "No arg for --$OPTARG option" >&2; exit 2 ;; charlie ) ARG_C=true ;; alpha* | charlie* ) echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;; '''' ) break ;; # "--" terminates argument processing * ) echo "Illegal option --$OPTARG" >&2; exit 2 ;; esac ;; /? ) exit 2 ;; # getopts already reported the illegal option esac done shift $((OPTIND-1)) # remove parsed options and args from $@ list

Cuando el argumento es un guión ( - ), tiene dos componentes más: el nombre del indicador y (opcionalmente) su argumento. Los delimito como lo haría cualquier comando, con el primer signo igual ( = ). $LONG_OPTARG es, por lo tanto, simplemente el contenido de $OPTARG sin el nombre del indicador o el signo igual.

El case interior implementa opciones largas manualmente, por lo que necesita un poco de mantenimiento:

  • bravo=? coincidencias --bravo=foo pero no --bravo= (nota: el case detiene después de la primera coincidencia)
  • bravo* sigue, notando el argumento requerido que falta en --bravo y --bravo=
  • alpha* | charlie* alpha* | charlie* captura los argumentos dados a las opciones que no los admiten
  • '''' está presente para admitir no opciones que comienzan con guiones
  • * atrapa todas las demás opciones largas y recrea el error generado por getopts para una opción no válida

No necesariamente necesitas todos esos artículos de limpieza. Por ejemplo, tal vez desee que --bravo tenga un argumento opcional (que -b no puede admitir debido a una limitación en getopts ). Simplemente quitar el =? y el caso de falla relacionado y luego llame a ${ARG_B:=$DEFAULT_ARG_B} la primera vez que use $ARG_B .


EasyOptions maneja opciones cortas y largas:

## Options: ## --verbose, -v Verbose mode ## --logfile=NAME Log filename source easyoptions || exit if test -n "${verbose}"; then echo "log file: ${logfile}" echo "arguments: ${arguments[@]}" fi


getopt y getopts son bestias diferentes, y la gente parece tener un poco de malentendido sobre lo que hacen. getopts es un comando incorporado a bash para procesar las opciones de línea de comandos en un bucle y asignar cada opción y valor encontrados a su vez a las variables incorporadas, para que pueda procesarlas aún más. getopt , sin embargo, es un programa de utilidad externo, y en realidad no procesa sus opciones como lo hace, por ejemplo, bash getopts , el módulo Perl Getopt o los módulos optparse / argparse Python. Todo lo que getopt hace es canonizar las opciones que se pasan, es decir, convertirlas a un formato más estándar, de modo que sea más fácil para un script de shell procesarlas. Por ejemplo, una aplicación de getopt podría convertir lo siguiente:

myscript -ab infile.txt -ooutfile.txt

dentro de esto:

myscript -a -b -o outfile.txt infile.txt

Tienes que hacer el procesamiento real a ti mismo. No tiene que usar getopt en absoluto si hace varias restricciones en la forma en que puede especificar opciones:

  • solo pon una opción por argumento;
  • todas las opciones van antes que cualquier parámetro posicional (es decir, argumentos sin opción);
  • para las opciones con valores (por ejemplo, -o arriba), el valor debe ir como un argumento separado (después de un espacio).

¿Por qué usar getopt lugar de getopts ? La razón básica es que solo GNU getopt le brinda soporte para las opciones de línea de comandos con nombre largo. 1 (GNU getopt es el valor predeterminado en Linux. Mac OS X y FreeBSD vienen con un getopt básico y no muy útil, pero la versión GNU puede instalarse; consulte a continuación).

Por ejemplo, aquí hay un ejemplo del uso de GNU getopt , de un script mío llamado javawrap :

# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this # separately; see below. TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: / -n ''javawrap'' -- "$@"` if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi # Note the quotes around `$TEMP'': they are essential! eval set -- "$TEMP" VERBOSE=false DEBUG=false MEMORY= DEBUGFILE= JAVA_MISC_OPT= while true; do case "$1" in -v | --verbose ) VERBOSE=true; shift ;; -d | --debug ) DEBUG=true; shift ;; -m | --memory ) MEMORY="$2"; shift 2 ;; --debugfile ) DEBUGFILE="$2"; shift 2 ;; --minheap ) JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;; --maxheap ) JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done

Esto le permite especificar opciones como --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt" o similar. El efecto de la llamada a getopt es getopt las opciones para --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt" para que pueda procesar más fácilmente ellos. La cotización en torno a "$1" y "$2" es importante ya que garantiza que los argumentos con espacios en ellos se manejen correctamente.

Si elimina las primeras 9 líneas (todo hasta la línea de eval set ), ¡el código seguirá funcionando ! Sin embargo, su código será mucho más selectivo en el tipo de opciones que acepta: en particular, deberá especificar todas las opciones en el formulario "canónico" descrito anteriormente. Sin embargo, con el uso de getopt , puede agrupar opciones de una sola letra, usar formas más cortas no ambiguas de opciones largas, usar el estilo --file foo.txt o --file=foo.txt , usar el -m 4096 o estilo -m4096 , opciones de mezcla y no opciones en cualquier orden, etc. getopt también getopt un mensaje de error si se encuentran opciones no reconocidas o ambiguas.

NOTA : En realidad, hay dos versiones totalmente diferentes de getopt , getopt básico y getopt GNU, con características diferentes y convenciones de llamada diferentes. 2 El getopt básico está bastante roto: no solo no maneja opciones largas, sino que tampoco puede manejar espacios incrustados dentro de argumentos o argumentos vacíos, mientras que getopts hace esto correctamente. El código anterior no funcionará en getopt básico. GNU getopt se instala de forma predeterminada en Linux, pero en Mac OS X y FreeBSD debe instalarse por separado. En Mac OS X, instale MacPorts ( http://www.macports.org ) y luego sudo port install getopt para instalar GNU getopt (generalmente en /opt/local/bin ), y asegúrese de que /opt/local/bin está en su ruta de shell delante de /usr/bin . En FreeBSD, instale misc/getopt .

Una guía rápida para modificar el código de ejemplo para su propio programa: De las primeras líneas, todo es "repetitivo" que debería permanecer igual, excepto la línea que llama a getopt . Debería cambiar el nombre del programa después de -n , especificar opciones cortas después de -o , y opciones largas después de --long . Ponga dos puntos después de las opciones que toman un valor.

Finalmente, si ve código que acaba de set lugar de eval set , se escribió para BSD getopt . Debería cambiarlo para usar el estilo del eval set , que funciona bien con ambas versiones de getopt , mientras que el set simple no funciona bien con GNU getopt .

1 En realidad, getopts en ksh93 admite opciones con nombre largo, pero este shell no se usa tan a menudo como bash . En zsh , use zparseopts para obtener esta funcionalidad.

2 Técnicamente, "GNU getopt " es un nombre inapropiado; esta versión en realidad fue escrita para Linux en lugar del proyecto GNU. Sin embargo, sigue todas las convenciones de GNU, y el término "GNU getopt " se usa comúnmente (por ejemplo, en FreeBSD).


Lasgetopts opciones cortas incorporadas solo se analizan (excepto en ksh93), pero aún puede agregar algunas líneas de secuencias de comandos para que getopts maneje opciones largas.

Aquí hay una parte del código que se encuentra en http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

#== set short options ==# SCRIPT_OPTS='':fbF:B:-:h'' #== set long options associated with short one ==# typeset -A ARRAY_OPTS ARRAY_OPTS=( [foo]=f [bar]=b [foobar]=F [barfoo]=B [help]=h [man]=h ) #== parse options ==# while getopts ${SCRIPT_OPTS} OPTION ; do #== translate long options to short ==# if [[ "x$OPTION" == "x-" ]]; then LONG_OPTION=$OPTARG LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d''='' -f2) LONG_OPTIND=-1 [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d''='' -f1) [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="/$$LONG_OPTIND" OPTION=${ARRAY_OPTS[$LONG_OPTION]} [[ "x$OPTION" = "x" ]] && OPTION="?" OPTARG="-$LONG_OPTION" if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then OPTION=":" OPTARG="-$LONG_OPTION" else OPTARG="$LONG_OPTARG"; if [[ $LONG_OPTIND -ne -1 ]]; then [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 )) shift $OPTIND OPTIND=1 fi fi fi fi #== options follow by another option instead of argument ==# if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then OPTARG="$OPTION" OPTION=":" fi #== manage options ==# case "$OPTION" in f ) foo=1 bar=0 ;; b ) foo=0 bar=1 ;; B ) barfoo=${OPTARG} ;; F ) foobar=1 && foobar_name=${OPTARG} ;; h ) usagefull && exit 0 ;; : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;; ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;; esac done shift $((${OPTIND} - 1))

Aquí hay una prueba:

# Short options test $ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2 foo=0 bar=1 barfoo=6 foobar=1 foobar_name=Hello world files=file1 file2 # Long and short options test $ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2 foo=0 bar=1 barfoo=6 foobar=1 foobar_name=Hello files=file1 file2

De lo contrario, en el reciente Korn Shell ksh93, getoptsnaturalmente , se pueden analizar opciones largas e incluso mostrar una página de manual por igual. (Consulte http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )


Inventando otra versión de la rueda ...

Esta función es (con suerte) un reemplazo de shell bourne simple compatible con POSIX para GNU getopt. Es compatible con opciones cortas / largas que pueden aceptar argumentos obligatorios / opcionales / no, y la forma en que se especifican las opciones es casi idéntica a la de GNU getopt, por lo que la conversión es trivial.

Por supuesto, esto sigue siendo una parte considerable del código para colocar en un script, pero es aproximadamente la mitad de las líneas de la conocida función de shell getopt_long, y puede ser preferible en los casos en los que solo desee reemplazar los usos existentes de GNU getopt.

Este es un código bastante nuevo, así que YMMV (y definitivamente hágamelo saber si este no es en realidad compatible con POSIX por alguna razón, la portabilidad fue la intención desde el principio, pero no tengo un entorno de prueba POSIX útil).

Código y ejemplo de uso a continuación:

#!/bin/sh # posix_getopt shell function # Author: Phil S. # Version: 1.0 # Created: 2016-07-05 # URL: http://.com/a/37087374/324105 # POSIX-compatible argument quoting and parameter save/restore # http://www.etalabs.net/sh_tricks.html # Usage: # parameters=$(save "$@") # save the original parameters. # eval "set -- ${parameters}" # restore the saved parameters. save () { local param for param; do printf %s//n "$param" / | sed "s/''/''////'''/g;1s/^/''/;/$s//$/'' /////" done printf %s//n " " } # Exit with status $1 after displaying error message $2. exiterr () { printf %s//n "$2" >&2 exit $1 } # POSIX-compatible command line option parsing. # This function supports long options and optional arguments, and is # a (largely-compatible) drop-in replacement for GNU getopt. # # Instead of: # opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@") # eval set -- ${opts} # # We instead use: # opts=$(posix_getopt "$shortopts" "$longopts" "$@") # eval "set -- ${opts}" posix_getopt () { # args: "$shortopts" "$longopts" "$@" local shortopts longopts / arg argtype getopt nonopt opt optchar optword suffix shortopts="$1" longopts="$2" shift 2 getopt= nonopt= while [ $# -gt 0 ]; do opt= arg= argtype= case "$1" in # ''--'' means don''t parse the remaining options ( -- ) { getopt="${getopt}$(save "$@")" shift $# break };; # process short option ( -[!-]* ) { # -x[foo] suffix=${1#-?} # foo opt=${1%$suffix} # -x optchar=${opt#-} # x case "${shortopts}" in ( *${optchar}::* ) { # optional argument argtype=optional arg="${suffix}" shift };; ( *${optchar}:* ) { # required argument argtype=required if [ -n "${suffix}" ]; then arg="${suffix}" shift else case "$2" in ( -* ) exiterr 1 "$1 requires an argument";; ( ?* ) arg="$2"; shift 2;; ( * ) exiterr 1 "$1 requires an argument";; esac fi };; ( *${optchar}* ) { # no argument argtype=none arg= shift # Handle multiple no-argument parameters combined as # -xyz instead of -x -y -z. If we have just shifted # parameter -xyz, we now replace it with -yz (which # will be processed in the next iteration). if [ -n "${suffix}" ]; then eval "set -- $(save "-${suffix}")$(save "$@")" fi };; ( * ) exiterr 1 "Unknown option $1";; esac };; # process long option ( --?* ) { # --xarg[=foo] suffix=${1#*=} # foo (unless there was no =) if [ "${suffix}" = "$1" ]; then suffix= fi opt=${1%=$suffix} # --xarg optword=${opt#--} # xarg case ",${longopts}," in ( *,${optword}::,* ) { # optional argument argtype=optional arg="${suffix}" shift };; ( *,${optword}:,* ) { # required argument argtype=required if [ -n "${suffix}" ]; then arg="${suffix}" shift else case "$2" in ( -* ) exiterr 1 / "--${optword} requires an argument";; ( ?* ) arg="$2"; shift 2;; ( * ) exiterr 1 / "--${optword} requires an argument";; esac fi };; ( *,${optword},* ) { # no argument if [ -n "${suffix}" ]; then exiterr 1 "--${optword} does not take an argument" fi argtype=none arg= shift };; ( * ) exiterr 1 "Unknown option $1";; esac };; # any other parameters starting with - ( -* ) exiterr 1 "Unknown option $1";; # remember non-option parameters ( * ) nonopt="${nonopt}$(save "$1")"; shift;; esac if [ -n "${opt}" ]; then getopt="${getopt}$(save "$opt")" case "${argtype}" in ( optional|required ) { getopt="${getopt}$(save "$arg")" };; esac fi done # Generate function output, suitable for: # eval "set -- $(posix_getopt ...)" printf %s "${getopt}" if [ -n "${nonopt}" ]; then printf %s "$(save "--")${nonopt}" fi }

Ejemplo de uso:

# Process command line options shortopts="hvd:c::s::L:D" longopts="help,version,directory:,client::,server::,load:,delete" #opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@") opts=$(posix_getopt "$shortopts" "$longopts" "$@") if [ $? -eq 0 ]; then #eval set -- ${opts} eval "set -- ${opts}" while [ $# -gt 0 ]; do case "$1" in ( -- ) shift; break;; ( -h|--help ) help=1; shift; break;; ( -v|--version ) version_help=1; shift; break;; ( -d|--directory ) dir=$2; shift 2;; ( -c|--client ) useclient=1; client=$2; shift 2;; ( -s|--server ) startserver=1; server_name=$2; shift 2;; ( -L|--load ) load=$2; shift 2;; ( -D|--delete ) delete=1; shift;; esac done else shorthelp=1 # getopt returned (and reported) an error. fi


Para mantener la compatibilidad con varias plataformas y evitar la dependencia de ejecutables externos, porté algo de código de otro idioma.

Me parece muy fácil de usar, aquí hay un ejemplo:

ArgParser::addArg "[h]elp" false "This list" ArgParser::addArg "[q]uiet" false "Supress output" ArgParser::addArg "[s]leep" 1 "Seconds to sleep" ArgParser::addArg "v" 1 "Verbose mode" ArgParser::parse "$@" ArgParser::isset help && ArgParser::showArgs ArgParser::isset "quiet" / && echo "Quiet!" / || echo "Noisy!" local __sleep ArgParser::tryAndGetArg sleep into __sleep / && echo "Sleep for $__sleep seconds" / || echo "No value passed for sleep" # This way is often more convienient, but is a little slower echo "Sleep set to: $( ArgParser::getArg sleep )"

El BASH requerido es un poco más largo de lo que podría ser, pero quería evitar confiar en las matrices asociativas de BASH 4. También puede descargar esto directamente desde http://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash # Updates to this script may be found at # http://nt4.com/bash/argparser.inc.sh # Example of runtime usage: # mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com # Example of use in script (see bottom) # Just include this file in yours, or use # source argparser.inc.sh unset EXPLODED declare -a EXPLODED function explode { local c=$# (( c < 2 )) && { echo function "$0" is missing parameters return 1 } local delimiter="$1" local string="$2" local limit=${3-99} local tmp_delim=$''/x07'' local delin=${string//$delimiter/$tmp_delim} local oldifs="$IFS" IFS="$tmp_delim" EXPLODED=($delin) IFS="$oldifs" } # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference # Usage: local "$1" && upvar $1 "value(s)" upvar() { if unset -v "$1"; then # Unset & validate varname if (( $# == 2 )); then eval $1=/"/$2/" # Return single value else eval $1=/(/"/${@:2}/"/) # Return array fi fi } function decho { : } function ArgParser::check { __args=${#__argparser__arglist[@]} for (( i=0; i<__args; i++ )) do matched=0 explode "|" "${__argparser__arglist[$i]}" if [ "${#1}" -eq 1 ] then if [ "${1}" == "${EXPLODED[0]}" ] then decho "Matched $1 with ${EXPLODED[0]}" matched=1 break fi else if [ "${1}" == "${EXPLODED[1]}" ] then decho "Matched $1 with ${EXPLODED[1]}" matched=1 break fi fi done (( matched == 0 )) && return 2 # decho "Key $key has default argument of ${EXPLODED[3]}" if [ "${EXPLODED[3]}" == "false" ] then return 0 else return 1 fi } function ArgParser::set { key=$3 value="${1:-true}" declare -g __argpassed__$key="$value" } function ArgParser::parse { unset __argparser__argv __argparser__argv=() # echo parsing: "$@" while [ -n "$1" ] do # echo "Processing $1" if [ "${1:0:2}" == ''--'' ] then key=${1:2} value=$2 elif [ "${1:0:1}" == ''-'' ] then key=${1:1} # Strip off leading - value=$2 else decho "Not argument or option: ''$1''" >& 2 __argparser__argv+=( "$1" ) shift continue fi # parameter=${tmp%%=*} # Extract name. # value=${tmp##*=} # Extract value. decho "Key: ''$key'', value: ''$value''" # eval $parameter=$value ArgParser::check $key el=$? # echo "Check returned $el for $key" [ $el -eq 2 ] && decho "No match for option ''$1''" >&2 # && __argparser__argv+=( "$1" ) [ $el -eq 0 ] && decho "Matched option ''${EXPLODED[2]}'' with no arguments" >&2 && ArgParser::set true "${EXPLODED[@]}" [ $el -eq 1 ] && decho "Matched option ''${EXPLODED[2]}'' with an argument of ''$2''" >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift shift done } function ArgParser::isset { declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0 return 1 } function ArgParser::getArg { # This one would be a bit silly, since we can only return non-integer arguments ineffeciently varname="__argpassed__$1" echo "${!varname}" } ## # usage: tryAndGetArg <argname> into <varname> # returns: 0 on success, 1 on failure function ArgParser::tryAndGetArg { local __varname="__argpassed__$1" local __value="${!__varname}" test -z "$__value" && return 1 local "$3" && upvar $3 "$__value" return 0 } function ArgParser::__construct { unset __argparser__arglist # declare -a __argparser__arglist } ## # @brief add command line argument # @param 1 short and/or long, eg: [s]hort # @param 2 default value # @param 3 description ## function ArgParser::addArg { # check for short arg within long arg if [[ "$1" =~ /[(.)/] ]] then short=${BASH_REMATCH[1]} long=${1//[$short/]/$short} else long=$1 fi if [ "${#long}" -eq 1 ] then short=$long long='''' fi decho short: "$short" decho long: "$long" __argparser__arglist+=("$short|$long|$1|$2|$3") } ## # @brief show available command line arguments ## function ArgParser::showArgs { # declare -p | grep argparser printf "Usage: %s [OPTION...]/n/n" "$( basename "${BASH_SOURCE[0]}" )" printf "Defaults for the options are specified in brackets./n/n"; __args=${#__argparser__arglist[@]} for (( i=0; i<__args; i++ )) do local shortname= local fullname= local default= local description= local comma= explode "|" "${__argparser__arglist[$i]}" shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html test -n "$shortname" / && test -n "$fullname" / && comma="," default="${EXPLODED[3]}" case $default in false ) default= ;; "" ) default= ;; * ) default="[$default]" esac description="${EXPLODED[4]}" printf " %2s%1s %-19s %s %s/n" "$shortname" "$comma" "$fullname" "$description" "$default" done } function ArgParser::test { # Arguments with a default of ''false'' do not take paramaters (note: default # values are not applied in this release) ArgParser::addArg "[h]elp" false "This list" ArgParser::addArg "[q]uiet" false "Supress output" ArgParser::addArg "[s]leep" 1 "Seconds to sleep" ArgParser::addArg "v" 1 "Verbose mode" ArgParser::parse "$@" ArgParser::isset help && ArgParser::showArgs ArgParser::isset "quiet" / && echo "Quiet!" / || echo "Noisy!" local __sleep ArgParser::tryAndGetArg sleep into __sleep / && echo "Sleep for $__sleep seconds" / || echo "No value passed for sleep" # This way is often more convienient, but is a little slower echo "Sleep set to: $( ArgParser::getArg sleep )" echo "Remaining command line: ${__argparser__argv[@]}" } if [ "$( basename "$0" )" == "argparser.inc.sh" ] then ArgParser::test "$@" fi


Si todas sus opciones largas tienen los primeros caracteres únicos y coincidentes como las opciones cortas, por ejemplo

./slamm --chaos 23 --plenty test -quiet

Es lo mismo que

./slamm -c 23 -p test -q

Puedes usar esto antes de getopts para reescribir $ args:

# change long options to short options for arg; do [[ "${arg:0:1}" == "-" ]] && delim="" || delim="/"" if [ "${arg:0:2}" == "--" ]; then args="${args} -${arg:2:1}" else args="${args} ${delim}${arg}${delim}" fi done # reset the incoming args eval set -- $args # proceed as usual while getopts ":b:la:h" OPTION; do .....

Gracias por mtvee por la inspiración ;-)


Solo escribo shell scripts de vez en cuando y quedo fuera de práctica, por lo que cualquier comentario es apreciado.

Usando la estrategia propuesta por @Arvid Requate, notamos algunos errores de los usuarios. Un usuario que olvida incluir un valor accidentalmente tendrá el nombre de la siguiente opción tratado como un valor:

./getopts_test.sh --loglevel= --toc=TRUE

hará que el valor de "loglevel" se vea como "--toc = TRUE". Esto se puede evitar.

Adapté algunas ideas sobre cómo verificar el error del usuario para CLI en http://mwiki.wooledge.org/BashFAQ/035 discusión sobre el análisis manual. Inserté el control de errores en el manejo de los argumentos "-" y "-".

Luego comencé a juguetear con la sintaxis, por lo que cualquier error aquí es estrictamente mi culpa, no de los autores originales.

Mi enfoque ayuda a los usuarios que prefieren ingresar por mucho tiempo con o sin el signo igual. Es decir, debería tener la misma respuesta a "--loglevel 9" como "--loglevel = 9". En el método - / space, no es posible saber con seguridad si el usuario olvida un argumento, por lo que es necesario adivinarlo.

  1. si el usuario tiene el formato de signo largo / igual (--opt =), un espacio después de = desencadena un error porque no se proporcionó un argumento.
  2. Si el usuario tiene argumentos largos / espaciales (--opt), esta secuencia de comandos genera un error si no sigue ningún argumento (final del comando) o si el argumento comienza con un guión

En caso de que esté comenzando con esto, hay una diferencia interesante entre los formatos "--opt = value" y "--opt value". Con el signo igual, el argumento de la línea de comando se ve como "opt = valor" y el trabajo a manejar es el análisis de cadenas, para separarse en el "=". En contraste, con "--opt value", el nombre del argumento es "opt" y tenemos el desafío de obtener el siguiente valor suministrado en la línea de comandos. Ahí es donde @Arvid Requate usó $ {! OPTIND}, la referencia indirecta. Aún no entiendo eso, bueno, en absoluto, y los comentarios en BashFAQ parecen advertir contra ese estilo ( http://mywiki.wooledge.org/BashFAQ/006 ). Por cierto, no creo que los comentarios del póster anterior sobre la importancia de OPTIND = $ (($ OPTIND + 1)) sean correctos. Quiero decir,No veo ningún daño por omitirlo.

En la versión más reciente de este script, flag -v significa una impresión VERBOSE.

Guárdelo en un archivo llamado "cli-5.sh", haga ejecutable, y cualquiera de estos funcionará, o fallará de la manera deseada

./cli-5.sh -v --loglevel=44 --toc TRUE ./cli-5.sh -v --loglevel=44 --toc=TRUE ./cli-5.sh --loglevel 7 ./cli-5.sh --loglevel=8 ./cli-5.sh -l9 ./cli-5.sh --toc FALSE --loglevel=77 ./cli-5.sh --toc=FALSE --loglevel=77 ./cli-5.sh -l99 -t yyy ./cli-5.sh -l 99 -t yyy

Aquí hay un ejemplo de salida de la comprobación de errores en el usuario intpu

$ ./cli-5.sh --toc --loglevel=77 ERROR: toc value must not have dash at beginning $ ./cli-5.sh --toc= --loglevel=77 ERROR: value for toc undefined

Debería considerar activar -v, ya que imprime las partes internas de OPTIND y OPTARG

#/usr/bin/env bash ## Paul Johnson ## 20171016 ## ## Combines ideas from ## https://.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options ## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035 # What I don''t understand yet: # In @Arvid REquate''s answer, we have # val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) # this works, but I don''t understand it! die() { printf ''%s/n'' "$1" >&2 exit 1 } printparse(){ if [ ${VERBOSE} -gt 0 ]; then printf ''Parse: %s%s%s/n'' "$1" "$2" "$3" >&2; fi } showme(){ if [ ${VERBOSE} -gt 0 ]; then printf ''VERBOSE: %s/n'' "$1" >&2; fi } VERBOSE=0 loglevel=0 toc="TRUE" optspec=":vhl:t:-:" while getopts "$optspec" OPTCHAR; do showme "OPTARG: ${OPTARG[*]}" showme "OPTIND: ${OPTIND[*]}" case "${OPTCHAR}" in -) case "${OPTARG}" in loglevel) #argument has no equal sign opt=${OPTARG} val="${!OPTIND}" ## check value. If negative, assume user forgot value showme "OPTIND is {$OPTIND} {!OPTIND} has value /"${!OPTIND}/"" if [[ "$val" == -* ]]; then die "ERROR: $opt value must not have dash at beginning" fi ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect? printparse "--${OPTARG}" " " "${val}" loglevel="${val}" shift ;; loglevel=*) #argument has equal sign opt=${OPTARG%=*} val=${OPTARG#*=} if [ "${OPTARG#*=}" ]; then printparse "--${opt}" "=" "${val}" loglevel="${val}" ## shift CAUTION don''t shift this, fails othewise else die "ERROR: $opt value must be supplied" fi ;; toc) #argument has no equal sign opt=${OPTARG} val="${!OPTIND}" ## check value. If negative, assume user forgot value showme "OPTIND is {$OPTIND} {!OPTIND} has value /"${!OPTIND}/"" if [[ "$val" == -* ]]; then die "ERROR: $opt value must not have dash at beginning" fi ## OPTIND=$(( $OPTIND + 1 )) #?? printparse "--${opt}" " " "${val}" toc="${val}" shift ;; toc=*) #argument has equal sign opt=${OPTARG%=*} val=${OPTARG#*=} if [ "${OPTARG#*=}" ]; then toc=${val} printparse "--$opt" " -> " "$toc" ##shift ## NO! dont shift this else die "ERROR: value for $opt undefined" fi ;; help) echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2 exit 2 ;; *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 fi ;; esac;; h|-/?|--help) ## must rewrite this for all of the arguments echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2 exit 2 ;; l) loglevel=${OPTARG} printparse "-l" " " "${loglevel}" ;; t) toc=${OPTARG} ;; v) VERBOSE=1 ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: ''-${OPTARG}''" >&2 fi ;; esac done echo " After Parsing values " echo "loglevel $loglevel" echo "toc $toc"


Tal vez sea más sencillo usar ksh, solo para la parte de getopts, si necesita opciones de línea de comandos largas, ya que puede hacerse más fácilmente allí.

# Working Getopts Long => KSH #! /bin/ksh # Getopts Long USAGE="s(showconfig)" USAGE+="c:(createdb)" USAGE+="l:(createlistener)" USAGE+="g:(generatescripts)" USAGE+="r:(removedb)" USAGE+="x:(removelistener)" USAGE+="t:(createtemplate)" USAGE+="h(help)" while getopts "$USAGE" optchar ; do case $optchar in s) echo "Displaying Configuration" ;; c) echo "Creating Database $OPTARG" ;; l) echo "Creating Listener LISTENER_$OPTARG" ;; g) echo "Generating Scripts for Database $OPTARG" ;; r) echo "Removing Database $OPTARG" ;; x) echo "Removing Listener LISTENER_$OPTARG" ;; t) echo "Creating Database Template" ;; h) echo "Help" ;; esac done


Una solución mejorada:

# translate long options to short # Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after "--" in option fields. for ((i=1;$#;i++)) ; do case "$1" in --) # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions... EndOpt=1 ;;& --version) ((EndOpt)) && args[$i]="$1" || args[$i]="-V";; # default case : short option use the first char of the long option: --?*) ((EndOpt)) && args[$i]="$1" || args[$i]="-${1:2:1}";; # pass through anything else: *) args[$i]="$1" ;; esac shift done # reset the translated args set -- "${args[@]}" function usage { echo "Usage: $0 [options] files" >&2 exit $1 } # now we can process with getopt while getopts ":hvVc:" opt; do case $opt in h) usage ;; v) VERBOSE=true ;; V) echo $Version ; exit ;; c) source $OPTARG ;; /?) echo "unrecognized option: -$opt" ; usage -1 ;; :) echo "option -$OPTARG requires an argument" usage -1 ;; esac done shift $((OPTIND-1)) [[ "$1" == "--" ]] && shift


getopts "podría usarse" para analizar opciones largas siempre que no espere que tengan argumentos ...

Aquí está cómo:

$ cat > longopt while getopts ''e:-:'' OPT; do case $OPT in e) echo echo: $OPTARG;; -) #long option case $OPTARG in long-option) echo long option;; *) echo long option: $OPTARG;; esac;; esac done $ bash longopt -e asd --long-option --long1 --long2 -e test echo: asd long option long option: long1 long option: long2 echo: test

Si intenta usar OPTIND para obtener un parámetro para la opción larga, getopts lo tratará como el primer parámetro posicional no opcional y dejará de analizar cualquier otro parámetro. En tal caso, será mejor que lo maneje manualmente con una simple declaración de caso.

Esto "siempre" funcionará:

$ cat >longopt2 while (($#)); do OPT=$1 shift case $OPT in --*) case ${OPT:2} in long1) echo long1 option;; complex) echo comples with argument $1; shift;; esac;; -*) case ${OPT:1} in a) echo short option a;; b) echo short option b with parameter $1; shift;; esac;; esac done $ bash longopt2 --complex abc -a --long -b test comples with argument abc short option a short option b with parameter test

Aunque no es tan flexible como Getopts y usted tiene que hacer gran parte del código de comprobación de errores en las instancias del caso ...

Pero es una opción.


hm

No estoy realmente satisfecho con las opciones puras de bash. ¿Por qué no usar Perl para obtener lo que quieres? Analice directamente la matriz $ * y asigne un nombre automático a sus opciones.

script de ayuda simple:

#!/usr/bin/perl use Getopt::Long; my $optstring = shift; my @opts = split(m#,#, $optstring); my %opt; GetOptions(/%opt, @opts); print "set -- " . join('' '', map("''$_''", @ARGV)) . ";"; my $xx; my $key; foreach $key (keys(%opt)) { print "export $key=''$opt{$key}''; "; }

luego puede usar en su guión como una sola línea, por ejemplo:

#!/bin/bash eval `getopts.pl reuse:s,long_opt:s,hello $*`; echo "HELLO: $hello" echo "LONG_OPT: $long_opt" echo "REUSE: $reuse" echo $*

/tmp/script.sh hola --reuse me --long_opt whatever_you_want_except_spaces --hello 1 2 3

HOLA: 1 LONG_OPT: espacios cualquier cosa que quieras_exponer REUSAR: yo

1 2 3

Sólo la advertencia aquí es que los espacios no funcionan. Pero evita la sintaxis de bucle bastante complicada de bash, funciona con argumentos largos, los nombra automáticamente como variables y cambia el tamaño de $ * automáticamente, por lo que funcionará el 99% del tiempo.


Aquí puede encontrar algunos enfoques diferentes para el análisis de opciones complejas en bash: http://mywiki.wooledge.org/ComplexOptionParsing

Creé la siguiente, y creo que es buena, porque es un código mínimo y funcionan tanto las opciones largas como las cortas. Una opción larga también puede tener múltiples argumentos con este enfoque.

#!/bin/bash # Uses bash extensions. Not portable as written. declare -A longoptspec longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren''t listed i n this way will have zero arguments by default optspec=":h-:" while getopts "$optspec" opt; do while true; do case "${opt}" in -) #OPTARG is name-of-long-option or name-of-long-option=value if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible then opt=${OPTARG/=*/} OPTARG=${OPTARG#*=} ((OPTIND--)) else #with this --key value1 value2 format multiple arguments are possible opt="$OPTARG" OPTARG=(${@:OPTIND:$((longoptspec[$opt]))}) fi ((OPTIND+=longoptspec[$opt])) continue #now that opt/OPTARG are set we can process them as if getopts would''ve given us long options ;; loglevel) loglevel=$OPTARG ;; h|help) echo "usage: $0 [--loglevel[=]<value>]" >&2 exit 2 ;; esac break; done done # End of file


El incorporado getoptsno puede hacer esto. Hay un programa externo getopt (1) que puede hacer esto, pero solo lo puede obtener en Linux desde el paquete util-linux . Viene con un script de ejemplo getopt-parse.bash .

También hay una función getopts_longescrita como shell.


En ksh93, getoptsapoya nombres largos ...

while getopts "f(file):s(server):" flag do echo "$flag" $OPTIND $OPTARG done

O eso dicen los tutoriales que he encontrado. Pruébalo y verás.


En caso de que no quieras la getoptdependencia, puedes hacer esto:

while test $# -gt 0 do case $1 in # Normal option processing -h | --help) # usage and help ;; -v | --version) # version info ;; # ... # Special cases --) break ;; --*) # error unknown (long) option $1 ;; -?) # error unknown (short) option $1 ;; # FUN STUFF HERE: # Split apart combined short options -*) split=$1 shift set -- $(echo "$split" | cut -c 2- | sed ''s/./-& /g'') "$@" continue ;; # Done with options *) break ;; esac # for testing purposes: echo "$1" shift done

Por supuesto, entonces no puedes usar opciones de estilo largas con un solo guión. Y si desea agregar versiones abreviadas (por ejemplo, --verbos en lugar de --verbose), debe agregarlas manualmente.

Pero si está buscando obtener getoptsfuncionalidad junto con opciones largas, esta es una forma simple de hacerlo.

También pongo este fragmento en una gist .


He estado trabajando en ese tema durante bastante tiempo ... e hice mi propia biblioteca, la cual necesitarás en tu script principal. Ver libopt4shell y cd2mpc para un ejemplo. Espero eso ayude !


Quería algo sin dependencias externas, con un estricto soporte de bash (-u), y lo necesitaba para funcionar incluso en las versiones anteriores de bash. Esto maneja varios tipos de parámetros:

  • Bools cortos (-h)
  • opciones cortas (-i "image.jpg")
  • bools largos (--help)
  • es igual a opciones (--archivo = "nombre_archivo.ext")
  • opciones de espacio (--archivo "nombre_archivo.ext")
  • bools concatinados (-hvm)

Simplemente inserte lo siguiente en la parte superior de su script:

# Check if a list of params contains a specific param # usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ... # the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable) _param_variant() { for param in $1 ; do local variants=${param///|/ } for variant in $variants ; do if [[ "$variant" = "$2" ]] ; then # Update the key to match the long version local arr=(${param///|/ }) let last=${#arr[@]}-1 key="${arr[$last]}" return 0 fi done done return 1 } # Get input parameters in short or long notation, with no dependencies beyond bash # usage: # # First, set your defaults # param_help=false # param_path="." # param_file=false # param_image=false # param_image_lossy=true # # Define allowed parameters # allowed_params="h|?|help p|path f|file i|image image-lossy" # # Get parameters from the arguments provided # _get_params $* # # Parameters will be converted into safe variable names like: # param_help, # param_path, # param_file, # param_image, # param_image_lossy # # Parameters without a value like "-h" or "--help" will be treated as # boolean, and will be set as param_help=true # # Parameters can accept values in the various typical ways: # -i "path/goes/here" # --image "path/goes/here" # --image="path/goes/here" # --image=path/goes/here # These would all result in effectively the same thing: # param_image="path/goes/here" # # Concatinated short parameters (boolean) are also supported # -vhm is the same as -v -h -m _get_params(){ local param_pair local key local value local shift_count while : ; do # Ensure we have a valid param. Allows this to work even in -u mode. if [[ $# == 0 || -z $1 ]] ; then break fi # Split the argument if it contains "=" param_pair=(${1//=/ }) # Remove preceeding dashes key="${param_pair[0]#--}" # Check for concatinated boolean short parameters. local nodash="${key#-}" local breakout=false if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h" local short_param_count=${#nodash} let new_arg_count=$#+$short_param_count-1 local new_args="" # $str_pos is the current position in the short param string $nodash for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do # The first character becomes the current key if [ $str_pos -eq 0 ] ; then key="${nodash:$str_pos:1}" breakout=true fi # $arg_pos is the current position in the constructed arguments list let arg_pos=$str_pos+1 if [ $arg_pos -gt $short_param_count ] ; then # handle other arguments let orignal_arg_number=$arg_pos-$short_param_count+1 local new_arg="${!orignal_arg_number}" else # break out our one argument into new ones local new_arg="-${nodash:$str_pos:1}" fi new_args="$new_args /"$new_arg/"" done # remove the preceding space and set the new arguments eval set -- "${new_args# }" fi if ! $breakout ; then key="$nodash" fi # By default we expect to shift one argument at a time shift_count=1 if [ "${#param_pair[@]}" -gt "1" ] ; then # This is a param with equals notation value="${param_pair[1]}" else # This is either a boolean param and there is no value, # or the value is the next command line argument # Assume the value is a boolean true, unless the next argument is found to be a value. value=true if [[ $# -gt 1 && -n "$2" ]]; then local nodash="${2#-}" if [ "$nodash" = "$2" ]; then # The next argument has NO preceding dash so it is a value value="$2" shift_count=2 fi fi fi # Check that the param being passed is one of the allowed params if _param_variant "$allowed_params" "$key" ; then # --key-name will now become param_key_name eval param_${key//-/_}="$value" else printf ''WARNING: Unknown option (ignored): %s/n'' "$1" >&2 fi shift $shift_count done }

Y utilízalo así:

# Assign defaults for parameters param_help=false param_path=$(pwd) param_file=false param_image=true param_image_lossy=true param_image_lossy_quality=85 # Define the params we will allow allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality" # Get the params from arguments provided _get_params $*


Th incorporado en OS X (BSD) getopt no es compatible con las opciones largas, pero la versión de GNU hace: brew install gnu-getopt. Entonces, algo similar a: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


Todavía no tengo suficiente reputación para comentar o votar su solución, pero la respuesta de Sme me fue muy bien. El único problema que encontré fue que los argumentos terminan envueltos en comillas simples (así que tengo una tira de ellos).

También agregué algunos usos de ejemplo y texto de AYUDA. Incluiré mi versión ligeramente extendida aquí:

#!/bin/bash # getopt example # from: https://.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options HELP_TEXT=/ " USAGE:/n Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options./n/n Accepts the following forms:/n/n getopt-example.sh -a -b -c value-for-c some-arg/n getopt-example.sh -c value-for-c -a -b some-arg/n getopt-example.sh -abc some-arg/n getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg/n getopt-example.sh some-arg --clong value-for-c/n getopt-example.sh " aflag=false bflag=false cargument="" # options may be followed by one colon to indicate they have a required argument if ! options=$(getopt -o abc:h/? -l along,blong,help,clong: -- "$@") then # something went wrong, getopt will put out an error message for us exit 1 fi set -- $options while [ $# -gt 0 ] do case $1 in -a|--along) aflag=true ;; -b|--blong) bflag=true ;; # for options with required arguments, an additional shift is required -c|--clong) cargument="$2" ; shift;; -h|--help|-/?) echo -e $HELP_TEXT; exit;; (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done # to remove the single quotes around arguments, pipe the output into: # | sed -e "s/^''//|''$//g" (just leading/trailing) or | sed -e "s/''//g" (all) echo aflag=${aflag} echo bflag=${bflag} echo cargument=${cargument} while [ $# -gt 0 ] do echo arg=$1 shift if [[ $aflag == true ]]; then echo a is true fi done


#!/bin/bash while getopts "abc:d:" flag do case $flag in a) echo "[getopts:$OPTIND]==> -$flag";; b) echo "[getopts:$OPTIND]==> -$flag";; c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";; d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";; esac done shift $((OPTIND-1)) echo "[otheropts]==> $@" exit

.

#!/bin/bash until [ -z "$1" ]; do case $1 in "--dlong") shift if [ "${1:1:0}" != "-" ] then echo "==> dlong $1" shift fi;; *) echo "==> other $1"; shift;; esac done exit