it-swarm.com.de

Rückgabewert in einer Bash-Funktion

Ich arbeite mit einem Bash-Skript und möchte eine Funktion zum Drucken eines Rückgabewerts ausführen:

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

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

247
mindia

Obwohl bash eine return -Anweisung hat, können Sie nur den exit -Status der Funktion angeben (ein Wert zwischen 0 und 255, 0 bedeutet "Erfolg"). ). Also ist return nicht das, was Sie wollen.

Möglicherweise möchten Sie Ihre return -Anweisung in eine echo -Anweisung umwandeln. Auf diese Weise kann Ihre Funktionsausgabe in geschweiften Klammern $() erfasst werden. Dies scheint genau das zu sein, was Sie möchten.

Hier ist ein Beispiel:

function fun1(){
  echo 34
}

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

Eine andere Möglichkeit, den Rückgabewert abzurufen (wenn Sie nur eine Ganzzahl von 0 bis 255 zurückgeben möchten), ist $?.

function fun1(){
  return 34
}

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

Beachten Sie auch, dass Sie den Rückgabewert verwenden können, um boolesche Logik wie fun1 || fun2 zu verwenden, die fun2 nur dann ausführt, wenn fun1 einen 0 -Wert zurückgibt. Der Standardrückgabewert ist der Exit-Wert der letzten Anweisung, die in der Funktion ausgeführt wurde.

318
tamasgal

$(...) erfasst den Text, der mit dem Befehl in stdout an stdout gesendet wurde. return wird nicht auf stdout ausgegeben. $? enthält den Ergebniscode des letzten Befehls.

fun1 (){
  return 34
}

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

Funktionen in Bash sind keine Funktionen wie in anderen Sprachen. Sie sind tatsächlich Befehle. Funktionen werden also so verwendet, als wären sie Binärdateien oder Skripte, die von Ihrem Pfad abgerufen wurden. Aus der Sicht Ihrer Programmlogik sollte es eigentlich keinen Unterschied geben.

Shell-Befehle werden durch Pipes (alias Streams) verbunden und nicht durch grundlegende oder benutzerdefinierte Datentypen wie in "echten" Programmiersprachen. Es gibt keinen Rückgabewert für einen Befehl, vielleicht vor allem, weil es keine echte Möglichkeit gibt, ihn zu deklarieren. Es könnte auf der Manpage oder der Ausgabe von --help des Befehls auftreten, aber beide sind nur für Menschen lesbar und daher in den Wind geschrieben.

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

Wenn ein Befehl etwas zurückgeben möchte, muss er es echo an seinen Ausgabestream senden. Eine andere häufig praktizierte Methode besteht darin, den Rückgabewert in dedizierten, globalen Variablen zu speichern. Das Schreiben in den Ausgabestream ist klarer und flexibler, da es auch Binärdaten aufnehmen kann. Zum Beispiel können Sie ein BLOB einfach zurückgeben:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

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". Stellen Sie sich den Exit-Code als Bonus vor, den andere Sprachen nicht haben, oder je nach Temperament als "Schmutzeffekt" von Shell-Funktionen. Dieser Status ist gemäß Konvention 0 bei Erfolg oder eine Ganzzahl im Bereich von 1 bis 255 für etwas anderes. Um dies zu verdeutlichen: 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 mit return keinen expliziten Wert angeben, wird der Status vom letzten Befehl in einer Bash-Anweisung/Funktion/einem Befehl usw. übernommen. Es gibt also immer einen Status, und return ist nur eine einfache Möglichkeit, ihn bereitzustellen.

49

Die return -Anweisung legt den Exit-Code der Funktion fest, ähnlich wie exit für das gesamte Skript.

Der Beendigungscode für den letzten Befehl ist immer in der Variablen $? verfügbar.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
25
Austin Phillips

Das Problem bei anderen Antworten ist, dass sie entweder eine globale Funktion verwenden, die überschrieben werden kann, wenn sich mehrere Funktionen in einer Aufrufkette befinden, oder echo, was bedeutet, dass Ihre Funktion keine Diagnoseinformationen ausgeben kann (Sie werden vergessen, dass Ihre Funktion dies tut und die " result ", dh der Rückgabewert, enthält mehr Informationen als Ihr Anrufer erwartet, was zu seltsamen Fehlern führt.) oder eval, was viel zu schwer und hackig ist.

Der richtige Weg, dies zu tun, besteht darin, das Zeug der obersten Ebene in eine Funktion zu setzen und ein local mit der dynamischen Gültigkeitsregel von bash 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 je nach Aufrufer auf ein anderes Objekt zeigt! Dies unterscheidet sich vom lexikalischen Scoping, das in den meisten Programmiersprachen verwendet wird. Dies ist eigentlich eine dokumentierte Funktion , nur leicht zu übersehen und nicht sehr gut erklärt, hier ist die Dokumentation dafür (der Schwerpunkt liegt bei mir):

Variablen, die für die Funktion lokal sind, können mit dem lokalen Builtin deklariert werden. Diese Variablen sind nur für die Funktion und die von ihr aufgerufenen Befehle sichtbar.

Für jemanden mit C/C++/Python/Java/C #/Javascript-Hintergrund ist dies wahrscheinlich die größte Hürde: Funktionen in bash sind keine Funktionen, sie sind Befehle und verhalten sich so: Sie können in stdout/ausgegeben werden. stderr, sie können ein/aus leiten, sie können einen Beendigungscode zurückgeben. Grundsätzlich besteht kein Unterschied zwischen dem Definieren eines Befehls in einem Skript und dem Erstellen einer ausführbaren Datei, die über die Befehlszeile aufgerufen werden kann.

Also anstatt dein Skript so zu schreiben:

top-level code 
bunch of functions
more top-level code

schreibe es so:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

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

Siehe auch die folgende Unix- und Linux-Frage: Gültigkeitsbereich lokaler Variablen in Shell-Funktionen .

Eine andere, je nach Situation vielleicht noch bessere Lösung ist die gepostet von ya.teck , die local -n verwendet.

9
Oliver

Eine andere Möglichkeit, dies zu erreichen, ist Namensreferenzen (erfordert Bash 4.3+).

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

example RESULT
echo $RESULT
7
ya.teck

Ich mache gerne Folgendes, wenn ich in einem Skript arbeite, in dem die Funktion definiert ist:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

Ich mag das, weil ich dann Echo-Anweisungen in meine Funktionen aufnehmen kann, wenn ich will

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
5
doc

Als Ergänzung zu den hervorragenden Beiträgen anderer ist hier ein Artikel, der diese Techniken zusammenfasst:

  • setze eine globale Variable
  • legen Sie eine globale Variable fest, deren Namen Sie an die Funktion übergeben haben
  • setze den Return Code (und nimm ihn mit $?)
  • Einige Daten 'zurückmelden' (und mit MYVAR = $ abholen (myfunction))

Rückgabe von Werten aus Bash-Funktionen

3
Tom Hundt