Como faço para analisar argumentos opcionais em um script bash se nenhuma ordem for dada?

7

Estou confuso sobre como incluir argumentos / sinalizadores opcionais ao escrever um script bash para o seguinte programa:

O programa requer dois argumentos:

run_program --flag1 <value> --flag2 <value>

No entanto, existem vários sinalizadores opcionais:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Eu gostaria de executar o script bash de modo que ele leva argumentos do usuário. Se os usuários inserem apenas dois argumentos em ordem, então seria:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Mas e se algum dos argumentos opcionais estiver incluído? Eu acho que seria

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Mas e se $ 4 for dado, mas não $ 3?

    
por ShanZhengYang 19.12.2016 / 23:46

4 respostas

14

Este artigo mostra duas maneiras diferentes - shift e getopts (e discute as vantagens e desvantagens do duas abordagens).

Com shift , o script analisa $1 , decide qual ação executar e, em seguida, executa shift , movendo $2 para $1 , $3 para $2 , etc.

Por exemplo:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

Com getopts , você define as opções (curtas) na expressão while :

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Obviamente, estes são apenas snippets de código, e deixei de fora a validação - verificando se os argumentos obrigatórios flag1 e flag2 estão definidos, etc.

Qual abordagem você usa é, até certo ponto, uma questão de gosto - quão portátil você quer que seu script seja, se você pode viver apenas com opções curtas (POSIX) ou se deseja opções longas (GNU), etc. p>     

por 20.12.2016 / 00:12
1

use uma matriz.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

isso manipulará argumentos com espaços neles corretamente.

[edit] Eu estava usando a sintaxe eqivalente args=( "${args[@]}" --optflag1 "$3" ) , mas o G-Man sugeriu uma maneira melhor.

    
por 20.12.2016 / 21:43
0

Em um script de shell, os argumentos são "$ 1", "$ 2", "$ 3", etc. O número de argumentos é $ #.
Se o seu script não reconhecer opções, você pode deixar de fora a detecção de opções e tratar todos os argumentos como operandos.
Para reconhecer opções use os getopts embutidos

    
por 19.12.2016 / 23:59
0

Se as suas opções input forem posicionais (você sabe em quais lugares elas estão), e não especificadas com flags, então o que você quer é apenas construir a linha de comando. Apenas prepare os argumentos de comando para todos eles:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Se os parâmetros não forem especificados, as sequências corresonding estarão vazias e serão expandidas para nada. Observe que na última linha, não há cotações. Isso é porque você quer que o shell divida os parâmetros em palavras (para dar --flag1 e $1 como argumentos separados para o seu programa). Claro, isso vai dar errado se seus parâmetros originais contiverem espaços. Se você é o único a executar isso, então você pode deixá-lo, mas se isso é um script geral, pode ter um comportamento inesperado se o usuário insere algo com espaços. Para lidar com isso, você precisará tornar o código um pouco mais feio.

O prefixo x no teste [] está lá no caso de $3 ou $4 estarem vazios. Nesse caso, o bash expandiria [ FALSE != $3 ] para [ FALSE != ] , que é um erro de sintaxe, portanto, outro caractere arbitrário está lá para protegê-lo. Esta é uma maneira muito comum, você verá em muitos scripts.

Eu defino OPTFLAG1 e o resto deles como "" no começo apenas para ter certeza (no caso de eles estarem definidos para algo antes), mas se eles não foram realmente declarados no ambiente, então você não precisa estritamente fazer isso.

Algumas observações adicionais:

  • Você pode, na verdade, apenas receber os parâmetros da mesma maneira que runprogram : com sinalizadores. É disso que o John N está falando. É aí que getopts se torna útil.
  • Usar FALSE para esse propósito é um pouco fora do padrão e bastante longo. Normalmente, um usa um único caractere (possivelmente - ) para sinalizar um valor vazio ou apenas passa uma string vazia, como "" , se isso não for um valor válido do parâmetro. Cadeia vazia também faz o teste mais curto, use apenas if [ -n "$3" ] .
  • Se houver apenas um sinalizador opcional, ou se eles são dependentes, então você nunca poderá ter OPTFLAG2, mas não OPTFLAG1, então você pode simplesmente ignorar aqueles que não deseja definir. Se você usa "" para parâmetros vazios, como sugerido acima, você pode pular todos os vazios à direita de qualquer maneira.

Este método é basicamente o modo como as opções são passadas para compiladores em Makefiles.

E novamente - se as entradas puderem conter espaços, ficará feio.

    
por 20.12.2016 / 09:19