A função Bash com 'getopts' funciona apenas na primeira vez em que é executada

6

Eu defini a função f no Bash com base no exemplo aqui (em "Uma opção com um argumento "):

f () {
  while getopts ":a:" opt; do
    case $opt in
      a)
        echo "-a was triggered, Parameter: $OPTARG" >&2
        ;;
      \?)
        echo "Invalid option: -$OPTARG" >&2
        return 1
        ;;
      :)
        echo "Option -$OPTARG requires an argument." >&2
        return 1
        ;;
    esac
  done
}

Considerando que eles usam um script, eu defino diretamente a função no shell.

Quando eu inicio o Bash pela primeira vez e defino a função, tudo funciona: f -a 123 imprime -a was triggered, Parameter: 123 . Mas quando eu corro exatamente a mesma linha pela segunda vez, nada é impresso .

O que está causando esse comportamento? Isso acontece no Bash 3.2 e 4.3, mas funciona bem no Zsh 5.1. Isso é surpreendente porque o exemplo deveria ser para Bash, não para Zsh.

    
por shadowtalker 03.10.2015 / 19:36

4 respostas

10

bash getopts use uma variável de ambiente OPTIND para acompanhar o último argumento processado. O fato de que OPTIND não foi redefinido automaticamente sempre que você chamou getopts na mesma sessão de shell, somente quando o shell foi chamado. Então, da segunda vez que você chamou getopts com os mesmos argumentos na mesma sessão, OPTIND não foi alterado, getopts achou que tinha feito o trabalho e não fez nada.

Você pode redefinir o OPTIND manualmente para que funcione:

$ OPTIND=1
$ f -a 123
-a was triggered, Parameter: 123

ou apenas coloque a função em um script e chame o script várias vezes.

zsh getopts é um pouco diferente. OPTIND foi normalmente redefinido para 1 a cada vez que sair da função shell.

    
por 03.10.2015 / 20:03
5

É um hábito de Deus declarar variáveis locais em qualquer função. Se você declarar $ opt, $ OPTARG e $ OPTIND, então o getopts funcionará sempre que você chamar a função. Variáveis locais são descartadas após a conclusão da função.

#!/bin/bash
function some_func {
  declare opt
  declare OPTARG
  declare OPTIND

  while getopts ":a:" opt; do
    echo $opt is $OPTARG
  done
}
    
por 08.12.2017 / 14:13
2

Você precisa definir OPTIND=1 no início da função f . Por padrão, é 1, mas depois é incrementado à medida que seus argumentos são analisados. Quando você chama getopts novamente, ele continua de onde parou. Você pode ver isso se sua segunda chamada for:

f -a 123 -a 999

quando imprimirá o 999.

    
por 03.10.2015 / 20:01
1

Quando getopt é chamado, rastreie as opções processadas pela variável OPTIND .

Tente o seguinte:

#!/bin/bash

f () {
    printf "Intro OPTIND: %d\n" "$OPTIND"
    while getopts ":a:b:" opt; do
        printf "Current OPTIND: %d\n" "$OPTIND"
        case $opt in
            a)
                echo "-a was triggered, Parameter: $OPTARG" >&2
                ;;
            b)
                echo "-b was triggered, Parameter: $OPTARG" >&2
                ;;
        esac
    done
    printf "Exit OPTIND: %d\n" "$OPTIND"
}

echo "Run #1"
f "$@"
echo "Run #2"
f "$@"

Rendimento:

./test -a foo -b bar
Run #1
Intro OPTIND: 1
Current OPTIND: 3
-a was triggered, Parameter: foo
Current OPTIND: 5
-b was triggered, Parameter: bar
Exit OPTIND: 5
Run #2
Intro OPTIND: 5
Exit OPTIND: 5

Assim, você pode fazer algo como:

OPTIND=1

no início da função. Ou, dependendo da situação, e geralmente melhor:

local OPTIND

Se OPTIND não foi usado, como a função é implementada, o loop while iria para sempre. Pode-se também usá-lo para retomar o processamento de argumentos, após falhar ou o que quer que seja, chamar uma função diferente se x ou y e retomar onde parou anteriormente, etc.

    
por 03.10.2015 / 20:05

Tags