bash - receive - shell script arguments
Shell Script: ¿es posible mezclar getopts con parámetros posicionales? (7)
Existen algunos estándares para el procesamiento de opciones de Unix, y en la programación de shell, getopts
es la mejor manera de aplicarlos. Casi cualquier lenguaje moderno (perl, python) tiene una variante en getopts
.
Este es solo un ejemplo rápido:
command [ options ] [--] [ words ]
Cada opción debe comenzar con un guión,
-
, y debe consistir en un solo carácter.El proyecto GNU introdujo Long Options, comenzando con dos guiones
--
, seguido de una palabra completa,--long_option
. El proyecto AST KSH tiene un getopts que también admite opciones largas, y opciones largas que comienzan con un solo guión,-
, como enfind(1)
.Las opciones pueden o no esperar argumentos.
Cualquier palabra que no comience con un guión,
-
, terminará el procesamiento de la opción.La cadena
--
debe omitirse y finalizará el procesamiento de la opción.Los argumentos restantes se dejan como parámetros posicionales.
Open Group tiene una sección sobre la Utility Argument Syntax
The Art of Unix Programming, de Eric Raymond, tiene un capítulo sobre las opciones tradicionales de Unix para las letras de opción y su significado.
Quiero diseñar un script de shell como un contenedor para un par de scripts. Me gustaría especificar parámetros para myshell.sh
utilizando getopts
y pasar los parámetros restantes en el mismo orden al script especificado.
Si myshell.sh
se ejecuta como:
myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
Todo lo anterior debe poder llamar como
test.sh param1 param2 param3
¿Es posible utilizar los parámetros de opciones en myshell.sh
y publicar los parámetros restantes en el script subyacente?
Mix opts y args:
ARGS=""
echo "options :"
while [ $# -gt 0 ]
do
unset OPTIND
unset OPTARG
while getopts as:c: options
do
case $options in
a) echo "option a no optarg"
;;
s) serveur="$OPTARG"
echo "option s = $serveur"
;;
c) cible="$OPTARG"
echo "option c = $cible"
;;
esac
done
shift $((OPTIND-1))
ARGS="${ARGS} $1 "
shift
done
echo "ARGS : $ARGS"
exit 1
Resultado:
bash test.sh -a arg1 arg2 -s serveur -c cible arg3
options :
option a no optarg
option s = serveur
option c = cible
ARGS : arg1 arg2 arg3
Pensé en una forma en que getopts
se puede extender para realmente mezclar opciones y parámetros posicionales. La idea es alternar entre getopts
llamada y asignar cualquier parámetro posicional encontrado a n1
, n2
, n3
, etc.
parse_args() {
_parse_args 1 "$@"
}
_parse_args() {
local n="$1"
shift
local options_func="$1"
shift
local OPTIND
"$options_func" "$@"
shift $(( OPTIND - 1 ))
if [ $# -gt 0 ]; then
eval test -n /${n$n+x}
if [ $? -eq 0 ]; then
eval n$n="/$1"
fi
shift
_parse_args $(( n + 1 )) "$options_func" "$@"
fi
}
Luego, en el caso del OP, podrías usarlo así:
main() {
local n1='''' n2='''' n3=''''
local duration hostname script
parse_args parse_main_options "$@"
echo "n1 = $n1"
echo "n2 = $n2"
echo "n3 = $n3"
echo "duration = $duration"
echo "hostname = $hostname"
echo "script = $script"
}
parse_main_options() {
while getopts d:h:s: opt; do
case "$opt" in
d) duration="$OPTARG" ;;
h) hostname="$OPTARG" ;;
s) script="$OPTARG" ;;
esac
done
}
main "$@"
Al ejecutarlo, se muestra el resultado:
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
n1 = param1
n2 = param2
n3 = param3
duration = waittime
hostname = hostname
script = test.sh
Solo una prueba de concepto, pero tal vez sea útil para alguien.
Nota: hay un error si una función que usa parse_args
llama a otra función que usa parse_args
y la función externa declara, por ejemplo, local n4=''''
, pero la interna no y 4 o más parámetros posicionales pasan a la función interna
Perdón por comentar sobre un hilo viejo, pero pensé en postear para aquellos, como yo, que estábamos buscando cómo hacer esto ...
Quería hacer algo similar al OP, y encontré la información relevante que requería here y here
Básicamente, si quieres hacer algo como:
script.sh [options] ARG1 ARG2
A continuación, obtenga sus opciones de esta manera:
while getopts "h:u:p:d:" flag; do
case "$flag" in
h) HOSTNAME=$OPTARG;;
u) USERNAME=$OPTARG;;
p) PASSWORD=$OPTARG;;
d) DATABASE=$OPTARG;;
esac
done
Y luego puedes obtener tus argumentos posicionales como este:
ARG1=${@:$OPTIND:1}
ARG2=${@:$OPTIND+1:1}
Más información y detalles están disponibles a través del enlace de arriba.
¡¡Espero que ayude!!
Puedes probar este truco: después de while loop con optargs, solo usa este fragmento
#shift away all the options so that only positional agruments
#remain in $@
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$@"
Sin embargo, este enfoque tiene un error:
- todas las opciones después del primer argumento posicional son asumidas por getopts y son consideradas como argumentos posicionales - event aquellas que son correctas (ver resultado de la muestra: -m y -c están entre los argumentos posicionales)
Tal vez tiene incluso más errores ...
Mira el ejemplo completo:
while getopts :abc opt; do
case $opt in
a)
echo found: -a
;;
b)
echo found: -b
;;
c)
echo found: -c
;;
/?) echo found bad option: -$OPTARG
;;
esac
done
#OPTIND-1 now points to the first arguments not beginning with -
#shift away all the options so that only positional agruments
#remain in $@
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$@"
echo "positional: $POSITIONAL"
Salida:
[root@host ~]# ./abc.sh -abc -de -fgh -bca haha blabla -m -c
found: -a
found: -b
found: -c
found bad option: -d
found bad option: -e
found bad option: -f
found bad option: -g
found bad option: -h
found: -b
found: -c
found: -a
positional: haha blabla -m -c
Solo prepare un quickie, que maneja fácilmente una mezcla de opciones y parámetros posicionales (dejando solo parametros posicionales en $ @):
#!/bin/bash
while [ ${#} -gt 0 ];do OPTERR=0;OPTIND=1;getopts "p:o:hvu" arg;case "$arg" in
p) echo "Path: [$OPTARG]" ;;
o) echo "Output: [$OPTARG]" ;;
h) echo "Help" ;;
v) echo "Version" ;;
/?) SET+=("$1") ;;
*) echo "Coding error: ''-$arg'' is not handled by case">&2 ;;
esac;shift;[ "" != "$OPTARG" ] && shift;done
[ ${#SET[@]} -gt 0 ] && set "" "${SET[@]}" && shift
echo -e "=========/nLeftover (positional) parameters (count=$#) are:"
for i in `seq $#`;do echo -e "/t$i> [${!i}]";done
Muestra de salida:
[root@hots:~]$ ./test.sh ''aa bb'' -h -v -u -q ''cc dd'' -p ''ee ff'' ''gg hh'' -o ooo
Help
Version
Coding error: ''-u'' is not handled by case
Path: [ee ff]
Output: [ooo]
=========
Leftover (positional) parameters (count=4) are:
1> [aa bb]
2> [-q]
3> [cc dd]
4> [gg hh]
[root@hots:~]$
getopts
no analizará la mezcla de las opciones param1
y -n
.
Es mucho mejor poner param1-3 en opciones como otras.
Además, puede usar bibliotecas ya existentes, como shflags . Es bastante inteligente y fácil de usar.
Y la última forma es escribir su propia función para analizar params sin getopts, simplemente iterando todos los parámetros a través de la construcción del case
. Es la forma más difícil, pero es la única manera de hacer coincidir exactamente sus expectativas.