script programas pasar parametros ejemplos comandos comando bash command-line scripting arguments

programas - ¿Cómo puedo analizar los argumentos de la línea de comandos en Bash?



pasar parametros shell script (29)

Método # 1: Usar bash sin getopt [s]

Dos formas comunes de pasar argumentos de par clave-valor son:

Separación de espacio de Bash (por ejemplo, --option argument ) (sin getopt [s])

Uso ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash POSITIONAL=() while [[ $# -gt 0 ]] do key="$1" case $key in -e|--extension) EXTENSION="$2" shift # past argument shift # past value ;; -s|--searchpath) SEARCHPATH="$2" shift # past argument shift # past value ;; -l|--lib) LIBPATH="$2" shift # past argument shift # past value ;; --default) DEFAULT=YES shift # past argument ;; *) # unknown option POSITIONAL+=("$1") # save it in an array for later shift # past argument ;; esac done set -- "${POSITIONAL[@]}" # restore positional parameters echo FILE EXTENSION = "${EXTENSION}" echo SEARCH PATH = "${SEARCHPATH}" echo LIBRARY PATH = "${LIBPATH}" echo DEFAULT = "${DEFAULT}" echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l) if [[ -n $1 ]]; then echo "Last line of file specified as non-opt/last argument:" tail -1 "$1" fi

Bash Equals-Separated (por ejemplo, --option=argument ) (sin getopt [s])

Uso ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/bin/bash for i in "$@" do case $i in -e=*|--extension=*) EXTENSION="${i#*=}" shift # past argument=value ;; -s=*|--searchpath=*) SEARCHPATH="${i#*=}" shift # past argument=value ;; -l=*|--lib=*) LIBPATH="${i#*=}" shift # past argument=value ;; --default) DEFAULT=YES shift # past argument with no value ;; *) # unknown option ;; esac done echo "FILE EXTENSION = ${EXTENSION}" echo "SEARCH PATH = ${SEARCHPATH}" echo "LIBRARY PATH = ${LIBPATH}" echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l) if [[ -n $1 ]]; then echo "Last line of file specified as non-opt/last argument:" tail -1 $1 fi

Para comprender mejor ${i#*=} busque "Eliminación de subcadenas" en esta guía . Es funcionalmente equivalente a `sed ''s/[^=]*=//'' <<< "$i"` que llama a un subproceso innecesario o `echo "$i" | sed ''s/[^=]*=//''` `echo "$i" | sed ''s/[^=]*=//''` que llama a dos subprocesos innecesarios.

Método # 2: usando bash con getopt [s]

de: http://mywiki.wooledge.org/BashFAQ/035#getopts

Limitaciones de getopt (1) (versiones anteriores, relativamente recientes de getopt ):

  • no puede manejar argumentos que son cadenas vacías
  • no puede manejar argumentos con espacios en blanco incrustados

Las versiones más recientes de getopt no tienen estas limitaciones.

Además, el shell POSIX (y otros) ofrecen getopts que no tienen estas limitaciones. Aquí hay un ejemplo simplista de getopts :

#!/bin/sh # A POSIX variable OPTIND=1 # Reset in case getopts has been used previously in the shell. # Initialize our own variables: output_file="" verbose=0 while getopts "h?vf:" opt; do case "$opt" in h|/?) show_help exit 0 ;; v) verbose=1 ;; f) output_file=$OPTARG ;; esac done shift $((OPTIND-1)) [ "${1:-}" = "--" ] && shift echo "verbose=$verbose, output_file=''$output_file'', Leftovers: $@" # End of file

Las ventajas de getopts son:

  1. Es más portátil, y funcionará en otros shells como dash .
  2. Puede manejar múltiples opciones individuales como -vf filename en la forma típica de Unix, automáticamente.

La desventaja de getopts es que solo puede manejar opciones cortas ( -h , no --help ) sin código adicional.

Hay un tutorial de getopts que explica lo que significan todas las sintaxis y variables. En bash, también hay help getopts , que puede ser informativo.

Digamos, tengo un script que se llama con esta línea:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

o este:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile

¿Cuál es la forma aceptada de analizar esto de manera tal que en cada caso (o una combinación de los dos) $v , $f , y $d todos se configurarán como true y $outFile será igual a /fizz/someOtherFile ?


Manera más sucinta

script.sh

#!/bin/bash while [[ "$#" > 0 ]]; do case $1 in -d|--deploy) deploy="$2"; shift;; -u|--uglify) uglify=1;; *) echo "Unknown parameter passed: $1"; exit 1;; esac; shift; done echo "Should deploy? $deploy" echo "Should uglify? $uglify"

Uso:

./script.sh -d dev -u # OR: ./script.sh --deploy dev --uglify


Otra solución sin getopt [s], POSIX, antiguo estilo Unix

Similar a la solución que Bruno Bronosky publicó aquí es una sin el uso de getopt(s).

La principal característica diferenciadora de mi solución es que permite tener opciones concatenadas juntas igual que tar -xzf foo.tar.gzes igual a tar -x -z -f foo.tar.gz. Y al igual que en tar, psetc. el guión principal es opcional para un bloque de opciones cortas (pero esto se puede cambiar fácilmente). Las opciones largas también son compatibles (pero cuando un bloque comienza con uno, se requieren dos guiones).

Código con opciones de ejemplo

#!/usr/bin/env bash #shebang.io validflags="rate time number" count=1 for arg in $@ do match=0 argval=$1 for flag in $validflags do sflag="-"$flag if [ "$argval" == "$sflag" ] then declare $flag=$2 match=1 fi done if [ "$match" == "1" ] then shift 2 else leftovers=$(echo $leftovers $argval) shift fi count=$(($count+1)) done #Cleanup then restore the leftovers shift $# set -- $leftovers

Para el uso del ejemplo, por favor vea los ejemplos más abajo.

Posicionamiento de opciones con argumentos.

Por lo que vale la pena, las opciones con argumentos no son las últimas (solo las opciones largas deben ser). Entonces, mientras que en tar(al menos en algunas implementaciones) las fopciones deben ser las últimas porque el nombre del archivo sigue ( tar xzf bar.tar.gzfunciona pero tar xfz bar.tar.gzno lo hace), este no es el caso aquí (vea los ejemplos posteriores).

Múltiples opciones con argumentos.

Como otra ventaja adicional, los parámetros de opción se consumen en el orden de las opciones por los parámetros con las opciones requeridas. Solo mire la salida de mi script aquí con la línea de comando abc XYZ(o -abc XYZ):

#!/usr/bin/env bash #shebang.io rate=30 time=30 number=30 echo "all args $@" validflags="rate time number" count=1 for arg in $@ do match=0 argval=$1 # argval=$(echo $@ | cut -d '' '' -f$count) for flag in $validflags do sflag="-"$flag if [ "$argval" == "$sflag" ] then declare $flag=$2 match=1 fi done if [ "$match" == "1" ] then shift 2 else leftovers=$(echo $leftovers $argval) shift fi count=$(($count+1)) done #Cleanup then restore the leftovers echo "pre final clear args: $@" shift $# echo "post final clear args: $@" set -- $leftovers echo "all post set args: $@" echo arg1: $1 arg2: $2 echo leftovers: $leftovers echo rate $rate time $time number $number

Opciones largas concatenadas también.

Además, también puede tener opciones largas en el bloque de opciones, dado que ocurren por última vez en el bloque. Por lo tanto, las siguientes líneas de comando son todas equivalentes (incluido el orden en que se procesan las opciones y sus argumentos):

  • -cba ZYX
  • cba ZYX
  • -cb-aaa-0-args ZYX
  • -c-bbb-1-args ZYX -a
  • --ccc-2-args ZY -ba X
  • c ZY b X a
  • -c ZY -b X -a
  • --ccc-2-args ZY --bbb-1-args X --aaa-0-args

Todos estos conducen a:

#!/usr/bin/env bash #shebang.io rate=30 time=30 number=30 validflags="rate time number" count=1 for arg in $@ do argval=$1 match=0 if [ "${argval:0:1}" == "-" ] then for flag in $validflags do sflag="-"$flag if [ "$argval" == "$sflag" ] then declare $flag=$2 match=1 fi done if [ "$match" == "0" ] then echo "Bad argument: $argval" exit 1 fi shift 2 else leftovers=$(echo $leftovers $argval) shift fi count=$(($count+1)) done #Cleanup then restore the leftovers shift $# set -- $leftovers echo rate $rate time $time number $number echo leftovers: $leftovers

No en esta solución

Argumentos opcionales

Las opciones con argumentos opcionales deberían ser posibles con un poco de trabajo, por ejemplo, mirando hacia adelante si hay un bloque sin un guión; el usuario tendría que poner un guión delante de cada bloque después de un bloque con un parámetro que tiene un parámetro opcional. Tal vez esto sea demasiado complicado para comunicarse con el usuario, por lo que es mejor que solo requiera un guión principal en este caso.

Las cosas se complican aún más con múltiples parámetros posibles. No recomendaría que las opciones traten de ser inteligentes al determinar si un argumento podría ser adecuado o no (por ejemplo, con una opción solo toma un número como un argumento opcional) porque esto podría interrumpirse en el futuro.

Personalmente prefiero opciones adicionales en lugar de argumentos opcionales.

Opción argumentos introducidos con un signo igual

Al igual que con los argumentos opcionales, no soy un fanático de esto (por cierto, ¿hay un hilo para discutir los pros / contras de los diferentes estilos de parámetros?) Pero si lo desea, probablemente podría implementarlo usted mismo como lo hizo en http://mywiki.wooledge.org/BashFAQ/035#Manual_loop con una --long-with-arg=?*declaración de caso y luego eliminando el signo igual (esto es, por cierto, el sitio que dice que hacer la concatenación de parámetros es posible con cierto esfuerzo, pero "lo dejó como ejercicio para el lector "lo que me hizo tomarles la palabra pero empecé desde cero".

Otras notas

Compatible con POSIX, funciona incluso en configuraciones de Busybox antiguas con las que tuve que lidiar (por ejemplo cut, heady getoptsfaltantes).


Solución que conserva los argumentos no manejados. Incluye demostraciones.

Aquí está mi solución. Es MUY flexible y, a diferencia de otros, no debería requerir paquetes externos y maneja los argumentos sobrantes de manera limpia.

El uso es: ./myscript -flag flagvariable -otherflag flagvar2

Todo lo que tienes que hacer es editar la línea validflags. Prepara un guión y busca todos los argumentos. Luego define el siguiente argumento como el nombre de la bandera, por ejemplo

#!/bin/sh until [ $# -eq 0 ] do name=${1:1}; shift; if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi done echo "year=$year month=$month day=$day flag=$flag"

El código principal (versión corta, detallado con ejemplos más abajo, también una versión con error):

sh test_args.sh -year 2017 -flag -month 12 -day 22

La versión detallada con demostraciones de eco incorporadas:

year=2017 month=12 day=22 flag=true

Una final, esta falla si se pasa un argumento no válido.

#!/bin/bash . import.sh log arguments NAME="world" parse_arguments "-n|--name)NAME;S" -- "$@" || { error "Cannot parse command line." exit 1 } info "Hello, $NAME!"

Pros: Lo que hace, lo maneja muy bien. Conserva los argumentos no utilizados que muchas de las otras soluciones aquí no. También permite que las variables sean llamadas sin ser definidas a mano en el script. También permite la prepopulación de variables si no se da ningún argumento correspondiente. (Ver ejemplo detallado).

Contras: no se puede analizar una sola cadena arg compleja, por ejemplo, -xcvf se procesaría como un solo argumento. Sin embargo, podrías escribir un código adicional en el mío que agregue esta funcionalidad.


Mezclando argumentos posicionales y basados ​​en banderas.

--param = arg (es igual a delimitado)

Mezclando libremente banderas entre argumentos posicionales:

./script.sh dumbo 127.0.0.1 --environment=production -q -d ./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

Se puede lograr con un enfoque bastante conciso:

# process flags pointer=1 while [[ $pointer -le $# ]]; do param=${!pointer} if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer else case $param in # paramter-flags with arguments -e=*|--environment=*) environment="${param#*=}";; --another=*) another="${param#*=}";; # binary flags -q|--quiet) quiet=true;; -d) debug=true;; esac # splice out pointer frame from positional list [[ $pointer -gt 1 ]] / && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} / || set -- ${@:((pointer + 1)):$#}; fi done # positional remain node_name=$1 ip_address=$2

--param arg (espacio delimitado)

Por lo general, es más claro no mezclar --flag=valuey --flag valueestilos.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Esto es un poco peligroso para leer, pero sigue siendo válido

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Fuente

# process flags pointer=1 while [[ $pointer -le $# ]]; do if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer else param=${!pointer} ((pointer_plus = pointer + 1)) slice_len=1 case $param in # paramter-flags with arguments -e|--environment) environment=${!pointer_plus}; ((slice_len++));; --another) another=${!pointer_plus}; ((slice_len++));; # binary flags -q|--quiet) quiet=true;; -d) debug=true;; esac # splice out pointer frame from positional list [[ $pointer -gt 1 ]] / && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} / || set -- ${@:((pointer + $slice_len)):$#}; fi done # positional remain node_name=$1 ip_address=$2


A riesgo de agregar otro ejemplo para ignorar, aquí está mi esquema.

  • maneja -n arg y --name=arg
  • permite argumentos al final
  • muestra errores sane si algo está mal escrito
  • Compatible, no usa bashismos.
  • legible, no requiere mantener el estado en un bucle

Espero que sea útil para alguien.

while [ "$#" -gt 0 ]; do case "$1" in -n) name="$2"; shift 2;; -p) pidfile="$2"; shift 2;; -l) logfile="$2"; shift 2;; --name=*) name="${1#*=}"; shift 1;; --pidfile=*) pidfile="${1#*=}"; shift 1;; --logfile=*) logfile="${1#*=}"; shift 1;; --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;; -*) echo "unknown option: $1" >&2; exit 1;; *) handle_argument "$1"; shift 1;; esac done


Ampliando la excelente respuesta de @guneysus, aquí hay un ajuste que le permite al usuario usar la sintaxis que prefiera, por ejemplo

command -x=myfilename.ext --another_switch

vs

command -x myfilename.ext --another_switch

Es decir, los iguales pueden ser reemplazados por espacios en blanco.

Es posible que esta "interpretación difusa" no sea de su agrado, pero si está creando secuencias de comandos que son intercambiables con otras utilidades (como es el caso de la mía, que debe funcionar con ffmpeg), la flexibilidad es útil.

STD_IN=0 prefix="" key="" value="" for keyValue in "$@" do case "${prefix}${keyValue}" in -i=*|--input_filename=*) key="-i"; value="${keyValue#*=}";; -ss=*|--seek_from=*) key="-ss"; value="${keyValue#*=}";; -t=*|--play_seconds=*) key="-t"; value="${keyValue#*=}";; -|--stdin) key="-"; value=1;; *) value=$keyValue;; esac case $key in -i) MOVIE=$(resolveMovie "${value}"); prefix=""; key="";; -ss) SEEK_FROM="${value}"; prefix=""; key="";; -t) PLAY_SECONDS="${value}"; prefix=""; key="";; -) STD_IN=${value}; prefix=""; key="";; *) prefix="${keyValue}=";; esac done


Así es como lo hago en una función para evitar que los getopts de ruptura se ejecuten al mismo tiempo en un lugar más alto en la pila:

function waitForWeb () { local OPTIND=1 OPTARG OPTION local host=localhost port=8080 proto=http while getopts "h:p:r:" OPTION; do case "$OPTION" in h) host="$OPTARG" ;; p) port="$OPTARG" ;; r) proto="$OPTARG" ;; esac done ... }


Creo que este es lo suficientemente simple para usar:

#!/bin/bash # readopt=''getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'' opts=vfdo: # Enumerating options while eval $readopt do echo OPT:$opt ${OPTARG+OPTARG:$OPTARG} done # Enumerating arguments for arg do echo ARG:$arg done

Ejemplo de invocación:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile OPT:v OPT:d OPT:o OPTARG:/fizz/someOtherFile OPT:f ARG:./foo/bar/someFile


He encontrado la cuestión de escribir un análisis portátil en scripts de manera tan frustrante que he escrito Argbash , un generador de código FOSS que puede generar el código de análisis de argumentos para su script, además de que tiene algunas características Argbash :

https://argbash.io


Me gustaría ofrecer mi versión de análisis de opciones, que permite lo siguiente:

-s p1 --stage p1 -w somefolder --workfolder somefolder -sw p1 somefolder -e=hello

También permite esto (podría ser no deseado):

-s--workfolder p1 somefolder -se=hello p1 -swe=hello p1 somefolder

Debe decidir antes de usar si = se usará en una opción o no. Esto es para mantener el código limpio (ish).

while [[ $# > 0 ]] do key="$1" while [[ ${key+x} ]] do case $key in -s*|--stage) STAGE="$2" shift # option has parameter ;; -w*|--workfolder) workfolder="$2" shift # option has parameter ;; -e=*) EXAMPLE="${key#*=}" break # option has been fully handled ;; *) # unknown option echo Unknown option: $key #1>&2 exit 10 # either this: my preferred way to handle unknown options break # or this: do this to signal the option has been handled (if exit isn''t used) ;; esac # prepare for next option in this key, if any [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}" done shift # option(s) fully processed, proceed to next input argument done


Mi respuesta se basa en gran medida en la respuesta de Bruno Bronosky , pero en cierto modo combiné sus dos implementaciones puras de bash en una que utilizo con bastante frecuencia.

# As long as there is at least one more argument, keep looping while [[ $# -gt 0 ]]; do key="$1" case "$key" in # This is a flag type option. Will catch either -f or --foo -f|--foo) FOO=1 ;; # Also a flag type option. Will catch either -b or --bar -b|--bar) BAR=1 ;; # This is an arg value type option. Will catch -o value or --output-file value -o|--output-file) shift # past the key and to the value OUTPUTFILE="$1" ;; # This is an arg=value type option. Will catch -o=value or --output-file=value -o=*|--output-file=*) # No need to shift here since the value is part of the same string OUTPUTFILE="${key#*=}" ;; *) # Do whatever you want with extra options echo "Unknown option ''$key''" ;; esac # Shift after checking all the cases to get the next option shift done

Esto le permite tener opciones / valores separados por espacios, así como valores definidos iguales.

Así que puedes ejecutar tu script usando:

./myscript --foo -b -o /fizz/file.txt

tanto como:

./myscript -f --bar -o=/fizz/file.txt

y ambos deben tener el mismo resultado final.

PROS:

  • Permite tanto -arg = valor como -arg valor

  • Funciona con cualquier nombre arg que puedes usar en bash

    • Significado -a o -arg o --arg o -arg o lo que sea
  • Puro bash. No hay necesidad de aprender / usar getopt o getopts

CONTRAS:

  • No puedo combinar args

    • Lo que significa no -abc. Debes hacer -a -b -c

Estos son los únicos pros / contras que se me ocurren en la cabeza


Te doy la función parse_params que analizará los parámetros desde la línea de comandos.

  1. Es una solución Bash pura, sin utilidades adicionales.
  2. No contamina el alcance global.
  3. Sin esfuerzo, le devuelve variables fáciles de usar, sobre las que podría construir más lógica.
  4. La cantidad de guiones antes de los parámetros no importa ( --all es igual a - all=all es igual a all=all )

El siguiente script es una demostración de trabajo copiar y pegar. Vea la función show_use para entender cómo usar parse_params .

Limitaciones:

  1. No admite parámetros delimitados por espacios ( -d 1 )
  2. Los nombres de --any-param perderán guiones, por lo que --any-param y -anyparam son equivalentes
  3. eval $(parse_params "$@") debe usarse dentro de la función bash (no funcionará en el ámbito global)

#!/bin/bash # Universal Bash parameter parsing # Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob") # Standalone named parameter value will equal its param name (--force creates variable $force=="force") # Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array) # Parses un-named params into ${ARGV[*]} array # Additionally puts all named params raw into ${ARGN[*]} array # Additionally puts all standalone "option" params raw into ${ARGO[*]} array # @author Oleksii Chekulaiev # @version v1.4 (Jun-26-2018) parse_params () { local existing_named local ARGV=() # un-named params local ARGN=() # named params local ARGO=() # options (--params) echo "local ARGV=(); local ARGN=(); local ARGO=();" while [[ "$1" != "" ]]; do # Escape asterisk to prevent bash asterisk expansion _escaped=${1//*//'/"*/"/'} # If equals delimited named parameter if [[ "$1" =~ ^..*=..* ]]; then # Add to named parameters array echo "ARGN+=(''$_escaped'');" # key is part before first = local _key=$(echo "$1" | cut -d = -f 1) # val is everything after key and = (protect from param==value error) local _val="${1/$_key=}" # remove dashes from key name _key=${_key///-} # skip when key is empty if [[ "$_key" == "" ]]; then shift continue fi # search for existing parameter name if (echo "$existing_named" | grep "/b$_key/b" >/dev/null); then # if name already exists then it''s a multi-value named parameter # re-declare it as an array if needed if ! (declare -p _key 2> /dev/null | grep -q ''declare /-a''); then echo "$_key=(/"/$$_key/");" fi # append new value echo "$_key+=(''$_val'');" else # single-value named parameter echo "local $_key=/"$_val/";" existing_named=" $_key" fi # If standalone named parameter elif [[ "$1" =~ ^/-. ]]; then # remove dashes local _key=${1///-} # skip when key is empty if [[ "$_key" == "" ]]; then shift continue fi # Add to options array echo "ARGO+=(''$_escaped'');" echo "local $_key=/"$_key/";" # non-named parameter else # Escape asterisk to prevent bash asterisk expansion _escaped=${1//*//'/"*/"/'} echo "ARGV+=(''$_escaped'');" fi shift done } #--------------------------- DEMO OF THE USAGE ------------------------------- show_use () { eval $(parse_params "$@") # -- echo "${ARGV[0]}" # print first unnamed param echo "${ARGV[1]}" # print second unnamed param echo "${ARGN[0]}" # print first named param echo "${ARG0[0]}" # print first option param (--force) echo "$anyparam" # print --anyparam value echo "$k" # print k=5 value echo "${multivalue[0]}" # print first value of multi-value echo "${multivalue[1]}" # print second value of multi-value [[ "$force" == "force" ]] && echo "/$force is set so let the force be with you" } show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2


Tenga en cuenta que getopt(1) fue un error de corta vida de AT&T.

getopt fue creado en 1984 pero ya fue enterrado en 1986 porque no era realmente utilizable.

Una prueba del hecho de que getopt está muy desactualizado es que la página del manual de getopt(1) aún menciona "$*" lugar de "$@" , que se agregó a Bourne Shell en 1986 junto con el getopts(1) incorporado. Para poder lidiar con argumentos con espacios en el interior.

Por cierto: si está interesado en analizar opciones largas en scripts de shell, puede ser interesante saber que la implementación de getopt(3) de libc (Solaris) y ksh93 agregaron una implementación de opción larga y uniforme que admite opciones largas como alias para corto opciones Esto hace que ksh93 y Bourne Shell implementen una interfaz uniforme para opciones largas a través de getopts .

Un ejemplo de opciones largas tomadas de la página man de Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

muestra cuánto tiempo se pueden usar los alias de opciones tanto en Bourne Shell como en ksh93.

Ver la página de manual de un reciente Bourne Shell:

http://schillix.sourceforge.net/man/man1/bosh.1.html

y la página de manual para getopt (3) de OpenSolaris:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

y por último, la página de comando man getopt (1) para verificar $ $ desactualizados:

http://schillix.sourceforge.net/man/man1/getopt.1.html


Tengo aproximadamente 4 años de retraso en esta pregunta, pero quiero devolver. Utilicé las respuestas anteriores como punto de partida para ordenar mi viejo análisis ad hoc de parámetros. Luego, rediseñé el siguiente código de plantilla. Maneja tanto parámetros largos como cortos, utilizando argumentos separados por espacios o =, así como múltiples parámetros cortos agrupados. Finalmente, vuelve a insertar cualquier argumento no param en las variables $ 1, $ 2 .. Espero que sea útil.

#!/usr/bin/env bash # NOTICE: Uncomment if your script depends on bashisms. #if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi echo "Before" for i ; do echo - $i ; done # Code template for parsing command line parameters using only portable shell # code, while handling both long and short params, handling ''-f file'' and # ''-f=file'' style param data and also capturing non-parameters to be inserted # back into the shell positional parameters. while [ -n "$1" ]; do # Copy so we can modify it (can''t modify $1) OPT="$1" # Detect argument termination if [ x"$OPT" = x"--" ]; then shift for OPT ; do REMAINS="$REMAINS /"$OPT/"" done break fi # Parse current opt while [ x"$OPT" != x"-" ] ; do case "$OPT" in # Handle --flag=value opts like this -c=* | --config=* ) CONFIGFILE="${OPT#*=}" shift ;; # and --flag value opts like this -c* | --config ) CONFIGFILE="$2" shift ;; -f* | --force ) FORCE=true ;; -r* | --retry ) RETRY=true ;; # Anything unknown is recorded for later * ) REMAINS="$REMAINS /"$OPT/"" break ;; esac # Check for multiple short options # NOTICE: be sure to update this pattern to match valid options NEXTOPT="${OPT#-[cfr]}" # try removing single short opt if [ x"$OPT" != x"$NEXTOPT" ] ; then OPT="-$NEXTOPT" # multiple short opts, keep going else break # long form, exit inner loop fi done # Done with that param. move to next shift done # Set the non-parameters back into the positional parameters ($1 $2 ..) eval set -- $REMAINS echo -e "After: /n configfile=''$CONFIGFILE'' /n force=''$FORCE'' /n retry=''$RETRY'' /n remains=''$REMAINS''" for i ; do echo - $i ; done


de: digitalpeer.com con pequeñas modificaciones

Uso myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash for i in "$@" do case $i in -p=*|--prefix=*) PREFIX="${i#*=}" ;; -s=*|--searchpath=*) SEARCHPATH="${i#*=}" ;; -l=*|--lib=*) DIR="${i#*=}" ;; --default) DEFAULT=YES ;; *) # unknown option ;; esac done echo PREFIX = ${PREFIX} echo SEARCH PATH = ${SEARCHPATH} echo DIRS = ${DIR} echo DEFAULT = ${DEFAULT}

Para comprender mejor ${i#*=} busque "Eliminación de subcadenas" en esta guía . Es funcionalmente equivalente a `sed ''s/[^=]*=//'' <<< "$i"` que llama a un subproceso innecesario o `echo "$i" | sed ''s/[^=]*=//''` `echo "$i" | sed ''s/[^=]*=//''` que llama a dos subprocesos innecesarios.


getopts funciona bien si # 1 lo tiene instalado y # 2 tiene la intención de ejecutarlo en la misma plataforma. OSX y Linux (por ejemplo) se comportan de manera diferente a este respecto.

Aquí hay una solución (no getopts) que admite banderas iguales, no iguales y booleanas. Por ejemplo, podría ejecutar su script de esta manera:

./script --arg1=value1 --arg2 value2 --shouldClean # parse the arguments. COUNTER=0 ARGS=("$@") while [ $COUNTER -lt $# ] do arg=${ARGS[$COUNTER]} let COUNTER=COUNTER+1 nextArg=${ARGS[$COUNTER]} if [[ $skipNext -eq 1 ]]; then echo "Skipping" skipNext=0 continue fi argKey="" argVal="" if [[ "$arg" =~ ^/- ]]; then # if the format is: -key=value if [[ "$arg" =~ /= ]]; then argVal=$(echo "$arg" | cut -d''='' -f2) argKey=$(echo "$arg" | cut -d''='' -f1) skipNext=0 # if the format is: -key value elif [[ ! "$nextArg" =~ ^/- ]]; then argKey="$arg" argVal="$nextArg" skipNext=1 # if the format is: -key (a boolean flag) elif [[ "$nextArg" =~ ^/- ]] || [[ -z "$nextArg" ]]; then argKey="$arg" argVal="" skipNext=0 fi # if the format has not flag, just a value. else argKey="" argVal="$arg" skipNext=0 fi case "$argKey" in --source-scmurl) SOURCE_URL="$argVal" ;; --dest-scmurl) DEST_URL="$argVal" ;; --version-num) VERSION_NUM="$argVal" ;; -c|--clean) CLEAN_BEFORE_START="1" ;; -h|--help|-help|--h) showUsage exit ;; esac done


EasyOptions no requiere ningún análisis:

## Options: ## --verbose, -v Verbose mode ## --output=FILE Output filename source easyoptions || exit if test -n "${verbose}"; then echo "output file is ${output}" echo "${arguments[@]}" fi


getopt() / getopts() es una buena opción. Robado desde here :

El uso simple de "getopt" se muestra en este mini-script:

#!/bin/bash echo "Before getopt" for i do echo $i done args=`getopt abc:d $*` set -- $args echo "After getopt" for i do echo "-->$i" done

Lo que hemos dicho es que se permitirá cualquiera de -a, -b, -c o -d, pero que -c es seguido por un argumento (el "c:" dice eso).

Si llamamos a esto "g" y lo probamos:

bash-2.05a$ ./g -abc foo Before getopt -abc foo After getopt -->-a -->-b -->-c -->foo -->--

Comenzamos con dos argumentos, y "getopt" rompe las opciones y pone cada uno en su propio argumento. También añadió "-".


No hay respuesta a las menciones mejoradas getopt . Y la respuesta más votada es engañosa: ignora las opciones cortas del estilo -⁠vfd (solicitadas por el OP), las opciones después de los argumentos posicionales (también solicitadas por el OP) e ignora los errores de análisis. En lugar:

  • Use getopt mejorado de util-linux o anteriormente GNU glibc . 1
  • Funciona con getopt_long() la función C de GNU glibc.
  • Tiene todas las características distintivas útiles (las otras no las tienen):
    • Maneja espacios, citando caracteres e incluso binarios en los argumentos 2.
    • puede manejar las opciones al final: script.sh -o outFile file1 file2 -v salida script.sh -o outFile file1 file2 -v
    • permite opciones de estilo largo: script.sh --outfile=fileOut --infile fileIn
  • Ya es tan viejo 3 que a ningún sistema GNU le falta esto (por ejemplo, cualquier Linux lo tiene).
  • Puede probar su existencia con: getopt --test → return value 4.
  • Otros getopt o shell- getopts son de uso limitado.

Las siguientes llamadas

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

todo regreso

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

con el siguiente myscript

#!/bin/bash # saner programming env: these switches turn some bugs into errors set -o errexit -o pipefail -o noclobber -o nounset ! getopt --test > /dev/null if [[ ${PIPESTATUS[0]} -ne 4 ]]; then echo "I’m sorry, `getopt --test` failed in this environment." exit 1 fi OPTIONS=dfo:v LONGOPTS=debug,force,output:,verbose # -use ! and PIPESTATUS to get exit code with errexit set # -temporarily store output to be able to check for errors # -activate quoting/enhanced mode (e.g. by writing out “--options”) # -pass arguments only via -- "$@" to separate them correctly ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") if [[ ${PIPESTATUS[0]} -ne 0 ]]; then # e.g. return value is 1 # then getopt has complained about wrong arguments to stdout exit 2 fi # read getopt’s output this way to handle the quoting right: eval set -- "$PARSED" d=n f=n v=n outFile=- # now enjoy the options in order and nicely split until we see -- while true; do case "$1" in -d|--debug) d=y shift ;; -f|--force) f=y shift ;; -v|--verbose) v=y shift ;; -o|--output) outFile="$2" shift 2 ;; --) shift break ;; *) echo "Programming error" exit 3 ;; esac done # handle non-option arguments if [[ $# -ne 1 ]]; then echo "$0: A single input file is required." exit 4 fi echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 getopt mejorado está disponible en la mayoría de los "sistemas bash", incluido Cygwin; en OS X intente con brew install gnu-getopt o sudo port install getopt
2 las convenciones de exec() POSIX no tienen una manera confiable de pasar NULL binarios en los argumentos de la línea de comando; esos bytes terminan prematuramente el argumento
3 primera versión lanzada en 1997 o antes (solo la rastreé hasta 1997)


Aquí está mi enfoque - usando expresiones regulares.

  • no getopts
  • maneja bloque de parametros cortos -qwerty
  • maneja parámetros cortos -q -w -e
  • maneja opciones largas --qwerty
  • puede pasar el atributo a la opción corta o larga (si está usando el bloque de opciones cortas, el atributo se adjunta a la última opción)
  • puede usar espacios o =para proporcionar atributos, pero los atributos coinciden hasta encontrar un "delimitador" de guión + espacio, por lo que en --q=qwe ty qwe tyun atributo
  • maneja la mezcla de todo lo anterior así que -oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ributees válido

guión:

#!/usr/bin/env sh help_menu() { echo "Usage: ${0##*/} [-h][-l FILENAME][-d] Options: -h, --help display this help and exit -l, --logfile=FILENAME filename -d, --debug enable debug " } parse_options() { case $opt in h|help) help_menu exit ;; l|logfile) logfile=${attr} ;; d|debug) debug=true ;; *) echo "Unknown option: ${opt}/nRun ${0##*/} -h for help.">&2 exit 1 esac } options=$@ until [ "$options" = "" ]; do if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([/_/./?////a-zA-Z0-9]?[ -]?[/_/./?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute] opt=${BASH_REMATCH[3]} attr=${BASH_REMATCH[7]} options=${BASH_REMATCH[9]} elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute] pile=${BASH_REMATCH[4]} while (( ${#pile} > 1 )); do opt=${pile:0:1} attr="" pile=${pile/${pile:0:1}/} parse_options done opt=$pile attr=${BASH_REMATCH[7]} options=${BASH_REMATCH[9]} else # leftovers that don''t match opt=${BASH_REMATCH[10]} options="" fi parse_options fi done


Aquí está mi solución mejorada de la respuesta de Bruno Bronosky usando matrices variables.

le permite mezclar la posición de los parámetros y darle una matriz de parámetros que conserva el orden sin las opciones

#!/bin/bash echo $@ PARAMS=() SOFT=0 SKIP=() for i in "$@" do case $i in -n=*|--skip=*) SKIP+=("${i#*=}") ;; -s|--soft) SOFT=1 ;; *) # unknown option PARAMS+=("$i") ;; esac done echo "SKIP = ${SKIP[@]}" echo "SOFT = $SOFT" echo "Parameters:" echo ${PARAMS[@]}

Saldrá por ejemplo:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj parameter -s somefile --skip=.c --skip=.obj SKIP = .c .obj SOFT = 1 Parameters: parameter somefile


He escrito un ayudante de bash para escribir una buena herramienta de bash

Inicio del proyecto: https://gitlab.mbedsys.org/mbedsys/bashopts

ejemplo:

#!/bin/bash -ei # load the library . bashopts.sh # Enable backtrace dusplay on error trap ''bashopts_exit_handle'' ERR # Initialize the library bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "/$first_name /$last_name" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args

dará ayuda

NAME: ./example.sh - This is myapp tool description displayed on help message USAGE: [options and commands] [-- [extra args]] OPTIONS: -h,--help Display this help -n,--non-interactive true Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false) -f,--first "John" First name - [$first_name] (type:string, default:"") -l,--last "Smith" Last name - [$last_name] (type:string, default:"") --display-name "John Smith" Display name - [$display_name] (type:string, default:"$first_name $last_name") --number 0 Age - [$age] (type:number, default:0) --email Email adress - [$email_list] (type:string, default:"")

disfrutar :)


La respuesta principal a esta pregunta me pareció un poco defectuosa cuando la probé. Aquí está mi solución que he encontrado más robusta:

boolean_arg="" arg_with_value="" while [[ $# -gt 0 ]] do key="$1" case $key in -b|--boolean-arg) boolean_arg=true shift ;; -a|--arg-with-value) arg_with_value="$2" shift shift ;; -*) echo "Unknown option: $1" exit 1 ;; *) arg_num=$(( $arg_num + 1 )) case $arg_num in 1) first_normal_arg="$1" shift ;; 2) second_normal_arg="$1" shift ;; *) bad_args=TRUE esac ;; esac done # Handy to have this here when adding arguments to # see if they''re working. Just edit the ''0'' to be ''1''. if [[ 0 == 1 ]]; then echo "first_normal_arg: $first_normal_arg" echo "second_normal_arg: $second_normal_arg" echo "boolean_arg: $boolean_arg" echo "arg_with_value: $arg_with_value" exit 0 fi if [[ $bad_args == TRUE || $arg_num < 2 ]]; then echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]" exit 1 fi


También puede ser útil saberlo, puede establecer un valor y, si alguien proporciona una entrada, anule el valor predeterminado con ese valor.

myscript.sh -f ./serverlist.txt o simplemente ./myscript.sh (y toma valores predeterminados)

./myscript -flag flagvariable -otherflag flagvar2 echo $flag $otherflag flagvariable flagvar2


Este ejemplo muestra cómo usar getopty evaly HEREDOCy shiftpara manejar parámetros cortos y largos con y sin el valor requerido que sigue. También la declaración de cambio / caso es concisa y fácil de seguir.

#!/usr/bin/env bash # usage function function usage() { cat << HEREDOC Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run] optional arguments: -h, --help show this help message and exit -n, --num NUM pass in a number -t, --time TIME_STR pass in a time string -v, --verbose increase the verbosity of the bash script --dry-run do a dry run, don''t change any files HEREDOC } # initialize variables progname=$(basename $0) verbose=0 dryrun=0 num_str= time_str= # use getopt and store the output into $OPTS # note the use of -o for the short options, --long for the long name options # and a : for any option that takes a parameter OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@") if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi eval set -- "$OPTS" while true; do # uncomment the next line to see how shift is working # echo "/$1:/"$1/" /$2:/"$2/"" case "$1" in -h | --help ) usage; exit; ;; -n | --num ) num_str="$2"; shift 2 ;; -t | --time ) time_str="$2"; shift 2 ;; --dry-run ) dryrun=1; shift ;; -v | --verbose ) verbose=$((verbose + 1)); shift ;; -- ) shift; break ;; * ) break ;; esac done if (( $verbose > 0 )); then # print out all the parameters we read in cat <<-EOM num=$num_str time=$time_str verbose=$verbose dryrun=$dryrun EOM fi # The rest of your script below

Las líneas más significativas del script anterior son las siguientes:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@") if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi eval set -- "$OPTS" while true; do case "$1" in -h | --help ) usage; exit; ;; -n | --num ) num_str="$2"; shift 2 ;; -t | --time ) time_str="$2"; shift 2 ;; --dry-run ) dryrun=1; shift ;; -v | --verbose ) verbose=$((verbose + 1)); shift ;; -- ) shift; break ;; * ) break ;; esac done

Corto, hasta el punto, legible, y maneja casi todo (IMHO).

Espero que ayude a alguien.


Quiero enviar mi proyecto: https://github.com/flyingangel/argparser

source argparser.sh parse_args "$@"

Simple como eso. El entorno se rellenará con variables con el mismo nombre que los argumentos.


Supongamos que creamos un script de shell llamado test_args.shcomo sigue

#!/bin/bash # --- set the value, if there is inputs, override the defaults. HOME_FOLDER="${HOME}/owned_id_checker" SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt" while [[ $# > 1 ]] do key="$1" shift case $key in -i|--inputlist) SERVER_FILE_LIST="$1" shift ;; esac done echo "SERVER LIST = ${SERVER_FILE_LIST}"

Después ejecutamos el siguiente comando:

#!/bin/sh echo echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]" echo print_usage() { echo "Usage: $0 {a|b|c} [ARG...] Options: --aaa-0-args -a Option without arguments. --bbb-1-args ARG -b ARG Option with one argument. --ccc-2-args ARG1 ARG2 -c ARG1 ARG2 Option with two arguments. " >&2 } if [ $# -le 0 ]; then print_usage exit 1 fi opt= while :; do if [ $# -le 0 ]; then # no parameters remaining -> end option parsing break elif [ ! "$opt" ]; then # we are at the beginning of a fresh block # remove optional leading hyphen and strip trailing whitespaces opt=$(echo "$1" | sed ''s/^-/?/([a-zA-Z0-9/?-]*/)//1/'') fi # get the first character -> check whether long option first_chr=$(echo "$opt" | awk ''{print substr($1, 1, 1)}'') [ "$first_chr" = - ] && long_option=T || long_option=F # note to write the options here with a leading hyphen less # also do not forget to end short options with a star case $opt in -) # end of options shift break ;; a*|-aaa-0-args) echo "Option AAA activated!" ;; b*|-bbb-1-args) if [ "$2" ]; then echo "Option BBB with argument ''$2'' activated!" shift else echo "BBB parameters incomplete!" >&2 print_usage exit 1 fi ;; c*|-ccc-2-args) if [ "$2" ] && [ "$3" ]; then echo "Option CCC with arguments ''$2'' and ''$3'' activated!" shift 2 else echo "CCC parameters incomplete!" >&2 print_usage exit 1 fi ;; h*|/?*|-help) print_usage exit 0 ;; *) if [ "$long_option" = T ]; then opt=$(echo "$opt" | awk ''{print substr($1, 2)}'') else opt=$first_chr fi printf ''Error: Unknown option: "%s"/n'' "$opt" >&2 print_usage exit 1 ;; esac if [ "$long_option" = T ]; then # if we had a long option then we are going to get a new block next shift opt= else # if we had a short option then just move to the next character opt=$(echo "$opt" | awk ''{print substr($1, 2)}'') # if block is now empty then shift to the next one [ "$opt" ] || shift fi done echo "Doing something..." exit 0

La salida sería:

Option AAA activated! Option BBB with argument ''X'' activated! Option CCC with arguments ''Y'' and ''Z'' activated!


Utilice el módulo "argumentos" de bash-modules

Ejemplo:

Option CCC with arguments ''Z'' and ''Y'' activated! Option BBB with argument ''X'' activated! Option AAA activated! Doing something...