Die Bash-Funktion mit `getopts` funktioniert nur beim ersten Start

9

Ich habe die Funktion fin Bash anhand des folgenden Beispiels definiert (unter "Eine Option mit einem Argument"):

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
}

Während sie ein Skript verwenden, definiere ich die Funktion direkt in der Shell.

Wenn ich Bash zum ersten Mal starte und die Funktion definiere, funktioniert alles: f -a 123Drucken -a was triggered, Parameter: 123. Aber wenn ich ein zweites Mal genau dieselbe Zeile laufen lasse, wird nichts gedruckt .

Was verursacht dieses Verhalten? Es passiert in Bash 3.2 und 4.3, aber es funktioniert gut in Zsh 5.1. Dies ist überraschend, da das Beispiel für Bash und nicht für Zsh sein sollte.

Shadowtalker
quelle

Antworten:

15

bash getopts verwenden eine Umgebungsvariable OPTIND , um das zuletzt verarbeitete Optionsargument zu verfolgen. Die Tatsache, dass OPTINDnicht jedes Mal getopts, wenn Sie in derselben Shell-Sitzung aufgerufen haben, automatisch zurückgesetzt wurde , nur wenn die Shell aufgerufen wurde. Also haben Sie ab dem zweiten Mal getoptsmit denselben Argumenten in derselben Sitzung angerufen , OPTINDwurden nicht geändert, getoptsdachten, es hätte den Job gemacht und nichts getan.

Sie können OPTINDmanuell zurücksetzen , damit es funktioniert:

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

oder fügen Sie die Funktion einfach in ein Skript ein und rufen Sie das Skript mehrmals auf.


zsh getopts ist etwas anders. OPTINDwurde normalerweise jedes Mal beim Beenden der Shell-Funktion auf 1 zurückgesetzt.

cuonglm
quelle
Ich akzeptiere diese Antwort pedantisch, weil sie etwas vollständiger ist als die andere
Shadowtalker
unset opt OPTARG OPTINDVor jedem while getopts...Anruf hinzugefügt und funktioniert jetzt einwandfrei. Danke :)
Deep
6

Es ist eine Gewohnheit Gottes, lokale Variablen in jeder Funktion zu deklarieren. Wenn Sie $ opt, $ OPTARG und $ OPTIND deklarieren, funktionieren getopts jedes Mal, wenn Sie die Funktion aufrufen. Lokale Variablen werden nach Beendigung der Funktion verworfen.

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

  while getopts ":a:" opt; do
    echo $opt is $OPTARG
  done
}
Thomasso
quelle
1
Nur zu erklären hat bei mir nicht funktioniert. Ich musste unset opt OPTARG OPTINDvor die while getopts...Aussage setzen, dass der Wert jeder dieser Variablen nicht gesetzt war. Wenn ich dies nicht tat und nur die Deklaration machte, da es in der gegebenen Bash-Sitzung, in der ich bereits getoptseinmal verwendet hatte OPTIND, unverändert blieb
Deep
2

Sie müssen zu OPTIND=1Beginn der Funktion einstellen f. Standardmäßig ist es 1, wird dann aber erhöht, wenn Ihre Argumente analysiert werden. Wenn Sie getoptserneut anrufen, wird dort fortgesetzt, wo es aufgehört hat. Sie können dies sehen, wenn Ihr zweiter Anruf lautet:

f -a 123 -a 999

wenn der 999 gedruckt wird.

meuh
quelle
Na sicher! Wenn ich von R und Python komme, stolpere ich immer über das Scoping in der Shell. Funktioniert es, wenn ich local OPTINDinnerhalb der Funktion deklariere, bevor sie getoptsaufgerufen wird?
Shadowtalker
1
sollte tun, gute Lösung.
Meuh
1

Wenn getoptes aufgerufen wird, verfolgt es die verarbeiteten Optionen durch die Variable OPTIND.

Versuche Folgendes:

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

Ausbeute:

./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

Als solches könnten Sie etwas tun wie:

OPTIND=1

zu Beginn der Funktion. Oder die, je nach Situation und meist besser:

local OPTIND

Wenn dies OPTINDnicht verwendet wird, da die Funktion implementiert ist, würde die while-Schleife für immer bestehen bleiben. Man kann es auch verwenden, um die Verarbeitung von Argumenten fortzusetzen, nach einem Fehler oder was auch immer, eine andere Funktion aufzurufen, wenn x oder y, und es wird dort fortgesetzt, wo zuvor aufgehört hat usw.

Runium
quelle