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:
- Es más portátil, y funcionará en otros shells como
dash
. - 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.gz
es igual a tar -x -z -f foo.tar.gz
. Y al igual que en tar
, ps
etc. 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 f
opciones deben ser las últimas porque el nombre del archivo sigue ( tar xzf bar.tar.gz
funciona pero tar xfz bar.tar.gz
no 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
, head
y getopts
faltantes).
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=value
y --flag value
estilos.
./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
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.
- Es una solución Bash pura, sin utilidades adicionales.
- No contamina el alcance global.
- Sin esfuerzo, le devuelve variables fáciles de usar, sobre las que podría construir más lógica.
- La cantidad de guiones antes de los parámetros no importa (
--all
es igual a -all=all
es igual aall=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:
- No admite parámetros delimitados por espacios (
-d 1
) - Los nombres de
--any-param
perderán guiones, por lo que--any-param
y-anyparam
son equivalentes -
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:
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
salidascript.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 ty
un atributo - maneja la mezcla de todo lo anterior así que
-oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute
es 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 getopt
y eval
y HEREDOC
y shift
para 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.sh
como 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...