556 Stimmen

Rückgabewert in einer Bash-Funktion

Ich arbeite mit einem Bash-Skript und ich möchte eine Funktion ausführen, um einen Rückgabewert auszugeben:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

Wenn ich fun2 ausführe, wird "34" nicht ausgegeben. Warum ist das so?

680voto

tamasgal Punkte 22710

Auch wenn Bash eine return-Anweisung hat, ist das einzige, was man damit angeben kann, der eigene exit-Status der Funktion (ein Wert zwischen 0 und 255, 0 bedeutet "Erfolg"). Also ist return nicht das, was du willst.

Du solltest vielleicht deine return-Anweisung in eine echo-Anweisung umwandeln - auf diese Weise kann der Output deiner Funktion mit Hilfe von $()-Klammern erfasst werden, was genau das ist, was du willst.

Hier ist ein Beispiel:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Ein weiterer Weg, um den Rückgabewert zu erhalten (wenn du nur eine Integer-Zahl von 0-255 zurückgeben möchtest), ist $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Außerdem solltest du beachten, dass du den Rückgabewert verwenden kannst, um Boolesche Logik zu ermöglichen - zum Beispiel wird fun1 || fun2 nur fun2 ausführen, wenn fun1 einen anderen Wert als 0 zurückgibt. Der Standard-Rückgabewert ist der Exit-Wert der zuletzt ausgeführten Anweisung innerhalb der Funktion.

120voto

Andreas Spindler Punkte 6601

Funktionen in Bash sind nicht Funktionen wie in anderen Sprachen; sie sind tatsächlich Befehle. Funktionen werden also verwendet, als wären sie Binärdateien oder Skripte, die aus Ihrem Pfad abgerufen werden. Aus der Perspektive Ihrer Programmlogik sollte es eigentlich keinen Unterschied geben.

Shell-Befehle sind durch Pipes (auch Ströme genannt) verbunden, und nicht grundlegende oder benutzerdefinierte Datentypen, wie in "echten" Programmiersprachen. Es gibt keine Rückgabewerte für einen Befehl, vielleicht hauptsächlich, weil es keine wirkliche Möglichkeit gibt, ihn zu deklarieren. Es könnte auf der man-Seite oder der --help-Ausgabe des Befehls auftreten, aber beides ist nur menschenlesbar und daher für die Katz geschrieben.

Wenn ein Befehl Eingaben erhalten möchte, liest er sie aus seinem Eingabestrom oder der Argumentenliste. In beiden Fällen müssen Textzeichenfolgen geparst werden.

Wenn ein Befehl etwas zurückgeben möchte, muss er es an seinen Ausgabestrom echoen. Eine weitere häufig praktizierte Methode besteht darin, den Rückgabewert in dedizierte globale Variablen zu speichern. Das Schreiben in den Ausgabestrom ist klarer und flexibler, da er auch binäre Daten enthalten kann. Zum Beispiel können Sie leicht einen BLOB zurückgeben:

encrypt() {
    gpg -c -o- $1 # Verschlüssele Daten in der Datei in die Standardausgabe (fragt nach einem Passwort)
}

encrypt public.dat > private.dat # Schreibe das Ergebnis der Funktion in eine Datei

Wie andere in diesem Thread geschrieben haben, kann der Aufrufer auch die Befehlsersetzung $() verwenden, um die Ausgabe zu erfassen.

Parallel dazu würde die Funktion den Exit-Code von gpg (GnuPG) "zurückgeben". Denken Sie dabei an den Exit-Code als Bonus, den andere Sprachen nicht haben, oder je nach Temperament als "Schmutzeffekt" von Shell-Funktionen. Dieser Status ist konventionell bei Erfolg 0 oder eine Ganzzahl im Bereich 1-255 für etwas anderes. Um das klarzustellen: return (wie exit) kann nur einen Wert von 0-255 annehmen, und andere Werte als 0 sind nicht unbedingt Fehler, wie oft behauptet wird.

Wenn Sie keinen expliziten Wert mit return angeben, wird der Status vom letzten Befehl in einer Bash-Anweisung/Funktion/Befehl usw. genommen. Es gibt also immer einen Status, und return ist nur ein einfacher Weg, ihn zu liefern.

103voto

$(...) erfasst den Text, der an die Standardausgabe vom darin enthaltenen Befehl gesendet wird. return gibt nicht an die Standardausgabe aus. $? enthält den Ergebniscode des letzten Befehls.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}

72voto

Oliver Punkte 24156

Das Problem bei anderen Antworten ist, dass sie entweder ein globales verwenden, das überschrieben werden kann, wenn mehrere Funktionen in einer Aufrufkette sind, oder echo, was bedeutet, dass Ihre Funktion keine Diagnoseinformationen ausgeben kann (Sie werden vergessen, dass Ihre Funktion dies tut, und das "Ergebnis", d.h. der Rückgabewert, wird mehr Informationen enthalten, als Ihr Aufrufer erwartet, was zu seltsamen Fehlern führen kann), oder eval, was viel zu schwerfällig und heftig ist.

Der richtige Weg, dies zu tun, besteht darin, die Top-Level-Dinge in eine Funktion zu setzen und Bash's dynamische Scoping-Regel mit einem local zu verwenden. Beispiel:

func1()
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

Dies gibt aus

nothing
hi
bye

Dynamisches Scoping bedeutet, dass ret_val auf ein anderes Objekt zeigt, abhängig vom Aufrufer! Dies unterscheidet sich vom lexikalischen Scoping, das von den meisten Programmiersprachen verwendet wird. Dies ist tatsächlich eine dokumentierte Funktion, die leicht zu übersehen ist und nicht sehr gut erklärt wird. Hier ist die Dokumentation dazu (die Betonung liegt bei mir):

Variablen, die nur in der Funktion und den von ihr aufgerufenen Befehlen sichtbar sind, können mit dem lokalen Befehl deklariert werden.

Für jemanden mit einem C, C++, Python, Java, C# oder JavaScript-Hintergrund ist dies wahrscheinlich das größte Hindernis: Funktionen in bash sind nicht wirklich Funktionen, sondern Befehle und verhalten sich als solche: sie können auf stdout/stderr ausgeben, sie können ein- und ausleiten und sie können einen Exit-Code zurückgeben. Grundsätzlich gibt es keinen Unterschied zwischen der Definition eines Befehls in einem Skript und der Erstellung eines ausführbaren Programms, das von der Befehlszeile aufgerufen werden kann.

Also anstatt Ihr Skript so zu schreiben:

Spitzencodierung
Haufen von Funktionen
Mehr Spitzencodierung

schreiben Sie es so:

# Definieren Sie Ihre Hauptfunktion, die alle Spitzencodierung enthält
main()
Haufen von Funktionen
# Hauptfunktion aufrufen
main

wobei main() ret_val als local deklariert und alle anderen Funktionen Werte über ret_val zurückgeben.

Siehe auch die Frage auf Unix & Linux Bereich lokaler Variablen in Shell-Funktionen.

Ein weiterer, vielleicht sogar besserer Lösungsvorschlag, je nach Situation, ist derjenige, der von ya.teck veröffentlicht wurde und local -n verwendet.

45voto

ya.teck Punkte 1672

Ein weiterer Weg, dies zu erreichen, sind Namensreferenzen (erfordert Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example ERGEBNIS
echo $ERGEBNIS

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X