Wie man einen Zeichenfolgenwert von einer Bash-function zurückgibt

Ich möchte eine Zeichenfolge aus einer Bashfunction zurückgeben.

Ich werde das Beispiel in Java schreiben, um zu zeigen, was ich tun möchte:

public String getSomeString() { return "tadaa"; } String variable = getSomeString(); 

Das folgende Beispiel funktioniert in bash, aber gibt es dafür einen besseren Weg?

 function getSomeString { echo "tadaa" } VARIABLE=$(getSomeString) 

Es gibt keinen besseren Weg, von dem ich weiß. Bash kennt nur Status-Codes (Integer) und Strings, die in das stdout geschrieben werden.

Die function könnte eine Variable als erstes Argument annehmen und die Variable mit der Zeichenfolge ändern, die zurückgegeben werden soll.

 #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var 

Drucke “foo bar rab oof”.

Bearbeiten : An der entsprechenden Stelle wurde ein Zitat hinzugefügt, um Whitespaces in Strings den @Luca Borriones Kommentar zu adressieren.

Bearbeiten : Als Demonstration sehen Sie das folgende Programm. Dies ist eine allgemeine Lösung: Sie können sogar eine Zeichenfolge in eine lokale Variable aufnehmen.

 #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" 

Dies druckt:

 + return_var= + pass_back_a_string return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local lvar= + pass_back_a_string lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

Bearbeiten : Zeigen, dass der Wert der ursprünglichen Variablen in der function verfügbar ist, wie von @Xichen Li in einem Kommentar falsch kritisiert wurde.

 #!/bin/bash set -x function pass_back_a_string() { eval "echo in pass_back_a_string, original $1 is \$$1" eval "$1='foo bar rab oof'" } return_var='original return_var' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='original lvar' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" 

Dies gibt die Ausgabe:

 + return_var='original return_var' + pass_back_a_string return_var + eval 'echo in pass_back_a_string, original return_var is $return_var' ++ echo in pass_back_a_string, original return_var is original return_var in pass_back_a_string, original return_var is original return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local 'lvar=original lvar' + pass_back_a_string lvar + eval 'echo in pass_back_a_string, original lvar is $lvar' ++ echo in pass_back_a_string, original lvar is original lvar in pass_back_a_string, original lvar is original lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

Alle obigen Antworten ignorieren, was auf der man-Seite von bash angegeben wurde.

  • Alle innerhalb einer function deklarierten Variablen werden mit der aufrufenden Umgebung geteilt.
  • Alle als lokal deklarierten Variablen werden nicht freigegeben.

Beispielcode

 #!/bin/bash f() { echo function starts local WillNotExists="It still does!" DoesNotExists="It still does!" echo function ends } echo $DoesNotExists #Should print empty line echo $WillNotExists #Should print empty line f #Call the function echo $DoesNotExists #Should print It still does! echo $WillNotExists #Should print empty line 

Und Ausgabe

 $ sh -x ./x.sh + echo + echo + f + echo function starts function starts + local 'WillNotExists=It still does!' + DoesNotExists='It still does!' + echo function ends function ends + echo It still 'does!' It still does! + echo 

Auch unter pdksh und ksh macht dieses Skript dasselbe!

Wie oben bei bstpierre , verwende und empfehle ich die explizite Benennung von Ausgabevariablen:

 function some_func() # OUTVAR ARG1 { local _outvar=$1 local _result # Use some naming convention to avoid OUTVARs to clash ... some processing .... eval $_outvar=\$_result # Instead of just =$_result } 

Beachten Sie die Verwendung von $. Dadurch wird vermieden, Inhalte in $result als Shell-Sonderzeichen zu interpretieren. Ich habe festgestellt, dass dies eine Größenordnung schneller als das result=$(some_func "arg1") Idiom der Erfassung eines Echos ist. Der Geschwindigkeitsunterschied scheint noch bemerkenswerter zu sein, wenn man bash auf MSYS verwendet, wo stdout das Erfassen von functionsaufrufen fast katastrophal ist.

Es ist in Ordnung, lokale Variablen zu senden, da Locals dynamisch in bash definiert sind:

 function another_func() # ARG { local result some_func result "$1" echo result is $result } 

Bash, seit Version 4.3, Februar 2014 (?), Hat explizite Unterstützung für Referenzvariablen oder Namensreferenzen (NameRefs), jenseits von “eval”, mit der gleichen positiven Performance und Indirection-Effekt, die in Ihren Skripten klarer und auch härter sein können “vergessen zu ‘eval’ und muss diesen Fehler beheben”:

 declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] Declare variables and/or give them attributes ... -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references and assignments to name, except for⋅ changing the -n attribute itself, are performed on the variable referenced by name's value. The -n attribute cannot be applied to array variables. ... When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied... 

und auch:

PARAMETER

Eine Variable kann dem Attribut naref zugeordnet werden, indem Sie den statementen declare oder local builtin die Option -n verwenden (siehe Beschreibungen von declare und local unten), um einen namenref oder einen Verweis auf eine andere Variable zu erstellen. Dadurch können Variablen indirekt manipuliert werden. Immer wenn auf die Variable naref verwiesen oder zugewiesen wird, wird die Operation tatsächlich für die Variable ausgeführt, die durch den Wert der Variablen name angegeben wird. Eine naref wird häufig in Shell-functionen verwendet, um auf eine Variable zu verweisen, deren Name als Argument für die function übergeben wird. Wenn zum Beispiel ein Variablenname als erstes Argument an eine Shell-function übergeben wird, wird running ausgeführt

  declare -n ref=$1 

Innerhalb der function wird eine namenref-Variable ref erstellt, deren Wert der als erstes Argument übergebene Variablenname ist. Referenzen und Zuordnungen zu ref werden als Referenzen und Zuweisungen zu der Variablen behandelt, deren Name als $ 1 übergeben wurde. Wenn die Steuervariable in einer for-Schleife das Attribut nameRef aufweist, kann die Liste der Wörter eine Liste von Shell-Variablen sein, und für jedes Wort in der Liste wird wiederum eine Namensreferenz festgelegt, wenn die Schleife ausgeführt wird. Array-Variablen können nicht das Attribut -n erhalten. Nameref-Variablen können jedoch auf Array-Variablen und subskribierte Array-Variablen verweisen. Namerefs können mit der Option -n für das nicht eingebaute Element deaktiviert werden. Andernfalls wird, wenn Unset mit dem Namen einer Variablen namens naref als Argument ausgeführt wird, die Variable, auf die von der Variablen namenref verwiesen wird, nicht gesetzt.

Zum Beispiel ( EDIT 2 : (danke Ron) Namespaced (Präfix) der function interne Variablenname, um externen Variablen Zusammenstöße zu minimieren, die schließlich richtig beantworten sollte, das Problem in den Kommentaren von Karsten aufgeworfen):

 # $1 : string; your variable to contain the return value function return_a_string () { declare -n ret=$1 local MYLIB_return_a_string_message="The date is " MYLIB_return_a_string_message+=$(date) ret=$MYLIB_return_a_string_message } 

und testen dieses Beispiel:

 $ return_a_string result; echo $result The date is 20160817 

Beachten Sie, dass die eingebaute bash “declare”, wenn sie in einer function verwendet wird, die deklarierte Variable standardmäßig “local” macht, und “-n” kann auch mit “local” verwendet werden.

Ich bevorzuge es, “wichtige Deklarationsvariablen” von “langweiligen lokalen” Variablen zu unterscheiden, so dass die Verwendung von “deklarieren” und “lokal” auf diese Weise als Dokumentation dient.

EDIT 1 – (Antwort auf Kommentar unten von Karsten) – Ich kann unten keine Kommentare mehr hinzufügen, aber Karstens Kommentar brachte mich zum Nachdenken, also habe ich den folgenden Test gemacht, der funktioniert, AFAKT – Karsten, wenn Sie dies lesen, geben Sie bitte einen genauen Satz an der Testschritte von der Befehlszeile aus, die das Problem anzeigen, von dem Sie annehmen, dass es existiert, weil diese folgenden Schritte gut funktionieren:

 $ return_a_string ret; echo $ret The date is 20170104 

(Ich habe das gerade jetzt ausgeführt, nachdem ich die obige function in einen Bash-Begriff eingefügt habe – wie Sie sehen können, funktioniert das Ergebnis gut.)

Sie könnten auch die functionsausgabe erfassen:

 #!/bin/bash function getSomeString() { echo "tadaa!" } return_var=$(getSomeString) echo $return_var # Alternative syntax: return_var=`getSomeString` echo $return_var 

Sieht seltsam aus, ist aber besser als die Verwendung globaler Variablen IMHO. Das Übergeben von Parametern funktioniert wie gewohnt, einfach in die Klammern oder Backticks stecken.

Wie bereits erwähnt, ist der “korrekte” Weg, eine Zeichenkette von einer function zurückzugeben, die Befehlsersetzung. Für den Fall, dass die function auch an die Konsole ausgeben muss (wie oben bei @Mani erwähnt), erstellen Sie am Anfang der function ein temporäres fd und leiten Sie an die Konsole um. Schließen Sie das temporäre fd, bevor Sie die Zeichenfolge zurückgeben.

 #!/bin/bash # file: func_return_test.sh returnString() { exec 3>&1 >/dev/tty local s=$1 s=${s:="some default string"} echo "writing directly to console" exec 3>&- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" 

Ausführen eines Skripts ohne Parameter erzeugt …

 # ./func_return_test.sh writing directly to console my_string: [some default string] 

hoffe das hilft den Menschen

-Andy

Die einfachste und robusteste Lösung ist die Befehlsersetzung, wie andere Leute geschrieben haben:

 assign() { local x x="Test" echo "$x" } x=$(assign) # This assigns string "Test" to x 

Der Nachteil ist die Performance, da hierfür ein separater process erforderlich ist.

Die andere Technik, die in diesem Thema vorgeschlagen wird, nämlich den Namen einer Variablen als Argument zu übergeben, hat Nebenwirkungen, und ich würde es nicht in seiner Grundform empfehlen. Das Problem ist, dass Sie wahrscheinlich einige Variablen in der function benötigen, um den Rückgabewert zu berechnen, und es kann vorkommen, dass der Name der Variable, die den Rückgabewert speichern soll, einen der folgenden Werte beeinflusst:

 assign() { local x x="Test" eval "$1=\$x" } assign y # This assigns string "Test" to y, as expected assign x # This will NOT assign anything to x in this scope # because the name "x" is declared as local inside the function 

Sie können natürlich keine internen Variablen der function als lokal deklarieren, aber Sie sollten dies immer tun, da Sie andernfalls versehentlich eine nicht verwandte Variable aus dem übergeordneten Bereich überschreiben, wenn es einen mit demselben Namen gibt .

Eine mögliche Problemumgehung ist eine explizite Deklaration der übergebenen Variablen als global:

 assign() { local x eval declare -g $1 x="Test" eval "$1=\$x" } 

Wenn der Name “x” als Argument übergeben wird, überschreibt die zweite Zeile des functionskörpers die vorherige lokale Deklaration. Aber die Namen selbst können sich immer noch gegenseitig stören. Wenn Sie also den zuvor in der übergebenen Variablen gespeicherten Wert verwenden möchten, bevor Sie den Rückgabewert dorthin schreiben, müssen Sie darauf achten, dass Sie ihn am Anfang in eine andere lokale Variable kopieren müssen. Andernfalls wird das Ergebnis unvorhersehbar sein! Außerdem wird dies nur in der neuesten Version von BASH funktionieren, nämlich 4.2. Mehr portabler Code könnte explizite bedingte Konstrukte mit demselben Effekt verwenden:

 assign() { if [[ $1 != x ]]; then local x fi x="Test" eval "$1=\$x" } 

Die vielleicht eleganteste Lösung besteht darin, nur einen globalen Namen für functionsrückgabewerte zu reservieren und ihn konsistent in jeder von Ihnen geschriebenen function zu verwenden.

Sie könnten eine globale Variable verwenden:

 declare globalvar='some string' string () { eval "$1='some other string'" } # ---------- end of function string ---------- string globalvar echo "'${globalvar}'" 

Das gibt

 'some other string' 

Um meinen Kommentar zu Andys Antwort zu illustrieren, mit zusätzlicher Datei-Deskriptor-Manipulation, um die Verwendung von /dev/tty zu vermeiden:

 #!/bin/bash exec 3>&1 returnString() { exec 4>&1 >&3 local s=$1 s=${s:="some default string"} echo "writing to stdout" echo "writing to stderr" >&2 exec >&4- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" 

Immer noch scheußlich.

Die Art, wie Sie es haben, ist die einzige Möglichkeit, dies zu tun, ohne den scope zu sprengen. Bash hat kein Konzept von Rückgabetypen, nur Exitcodes und Dateideskriptoren (stdin / out / err, etc)

Wir wenden uns an Vicky Ronnens Kopf und betrachten den folgenden Code:

 function use_global { eval "$1='changed using a global var'" } function capture_output { echo "always changed" } function test_inside_a_func { local _myvar='local starting value' echo "3. $_myvar" use_global '_myvar' echo "4. $_myvar" _myvar=$( capture_output ) echo "5. $_myvar" } function only_difference { local _myvar='local starting value' echo "7. $_myvar" local use_global '_myvar' echo "8. $_myvar" local _myvar=$( capture_output ) echo "9. $_myvar" } declare myvar='global starting value' echo "0. $myvar" use_global 'myvar' echo "1. $myvar" myvar=$( capture_output ) echo "2. $myvar" test_inside_a_func echo "6. $_myvar" # this was local inside the above function only_difference 

wird geben

 0. global starting value 1. changed using a global var 2. always changed 3. local starting value 4. changed using a global var 5. always changed 6. 7. local starting value 8. local starting value 9. always changed 

Das normale Szenario besteht darin, die in der function test_inside_a_func verwendete Syntax zu verwenden. test_inside_a_func können Sie in den meisten Fällen beide Methoden verwenden, obwohl das Erfassen der Ausgabe die sicherere Methode ist, die in jeder Situation funktioniert und den zurückgegebenen Wert einer function nachahmt kann in anderen Sprachen finden, wie Vicky Ronnen richtig darauf hingewiesen hat.

Die Optionen wurden alle aufgezählt, denke ich. Bei der Wahl eines solchen wird es auf den besten Stil für Ihre spezielle Anwendung ankommen, und in diesem Sinne möchte ich einen bestimmten Stil anbieten, den ich für nützlich halte. In bash befinden sich Variablen und functionen nicht im selben Namensraum. Also, die Variable mit dem gleichen Namen wie der Wert der function zu behandeln ist eine Konvention, die Namenskonflikte minimiert und die Lesbarkeit verbessert, wenn ich sie streng anwende. Ein Beispiel aus dem wirklichen Leben:

 UnGetChar= function GetChar() { # assume failure GetChar= # if someone previously "ungot" a char if ! [ -z "$UnGetChar" ]; then GetChar="$UnGetChar" UnGetChar= return 0 # success # else, if not at EOF elif IFS= read -N1 GetChar ; then return 0 # success else return 1 # EOF fi } function UnGetChar(){ UnGetChar="$1" } 

Und ein Beispiel für die Verwendung solcher functionen:

 function GetToken() { # assume failure GetToken= # if at end of file if ! GetChar; then return 1 # EOF # if start of comment elif [[ "$GetChar" == "#" ]]; then while [[ "$GetChar" != $'\n' ]]; do GetToken+="$GetChar" GetChar done UnGetChar "$GetChar" # if start of quoted string elif [ "$GetChar" == '"' ]; then # ... et cetera 

Wie Sie sehen, ist der Rückgabestatus für Sie verfügbar, wenn Sie ihn benötigen, oder Sie ignorieren ihn, wenn Sie dies nicht tun. Die Variable “returned” kann ebenfalls verwendet oder ignoriert werden, aber natürlich erst nachdem die function aufgerufen wurde.

Natürlich ist das nur eine Konvention. Es ist Ihnen freigestellt, den zugehörigen Wert vor der Rückgabe nicht zu setzen (daher meine Konvention, den Wert immer zu Beginn der function aufzuheben) oder den Wert zu trampeln, indem Sie die function (möglicherweise indirekt) erneut aufrufen. Dennoch ist es eine Konvention, die ich sehr nützlich finde, wenn ich mich stark auf Bash-functionen stütze.

Im Gegensatz zu dem Gefühl, dass dies ein Zeichen ist, dass man sich zB zu Perl bewegen sollte, ist meine Philosophie, dass Konventionen immer wichtig sind, um die Komplexität jeder Sprache zu managen.

Sie sind das Schlüsselproblem eines beliebigen ‘benannten Ausgabevariablen’-Schemas, bei dem der Aufrufer den Variablennamen (egal ob eval oder declare -n ) übergeben kann, ist ungewolltes Aliasing, dh Namenskonflikte: Aus Sicht der Kapselung ist es schrecklich, nicht in der Lage zu sein um eine lokale Variable in einer function hinzuzufügen oder umzubenennen, ohne ALLE Aufrufer der function zu überprüfen, um sicherzustellen, dass sie nicht denselben Namen wie der Ausgabeparameter übergeben wollen. (Oder in die andere Richtung, ich möchte nicht die Quelle der function, die ich rufe, nur lesen müssen, um sicherzustellen, dass der Ausgabeparameter, den ich verwenden möchte, kein lokaler in dieser function ist.)

Der einzige Weg dazu ist die Verwendung einer einzigen dedizierten Ausgabevariablen wie REPLY (wie von Evi1M4chine vorgeschlagen ) oder einer Konvention wie der von Ron Burk vorgeschlagenen .

Es ist jedoch möglich, dass functionen intern eine feste Ausgabevariable verwenden und dann oben Zucker hinzufügen, um diese Tatsache vor dem Aufrufer zu verbergen , wie ich es im folgenden Beispiel mit der Aufruffunktion getan habe. Betrachten Sie dies als einen Beweis des Konzepts, aber die wichtigsten Punkte sind

  • Die function weist REPLY immer den Rückgabewert zu und kann wie üblich auch einen Exit-Code zurückgeben
  • Aus Sicht des Aufrufers kann der Rückgabewert einer beliebigen Variablen (lokal oder global) einschließlich REPLY (siehe wrapper Beispiel). Der Exit-Code der function wird durchlaufen, sodass die Verwendung in einem if oder while Konstrukt oder in ähnlichen Konstrukten wie erwartet funktioniert.
  • Syntaktisch ist der functionsaufruf immer noch eine einfache statement.

Der Grund dafür ist, dass die call function selbst keine Locals hat und keine anderen Variablen als REPLY , um mögliche Konflikte mit Namen zu vermeiden. An dem Punkt, an dem der Name der aufruferdefinierten Ausgabevariablen zugewiesen wird, befinden wir uns effektiv im Bereich des Aufrufers (technisch im identischen Bereich der call ) und nicht im Bereich der aufgerufenen function.

 #!/bin/bash function call() { # var=func [args ...] REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?" } function greet() { case "$1" in us) REPLY="hello";; nz) REPLY="kia ora";; *) return 123;; esac } function wrapper() { call REPLY=greet "$@" } function main() { local abcd call a=greet us echo "a='$a' ($?)" call b=greet nz echo "b='$b' ($?)" call c=greet de echo "c='$c' ($?)" call d=wrapper us echo "d='$d' ($?)" } main 

Ausgabe:

 a='hello' (0) b='kia ora' (0) c='' (123) d='hello' (0) 

In meinen Programmen ist dies die Konvention, für die die vorher existierende $REPLY Variable verwendet wird, die für genau diesen Zweck read wird.

 function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY 

Dies echo

 tadaa 

Um Konflikte zu vermeiden, reicht jedoch jede andere globale Variable aus.

 declare result function getSomeString { result="tadaa" } getSomeString echo $result 

Wenn das nicht genug ist, empfehle ich die Lösung von Markarian451 .

Sie können eine Zeichenfolge wiedergeben, aber fangen Sie sie ab, indem Sie ( | ) die function an etwas anderes weiterleiten.

Sie können dies mit expr tun, obwohl ShellCheck diese Verwendung als veraltet angibt .

Bash- Muster, um sowohl Skalar- als auch Array- Wertobjekte zurückzugeben:

Definition

 url_parse() { # parse 'url' into: 'url_host', 'url_port', ... local "$@" # inject caller 'url' argument in local scope local url_host="..." url_path="..." # calculate 'url_*' components declare -p ${!url_*} # return only 'url_*' object fields to the caller } 

Aufruf

 main() { # invoke url parser and inject 'url_*' results in local scope eval "$(url_parse url=http://host/path)" # parse 'url' echo "host=$url_host path=$url_path" # use 'url_*' components } 
 agt@agtsoft:~/temp$ cat ./fc #!/bin/sh fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall' function f1 { res=$[($1+$2)*2]; } function f2 { local a; eval ${fcall//fname/f1} a 2 3; echo f2:$a; } a=3; f2; echo after:a=$a, res=$res agt@agtsoft:~/temp$ ./fc f2:10 after:a=3, res=