Pasar argumentos con nombre a scripts de shell

114

¿Hay alguna manera fácil de pasar (recibir) parámetros con nombre a un script de shell?

Por ejemplo,

my_script -p_out '/some/path' -arg_1 '5'

Y en el interior los my_script.shrecibimos como:

# I believe this notation does not work, but is there anything close to it?
p_out=$ARGUMENTS['p_out']
arg1=$ARGUMENTS['arg_1']

printf "The Argument p_out is %s" "$p_out"
printf "The Argument arg_1 is %s" "$arg1"

¿Es esto posible en Bash o Zsh?

Amelio Vazquez-Reina
fuente
2
Eche un vistazo a docopt : ayuda con los parámetros con nombre y también valida la entrada
Beat

Respuestas:

34

La sintaxis probablemente más cercana a eso es:

p_out='/some/path' arg_1='5' my_script
Hauke ​​Laging
fuente
77
Relacionado con esto, si la -kopción se establece en el shell de llamada , entonces my_script p_out='/some/path' arg_1='5'tiene el mismo efecto. (Todos los argumentos en la forma de una asignación se añaden al medio ambiente, no sólo aquellas asignaciones anterior el comando.)
chepner
13
Solía ​​amar esta sintaxis, pero tiene una GRAN advertencia: después de la ejecución del comando / función, ¡esas variables aún se definirán en el alcance actual! Por ejemplo: ¡lo x=42 echo $x; echo $xque significa que en la próxima ejecución de my_script, si p_outse omite, se mantendrá en el valor pasado la última vez! ( '/some/path')
Lucas Cimon
@LucasCimon ¿No puedes unsetdespués de la primera ejecución, restablecerlos antes de la próxima ejecución?
Nikos Alexandris
2
@LucasCimon Eso no es correcto. x=42 echo $xni siquiera genera nada si $xno se definió antes.
Hauke ​​Laging
Tienes razón @HaukeLaging, gracias por corregir eso
Lucas Cimon
148

Si no le importa estar limitado a nombres de argumentos de una sola letra my_script -p '/some/path' -a5, es decir , en bash puede usar el incorporado getopts, por ejemplo

#!/bin/bash

while getopts ":a:p:" opt; do
  case $opt in
    a) arg_1="$OPTARG"
    ;;
    p) p_out="$OPTARG"
    ;;
    \?) echo "Invalid option -$OPTARG" >&2
    ;;
  esac
done

printf "Argument p_out is %s\n" "$p_out"
printf "Argument arg_1 is %s\n" "$arg_1"

Entonces puedes hacer

$ ./my_script -p '/some/path' -a5
Argument p_out is /some/path
Argument arg_1 is 5

Hay un tutorial útil de Small getopts o puede escribir help getoptsen el indicador de comandos de shell.

conductor de acero
fuente
25
Esta debería ser la respuesta aceptada
Kaushik Ghose
3
Sé que esto es un poco viejo, pero ¿por qué solo 1 letra para los argumentos?
Kevin
1
Implementé esto (pero con iy d). Cuando lo ejecuto, my_script -i asd -d asdobtengo una cadena vacía para el dargumento. Cuando lo ejecuto, my_script -d asd -i asdobtengo una cadena vacía para ambos argumentos.
Milkncookiez
3
@Milkncookiez - Tuve un problema similar - No incluí un ':' después del último argumento (una 'w' en mi caso). Una vez que agregué el ':' comenzó a funcionar como se esperaba
Derek
37

Robé esto de drupal.org , pero podrías hacer algo como esto:

while [ $# -gt 0 ]; do
  case "$1" in
    --p_out=*)
      p_out="${1#*=}"
      ;;
    --arg_1=*)
      arg_1="${1#*=}"
      ;;
    *)
      printf "***************************\n"
      printf "* Error: Invalid argument.*\n"
      printf "***************************\n"
      exit 1
  esac
  shift
done

La única advertencia es que debe usar la sintaxis my_script --p_out=/some/path --arg_1=5.

cdmo
fuente
77
La advertencia no es necesaria. :) Puede tener las siguientes condiciones:-c|--condition)
Milkncookiez
28

Uso este script y funciona de maravilla:

for ARGUMENT in "$@"
do

    KEY=$(echo $ARGUMENT | cut -f1 -d=)
    VALUE=$(echo $ARGUMENT | cut -f2 -d=)   

    case "$KEY" in
            STEPS)              STEPS=${VALUE} ;;
            REPOSITORY_NAME)    REPOSITORY_NAME=${VALUE} ;;     
            *)   
    esac    


done

echo "STEPS = $STEPS"
echo "REPOSITORY_NAME = $REPOSITORY_NAME"

Uso

bash my_scripts.sh  STEPS="ABC" REPOSITORY_NAME="stackexchange"

Resultado de la consola:

STEPS = ABC
REPOSITORY_NAME = stackexchange

STEPS y REPOSITORY_NAME están listos para usar en el script.

No importa en qué orden estén los argumentos.

JRichardsz
fuente
44
Esto es ordenado y debe ser la respuesta aceptada.
miguelmorin
15

Con zsh, usarías zparseopts:

#! /bin/zsh -
zmodload zsh/zutil
zparseopts -A ARGUMENTS -p_out: -arg_1:

p_out=$ARGUMENTS[--p_out]
arg1=$ARGUMENTS[--arg_1]

printf 'Argument p_out is "%s"\n' "$p_out"
printf 'Argument arg_1 is "%s"\n' "$arg_1"

Pero llamarías al guión con myscript --p_out foo.

Tenga en cuenta que zparseoptsno admite abreviar opciones largas o la --p_out=foosintaxis como lo getopt(3)hace GNU .

Stéphane Chazelas
fuente
¿Sabes por qué el zparseopts usa solo un guión para los argumentos mientras que en el []es 2 guiones? ¡No tiene sentido!
Timo
@Timo, ver info zsh zparseoptspara más detalles
Stéphane Chazelas
9

Se me ocurrió este guión.

while [ $# -gt 0 ]; do

   if [[ $1 == *"--"* ]]; then
        v="${1/--/}"
        declare $v="$2"
   fi

  shift
done

pásalo como my_script --p_out /some/path --arg_1 5y luego en el script puedes usar $arg_1y $p_out.

Shahzad Malik
fuente
Me gusta esta solución en KSH88 Tuve que v=``echo ${1} | awk '{print substr($1,3)}'`` typeset $v="$2"(Eliminar una marca de retroceso cada lado)
hol
-2

Si una función o una aplicación tiene más de cero argumentos, siempre tiene un último argumento.

Si desea leer el indicador de opción y los pares de valores, como en: $ ./t.sh -o output -i input -l last

Y desea aceptar un número variable de pares de opción / valor,

Y no quiero un gran árbol "si ... entonces ... más ... fi",

Luego, después de verificar un recuento de argumentos que no sea cero e incluso,

Escriba un ciclo while con estas cuatro declaraciones eval como el cuerpo, seguido de una declaración de caso utilizando los dos valores determinados en cada pasada a través del ciclo.

La parte difícil de la secuencia de comandos se demuestra aquí:

#!/bin/sh    

# For each pair - this chunk is hard coded for the last pair.
eval TMP="'$'$#"
eval "PICK=$TMP"
eval TMP="'$'$(($#-1))"
eval "OPT=$TMP"

# process as required - usually a case statement on $OPT
echo "$OPT \n $PICK"

# Then decrement the indices (as in third eval statement) 

:<< EoF_test
$ ./t.sh -o output -i input -l last
-l 
last
$ ./t.sh -o output -l last
-l 
last
$ ./t.sh  -l last
-l 
last
EoF_test
knc1
fuente
-3
mitsos@redhat24$ my_script "a=1;b=mitsos;c=karamitsos"
#!/bin/sh
eval "$1"

acabas de inyectar parámetros de línea de comando dentro del alcance del script !!

thettalos
fuente
3
Esto no funciona con la sintaxis especificada por el OP; quieren-a 1 -b mitsos -c karamitsos
Michael Mrozek