Rückgabewert in einer Bash-Funktion

305

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 ausführe fun2, wird "34" nicht gedruckt. Warum ist das so?

Mindia
quelle
8
returnin deinem Fall ist im Wesentlichen der gleiche wie exit codeder Bereich von 0 - 255. Verwenden Sie echowie von @septi vorgeschlagen. Exit-Codes können mit erfasst werden $?.
devnull
1
In diesem Fall ist es viel flexibler, Echo bereits in fun1 zu verwenden. Es ist die Idee der Unix-Programmierung: Echo sendet die Ergebnisse an die Standardausgabe, die dann von anderen Funktionen mit res = $ (fun1) wiederverwendet oder direkt an andere Funktionen weitergeleitet werden kann:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
Der richtige Weg, dies zu tun, besteht darin, die Inhalte der obersten Ebene in eine Funktion einzufügen und eine lokale Regel mit der dynamischen Gültigkeitsregel von bash zu verwenden. Ich werde eine Antwort erstellen, um zu demonstrieren, dass es sich nicht um eine bekannte, sondern vollständig unterstützte Funktion handelt.
Oliver
Siehe auch: stackoverflow.com/a/8743103/12887
Jonathan Tran

Antworten:

374

Obwohl bash eine returnAnweisung hat, können Sie damit nur den eigenen exitStatus der Funktion angeben (ein Wert zwischen 0und 255, 0 bedeutet "Erfolg"). Also returnist nicht was du willst.

Möglicherweise möchten Sie Ihre returnAnweisung in eine echoAnweisung konvertieren. 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 0-255 zurückgeben möchten), ist $?.

function fun1(){
  return 34
}

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

Beachten Sie außerdem, dass Sie den Rückgabewert verwenden können, um eine boolesche Logik zu verwenden, fun1 || fun2die nur ausgeführt wird, fun2wenn fun1ein 0Wert zurückgegeben wird. Der Standardrückgabewert ist der Exit-Wert der letzten innerhalb der Funktion ausgeführten Anweisung.

Tamasgal
quelle
2
Sie müssen ausführen fun1und dann wird der Rückgabewert in gespeichert $?. Obwohl ich das nicht empfehlen würde ...
Tamasgal
9
Warum nicht verwenden $??
Pithikos
147
Nein, ich brauche den verdammten Rückgabewert . Zum Teufel mit Echo.
Tomáš Zato - Wiedereinsetzung Monica
7
@Blauhirn In dieser Umgebung wird mit diesem ||Konstrukt ein Exit-Code von 0 als Erfolg und daher als "wahr" angesehen. Nicht Null ist ein Fehler und daher falsch. fun1 || fun2Stellen Sie sich eine Abkürzung für "wenn fun1 Erfolg zurückgibt oder fun2 Erfolg zurückgibt" vor, anstatt einen Operator für die tatsächlichen Rückgabewerte selbst.
DavidA
6
Was ärgerlich ist, ist, dass eine Funktion, die Daten bereitstellen soll, nicht auch andere Dinge an stdout zurückgeben kann, da der Aufrufer, der $ () verwendet, dies ebenfalls empfängt und verwirrt wird oder die Ausgabe analysieren muss. Globale Variablen sind nicht großartig, da es nur eine Frage der Zeit ist, bis Sie dieselbe globale Variable an zwei Stellen verwenden, die verschachtelt sind und Daten verloren gehen können. Es sollten separate Kanäle zum Drucken von Daten und zum Zurücksenden von Daten vorhanden sein.
Oliver
68

$(...)Erfasst den Text, der mit dem darin enthaltenen Befehl an stdout gesendet wurde. returnwird nicht an stdout ausgegeben. $?enthält den Ergebniscode des letzten Befehls.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
quelle
6
Ja returnwird für die Einstellung verwendet, bei der es sich um $?die handelt exit status. In dem obigen Beispiel fun1ist exit statuswäre 34. Beachten Sie $(...)außerdem , dass neben stdout auch stderr aus dem angegebenen Befehl erfasst wird.
Swoop81
59

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

Shell-Befehle sind durch Pipes (auch bekannt als Streams) verbunden und nicht durch grundlegende oder benutzerdefinierte Datentypen wie in "echten" Programmiersprachen. Es gibt keinen Rückgabewert für einen Befehl, vielleicht hauptsächlich, weil es keine echte Möglichkeit gibt, ihn zu deklarieren. Es kann auf der Manpage oder der --helpAusgabe des Befehls auftreten, aber beide sind nur für Menschen lesbar und daher in den Wind geschrieben.

Wenn ein Befehl eine Eingabe erhalten möchte, liest er ihn 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 echoes an seinen Ausgabestream zurückgeben. 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. Sie können beispielsweise 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 555 für etwas anderes. Um dies klar zu machen: return(like 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 returnangeben, wird der Status aus dem letzten Befehl in einer Bash-Anweisung / Funktion / Befehl usw. übernommen. Es gibt also immer einen Status und es returnist nur eine einfache Möglichkeit, ihn bereitzustellen.

Andreas Spindler
quelle
4
+1 für die Erklärung von Funktionen und Befehlen und wie sich dies auf die Idee auswirkt, Daten an den Anrufer zurückzusenden
Oliver
4
+1 für die Erklärung, dass es bei der Shell-Programmierung darum geht, Befehle über Pipes zu verbinden. Andere Programmiersprachen erstellen Funktionen über Rückgabetypen. Bash erstellt Befehle über Textströme.
Jrahhali
29

Die returnAnweisung legt den Exit-Code der Funktion fest, ähnlich wie exitfür das gesamte Skript.

Der Exit-Code 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
}
Austin Phillips
quelle
21

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 echodass Ihre Funktion keine Diagnoseinformationen ausgeben kann (Sie werden vergessen, dass Ihre Funktion dies tut und das "Ergebnis", dh zurück Wert, enthält mehr Informationen als Ihr Anrufer erwartet, was zu einem seltsamen Fehler führt) oder evalder viel zu schwer und hackig ist.

Der richtige Weg, dies zu tun, besteht darin, das Material der obersten Ebene in eine Funktion localeinzufügen und eine mit Bashs dynamische Scoping-Regel 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_valje nach Anrufer auf ein anderes Objekt verweist! Dies unterscheidet sich vom lexikalischen Scoping, das in den meisten Programmiersprachen verwendet wird. Dies ist eigentlich eine dokumentierte Funktion , die leicht zu übersehen und nicht sehr gut erklärt ist. Hier ist die Dokumentation dazu (Schwerpunkt liegt bei mir):

Für die Funktion lokale Variablen können mit dem lokalen integrierten Element deklariert werden. Diese Variablen sind nur für die Funktion und die von ihr aufgerufenen Befehle sichtbar .

Für jemanden mit einem 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 als solche: Sie können an stdout/ ausgeben stderr, sie können einleiten / out können sie einen Exit-Code zurückgeben. Grundsätzlich gibt es keinen 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  

Dabei main()deklariert ret_valas localund alle anderen Funktionen geben Werte über zurück ret_val.

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

Eine andere, vielleicht sogar bessere Lösung je nach Situation, ist derjenige von ya.teck geschrieben welche Anwendungen local -n.

Oliver
quelle
17

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

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

example RESULT
echo $RESULT
ya.teck
quelle
3
Jeder, der sich fragt, was er -n <name>=<reference>tut: macht die neu erstellte Variable zu einem Verweis auf eine andere, auf die verwiesen wird <reference>. Weitere Zuweisungen an <name>werden für die referenzierte Variable durchgeführt.
Valerio
7

Ich möchte Folgendes tun, wenn ich in einem Skript ausgeführt werde, 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 Echoanweisungen in meine Funktionen aufnehmen kann, wenn ich möchte

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

Als Ergänzung zu den hervorragenden Beiträgen anderer finden Sie hier einen Artikel, in dem diese Techniken zusammengefasst werden:

  • Setzen Sie eine globale Variable
  • Legen Sie eine globale Variable fest, deren Namen Sie an die Funktion übergeben haben
  • Stellen Sie den Rückkehrcode ein (und holen Sie ihn mit $ ab?)
  • 'echo' einige Daten (und nimm sie mit MYVAR = $ (myfunction) auf)

Rückgabe von Werten aus Bash-Funktionen

Tom Hundt
quelle
Dies ist die beste Antwort, da der Artikel alle Optionen sauber beschreibt.
mzimmermann
-2

Git Bash unter Windows verwendet Arrays für mehrere Rückgabewerte

BASH CODE:

#!/bin/bash

##A 6-element array used for returning
##values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ##give the positional arguments/inputs
   ##$1 and $2 some sensible names:
   local out_dex_1="$1" ##output index
   local out_dex_2="$2" ##output index

   ##Echo for debugging:
   echo "running: FN_MULTIPLE_RETURN_VALUES"

   ##Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ##set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ##<--Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
##----------------------------------------------##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ##<--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
##----------------------------------------------##
FN_MULTIPLE_RETURN_VALUES 4 5  ##<---Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

ERWARTETE AUSGABE:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
JMI MADISON
quelle