Passando argumentos nomeados para shell scripts

114

Existe alguma maneira fácil de passar (receber) parâmetros nomeados para um script de shell?

Por exemplo,

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

E por dentro my_script.shreceba-os 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"

Isso é possível no Bash ou no Zsh?

Amelio Vazquez-Reina
fonte
2
ter um olhar para docopt - ajuda com parâmetros nomeados e não validação de entrada, também
Bata

Respostas:

34

A sintaxe provavelmente mais próxima disso é:

p_out='/some/path' arg_1='5' my_script
Hauke ​​Laging
fonte
7
Relacionado a isso, se a -kopção estiver configurada no shell de chamada , terá my_script p_out='/some/path' arg_1='5'o mesmo efeito. (Todos os argumentos na forma de uma atribuição são adicionadas ao meio ambiente, não apenas as atribuições que precede o comando.)
chepner
13
Eu adorava essa sintaxe, mas ela tem uma grande ressalva: após a execução do comando / função, essas variáveis ​​ainda serão definidas no escopo atual! Ex .: O x=42 echo $x; echo $xque significa que na próxima execução de my_script, se p_outfor omitido, permanecerá no valor passado na última vez !! ( '/some/path')
Lucas Cimon
@LucasCimon Você não pode unsetapós a primeira execução, redefini-los antes da próxima execução?
Nikos Alexandris
2
@LucasCimon Isso não está correto. x=42 echo $xnem gera nada se $xnão tiver sido definido antes.
Hauke ​​Laging 22/03/19
Você está certo @HaukeLaging, obrigado por corrigir essa
Lucas Cimon
148

Se você não se importa em se limitar a nomes de argumentos de uma letra my_script -p '/some/path' -a5, ou seja , no bash, você pode usar o built-in getopts, por exemplo

#!/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"

Então você pode fazer

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

Há um tutorial útil sobre getopts pequenos ou você pode digitar help getoptsno prompt do shell.

chave de aço
fonte
25
Esta deve ser a resposta aceita
Kaushik Ghose
3
Eu sei que isso é um pouco antigo, mas por que apenas uma letra para os argumentos?
28417 Kevin
11
Eu implementei isso (mas com ie d). Quando o executo, my_script -i asd -d asdrecebo uma string vazia para o dargumento. Quando o executo, my_script -d asd -i asdrecebo uma string vazia para ambos os argumentos.
22818 Milkncookiez
3
@Milkncookiez - tive um problema semelhante - não incluí um ':' após o último argumento (um 'w' no meu caso). Depois que adicionei o ':', ele começou a funcionar como esperado
Derek
37

Eu roubei isso do drupal.org , mas você poderia fazer algo assim:

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

A única ressalva é que você precisa usar a sintaxe my_script --p_out=/some/path --arg_1=5.

cdmo
fonte
7
A ressalva não é necessária. :) Você pode ter as condições da seguinte forma:-c|--condition)
Milkncookiez
28

Eu uso esse script e funciona como um encanto:

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 do console:

STEPS = ABC
REPOSITORY_NAME = stackexchange

STEPS e REPOSITORY_NAME estão prontos para uso no script.

Não importa em que ordem os argumentos estão.

JRichardsz
fonte
4
Isso é legal e deve ser a resposta aceita.
miguelmorin
15

Com zsh, você usaria 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"

Mas você chamaria o script com myscript --p_out foo.

Note que zparseoptsnão suporta abreviar opções longas ou a --p_out=foosintaxe como o GNU getopt(3).

Stéphane Chazelas
fonte
Você sabe por que o zparseopts usa apenas um traço para os argumentos, enquanto nos []2 traços? Não faz sentido!
Timo
@Timo, consulte info zsh zparseoptspara mais detalhes
Stéphane Chazelas
9

Eu só vim com esse script

while [ $# -gt 0 ]; do

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

  shift
done

passe como my_script --p_out /some/path --arg_1 5e então no script você pode usar $arg_1e $p_out.

Shahzad Malik
fonte
Gosto esta solução em ksh88 I teve de v=``echo ${1} | awk '{print substr($1,3)}'`` typeset $v="$2"(Retirar uma crase cada lado)
hol
-2

Se uma função ou aplicativo tiver mais de zero argumentos, ele sempre terá um último argumento.

Se você deseja ler os pares de sinalizador e valor de opção, como em: $ ./t.sh -o output -i input -l last

E você deseja aceitar um número variável de pares de opção / valor,

E não quero uma árvore enorme "se .. então .. mais .. fi",

Depois de verificar uma contagem de argumentos diferente de zero e par,

Escreva um loop while com essas quatro instruções eval como o corpo, seguido de uma instrução case usando os dois valores determinados em cada passagem pelo loop.

A parte complicada do script é demonstrada aqui:

#!/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
fonte
-3
mitsos@redhat24$ my_script "a=1;b=mitsos;c=karamitsos"
#!/bin/sh
eval "$1"

você acabou de injetar parâmetros de linha de comando no escopo do script !!

thettalos
fonte
3
Isso não funciona com a sintaxe especificada pelo OP; eles querem-a 1 -b mitsos -c karamitsos
Michael Mrozek