So geben Sie einen Zeichenfolgenwert von einer Bash-Funktion zurück

461

Ich möchte einen String von einer Bash-Funktion 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 einen besseren Weg, dies zu tun?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Tomas F.
quelle
4
Abgesehen davon function funcName {ist die Legacy-Syntax vor POSIX vom frühen ksh geerbt (wo es semantische Unterschiede gab, die bash nicht berücksichtigt). funcName() {, mit no function, sollte stattdessen verwendet werden; siehe wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
Dieser Link besagt, dass NAME () COMPOUND-CMD oder die Funktion NAME {CMDS; } Also function myFunction { blah; }ist gut; Es function myFunction() { blah }ist nicht in Ordnung, dh die Verwendung von Klammern mit der Schlüsselwortfunktion.
Will

Antworten:

290

Es gibt keinen besseren Weg, den ich kenne. Bash kennt nur Statuscodes (Ganzzahlen) und Zeichenfolgen, die in die Standardausgabe geschrieben wurden.

Philipp
quelle
15
+1 @ tomas-f: Sie müssen wirklich vorsichtig sein, was Sie in dieser Funktion "getSomeString ()" haben, da jeder Code, der schließlich widerhallt, bedeutet, dass Sie eine falsche Rückgabezeichenfolge erhalten.
Mani
11
Das ist einfach falsch. Sie können beliebige Daten innerhalb einer Rückgabevariablen zurückgeben. Welches ist eindeutig ein besserer Weg.
Evi1M4chine
36
@ Evi1M4chine, ähm ... nein, das kannst du nicht. Sie können eine globale Variable festlegen und sie "return" nennen, wie Sie es in Ihren Skripten sehen. Aber das ist konventionell und NICHT programmgesteuert an die Ausführung Ihres Codes gebunden. "eindeutig ein besserer Weg"? Ähm, nein. Die Befehlssubstitution ist weitaus expliziter und modularer.
Wildcard
6
"Befehlssubstitution ist weitaus expliziter und modularer" wäre relevant, wenn die Frage nach Befehlen wäre; Diese Frage ist, wie man einen String von einer Bash-Funktion zurückgibt! Seit Bash 4.3 (2014?) Ist eine integrierte Methode verfügbar, um das zu tun, was das OP verlangt hat - siehe meine Antwort unten.
Zenaan
4
Die ursprüngliche Frage enthält den einfachsten Weg, dies zu tun, und funktioniert in den meisten Fällen gut. Bash-Rückgabewerte sollten wahrscheinlich als "Rückkehrcodes" bezeichnet werden, da sie weniger wie Standardrückgabewerte in Skripten sind, sondern eher wie Exit-Codes für numerische Shell-Befehle (Sie können solche Dinge tun somefunction && echo 'success'). Wenn Sie sich eine Funktion wie einen anderen Befehl vorstellen, ist dies sinnvoll. Befehle "geben" beim Beenden nichts anderes als einen Statuscode zurück, können jedoch in der Zwischenzeit Dinge ausgeben, die Sie erfassen können.
Beejor
193

Sie können die Funktion eine Variable als erstes Argument verwenden lassen und die Variable mit der Zeichenfolge ändern, die Sie zurückgeben möchten.

#!/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

Druckt "foo bar rab oof".

Bearbeiten : An der entsprechenden Stelle wird ein Anführungszeichen hinzugefügt, damit Leerzeichen in der Zeichenfolge den Kommentar von @Luca Borrione adressieren können.

Bearbeiten : Zur Demonstration sehen Sie das folgende Programm. Dies ist eine Allzwecklösung: Sie können sogar eine Zeichenfolge in eine lokale Variable empfangen.

#!/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 : was zeigt , dass der ursprüngliche Variablenwert ist in der Funktion zur Verfügung, wie falsch von @Xichen Li in einem Kommentar 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 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
bstpierre
quelle
1
Diese Antwort ist großartig! Parameter können durch Verweise übergeben werden, ähnlich wie in C ++.
Yun Huang
4
Es wäre schön, eine Antwort von einem Experten zu dieser Antwort zu erhalten. Ich habe das noch nie in Skripten gesehen, vielleicht aus gutem Grund. Wie auch immer: das ist +1. Es hätte für die richtige Antwort gestimmt werden sollen
John
Ist das nicht die gleiche fgmAntwort, die auf vereinfachte Weise geschrieben wurde? Dies funktioniert nicht, wenn die Zeichenfolge fooLeerzeichen enthält, während die Zeichenfolge fgm.. wie er zeigt.
Luca Borrione
4
@XichenLi: Danke, dass du einen Kommentar mit deiner Ablehnung hinterlassen hast; Bitte sehen Sie meine Bearbeitung. Den Anfangswert der Variablen erhalten Sie in der Funktion mit \$$1. Wenn Sie etwas anderes suchen, lassen Sie es mich bitte wissen.
Bstpierre
1
@timiscoding Das kann mit a behoben werden printf '%q' "$var". % q ist eine Formatzeichenfolge für das Shell-Escape. Dann gib es einfach roh weiter.
bb010g
99

Alle obigen Antworten ignorieren, was in der Manpage von bash angegeben wurde.

  • Alle in einer Funktion deklarierten Variablen werden für die aufrufende Umgebung freigegeben.
  • Alle als lokal deklarierten Variablen werden nicht gemeinsam genutzt.

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!

Vicky Ronnen
quelle
10
Diese Antwort hat ihre Vorzüge. Ich kam hierher und dachte, ich wollte einen String von einer Funktion zurückgeben. Diese Antwort machte mir klar, dass das nur meine C # -Leben waren. Ich vermute, andere haben die gleiche Erfahrung.
LOAS
4
@ElmarZander Du liegst falsch, das ist völlig relevant. Dies ist eine einfache Möglichkeit, einen Funktionsbereichswert in den globalen Bereich zu bringen, und einige würden dies als besser / einfacher betrachten als den von bstpierre beschriebenen eval-Ansatz zur Neudefinition einer globalen Variablen.
KomodoDave
local ist nicht für Nicht-Bash-Skripte portierbar, was ein Grund ist, warum manche Leute es vermeiden.
Don Bright
Frage: Was ist mit Variablen in Schleifen?
Anu
1
Auf einem Mac ($ bash --version GNU bash, Version 3.2.57 (1) - Release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.) ist es richtig, dass eine übereinstimmende globale Variable vorliegt initialisiert, aber wenn ich versuche, dieselbe Variable in einer anderen Funktion f2 als Nebenwirkung zu verwenden, bleibt diese Nebenwirkung nicht bestehen. Es scheint also sehr inkonsistent und daher nicht gut für meinen Gebrauch zu sein.
AnneTheAgile
45

Bash bietet seit Version 4.3, Februar 2014 (?), Eine explizite Unterstützung für Referenzvariablen oder Namensreferenzen (namerefs), die über "eval" hinausgehen, mit derselben vorteilhaften Leistung und demselben Indirektionseffekt, die in Ihren Skripten möglicherweise klarer und auch schwieriger sind um "zu vergessen, 'auszuwerten' und diesen Fehler zu 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

Einer Variablen kann das Attribut nameref mithilfe der Option -n den Deklarations- oder lokal eingebauten Befehlen zugewiesen werden (siehe die Beschreibungen von deklarieren und lokal unten), um ein nameref oder einen Verweis auf eine andere Variable zu erstellen. Dadurch können Variablen indirekt manipuliert werden. Immer wenn auf die nameref-Variable verwiesen oder zugewiesen wird, wird die Operation tatsächlich für die Variable ausgeführt, die durch den Wert der nameref-Variablen angegeben wird. Ein nameref wird üblicherweise in Shell-Funktionen verwendet, um auf eine Variable zu verweisen, deren Name als Argument an die Funktion übergeben wird. Wenn beispielsweise ein Variablenname als erstes Argument an eine Shell-Funktion übergeben wird, wird ausgeführt

      declare -n ref=$1

Innerhalb der Funktion wird eine nameref-Variablenreferenz erstellt, deren Wert der Variablenname ist, der als erstes Argument übergeben wird. Referenzen und Zuweisungen an ref werden als Referenzen und Zuweisungen an die Variable behandelt, deren Name als $ 1 übergeben wurde. Wenn die Steuervariable in einer for-Schleife das Attribut nameref hat, kann die Liste der Wörter eine Liste der Shell-Variablen sein, und für jedes Wort in der Liste wird nacheinander eine Namensreferenz erstellt, wenn die Schleife ausgeführt wird. Array-Variablen können nicht das Attribut -n erhalten. Nameref-Variablen können jedoch auf Array-Variablen und tiefgestellte Array-Variablen verweisen. Namerefs können mit der Option -n für das nicht festgelegte integrierte Objekt deaktiviert werden. Andernfalls, wenn unset mit dem Namen einer nameref-Variablen als Argument ausgeführt wird,

Zum Beispiel ( EDIT 2 : (danke Ron) Namespace (vorangestellt) des funktionsinternen Variablennamens, um externe Variablenkonflikte zu minimieren, die das in den Kommentaren von Karsten aufgeworfene Problem endgültig beantworten sollten):

# $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 Beispiels:

$ return_a_string result; echo $result
The date is 20160817

Beachten Sie, dass die eingebaute Bash "deklarieren" bei Verwendung in einer Funktion die deklarierte Variable standardmäßig "lokal" macht und "-n" auch mit "lokal" verwendet werden kann.

Ich bevorzuge es, "wichtige Deklarations" -Variablen von "langweiligen lokalen" Variablen zu unterscheiden, daher dient die Verwendung von "Deklaration" und "lokal" auf diese Weise als Dokumentation.

EDIT 1 - (Antwort auf Kommentar unten von Karsten) - Ich kann unten keine Kommentare mehr hinzufügen, aber Karstens Kommentar hat mich zum Nachdenken gebracht, also habe ich den folgenden Test durchgeführt, der FUNKTIONIERT, AFAICT - Karsten, wenn Sie dies lesen, geben Sie bitte einen genauen Satz an von Testschritten über die Befehlszeile, die das von Ihnen angenommene Problem anzeigen, da diese folgenden Schritte einwandfrei funktionieren:

$ return_a_string ret; echo $ret
The date is 20170104

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

Zenaan
quelle
4
Ich hoffe, dass dies nach oben sickert. eval sollte ein letzter Ausweg sein. Erwähnenswert ist, dass nameref-Variablen erst seit Bash 4.3 (laut Changelog ) (veröffentlicht im Februar 2014 [?]) Verfügbar sind. Dies ist wichtig, wenn die Portabilität ein Problem darstellt. Bitte zitieren Sie das Bash-Handbuch zur Tatsache, dass declarelokale Variablen innerhalb von Funktionen erstellt werden (diese Informationen werden nicht von angegeben help declare): "... Wenn Sie in einer Funktion verwendet werden, deklarieren und setzen Sie jeden Namen lokal, wie beim lokalen Befehl, es sei denn, - g Option wird mitgeliefert ... "
init_js
2
Dies hat das gleiche Aliasing-Problem wie die Evaluierungslösung. Wenn Sie eine Funktion aufrufen und den Namen der Ausgabevariablen übergeben, müssen Sie vermeiden, den Namen einer Variablen zu übergeben, die lokal in der von Ihnen aufgerufenen Funktion verwendet wird. Dies ist ein großes Problem in Bezug auf die Kapselung, da Sie nicht einfach neue lokale Variablen in einer Funktion hinzufügen oder umbenennen können, ohne dass einer der Funktionsaufrufer diesen Namen für den Ausgabeparameter verwenden möchte.
Karsten
1
@ Karsten stimmte zu. In beiden Fällen (eval und namerefs) müssen Sie möglicherweise einen anderen Namen auswählen. Ein Vorteil des nameref-Ansatzes gegenüber eval ist, dass man sich nicht mit Escape-Strings befassen muss. Natürlich können Sie immer so etwas tun K=$1; V=$2; eval "$A='$V'";, aber einen Fehler (z. B. einen leeren oder ausgelassenen Parameter), und es wäre gefährlicher. @zenaan Das von @Karsten aufgeworfene Problem tritt auf, wenn Sie "Nachricht" als Namen der Rückgabevariable anstelle von "Ret" auswählen.
init_js
3
Eine Funktion muss vermutlich von Anfang an so konzipiert sein, dass sie ein nameref-Argument akzeptiert. Daher sollte der Funktionsautor die Möglichkeit einer Namenskollision kennen und eine typische Konvention verwenden, um dies zu vermeiden. Benennen Sie beispielsweise in Funktion X lokale Variablen mit der Konvention "X_LOCAL_name".
Ron Burk
34

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 der Angabe des $. Dadurch wird vermieden, dass Inhalte $resultals Shell-Sonderzeichen interpretiert werden . Ich habe festgestellt, dass dies eine Größenordnung schneller ist als die result=$(some_func "arg1")Redewendung, ein Echo zu erfassen. Der Geschwindigkeitsunterschied scheint bei Verwendung von Bash auf MSYS noch bemerkenswerter zu sein, wo die Standarderfassung von Funktionsaufrufen fast katastrophal ist.

Es ist in Ordnung, lokale Variablen einzusenden, da die Einheimischen dynamisch in Bash festgelegt sind:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
quelle
4
Dies hilft mir, weil ich gerne mehrere Echoanweisungen zum Debuggen / Protokollieren verwende. Die Redewendung zum Erfassen von Echo schlägt fehl, da alle erfasst werden. Vielen Dank!
AnneTheAgile
Dies ist die (zweitbeste) richtige Lösung! Sauber, schnell, elegant, vernünftig.
Evi1M4chine
+2, um es real zu halten. Ich wollte gerade sagen. Wie können so viele Leute ignorieren, ein echoInneres einer Funktion mit einer Befehlssubstitution zu kombinieren?
Anthony Rutledge
22

Sie können auch die Funktionsausgabe erfassen:

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

Sieht komisch aus, ist aber meiner Meinung nach besser als die Verwendung globaler Variablen. Das Übergeben von Parametern funktioniert wie gewohnt. Setzen Sie sie einfach in die geschweiften Klammern oder Backticks.

Chiborg
quelle
11
Ist dies, abgesehen von der alternativen Syntaxnotiz, nicht genau das, was der Op bereits in seiner eigenen Frage geschrieben hat?
Luca Borrione
12

Die einfachste und robusteste Lösung ist die Verwendung der Befehlssubstitution, wie andere geschrieben haben:

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

Der Nachteil ist die Leistung, da dies einen separaten Prozess erfordert.

Die andere in diesem Thema vorgeschlagene Technik, nämlich die Übergabe des Namens einer Variablen, der als Argument zugewiesen werden soll, hat Nebenwirkungen, und ich würde sie in ihrer Grundform nicht empfehlen. Das Problem ist, dass Sie wahrscheinlich einige Variablen in der Funktion benötigen, um den Rückgabewert zu berechnen, und es kann vorkommen, dass der Name der Variablen, die den Rückgabewert speichern soll, eine davon stört:

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 interne Variablen der Funktion natürlich nicht als lokal deklarieren, aber Sie sollten dies wirklich immer tun, da Sie andernfalls versehentlich eine nicht verwandte Variable aus dem übergeordneten Bereich überschreiben können, wenn es eine 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 Funktionskörpers die vorherige lokale Deklaration. Die Namen selbst können jedoch weiterhin stören. Wenn Sie also den zuvor in der übergebenen Variablen gespeicherten Wert verwenden möchten, bevor Sie den Rückgabewert dort schreiben, müssen Sie ihn ganz am Anfang in eine andere lokale Variable kopieren. Andernfalls ist das Ergebnis unvorhersehbar! Außerdem funktioniert dies nur in der neuesten Version von BASH, nämlich 4.2. Portablerer Code verwendet möglicherweise explizite bedingte Konstrukte mit demselben Effekt:

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 Funktionsrückgabewerte zu reservieren und ihn in jeder von Ihnen geschriebenen Funktion konsistent zu verwenden.

Tomasz Żuk
quelle
3
Dies ^^^. Das versehentliche Aliasing, das die Kapselung unterbricht, ist das große Problem sowohl bei der evalals auch bei der declare -nLösung. Die Problemumgehung, einen einzigen dedizierten Variablennamen wie resultfür alle Ausgabeparameter zu haben, scheint die einzige Lösung zu sein, für die keine Funktionen erforderlich sind, um alle Aufrufer zu kennen, um Konflikte zu vermeiden.
Karsten
12

Wie bereits erwähnt, ist die "richtige" Möglichkeit, eine Zeichenfolge von einer Funktion zurückzugeben, die Befehlssubstitution. Für den Fall, dass die Funktion auch an die Konsole ausgegeben werden muss (wie oben von @Mani erwähnt), erstellen Sie am Anfang der Funktion ein temporäres fd und leiten Sie zur Konsole um. Schließen Sie das temporäre fd, bevor Sie Ihren String 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]"

Das 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

Andy
quelle
6
Das hat seine Verwendung, aber im Großen und Ganzen sollten Sie eine explizite Weiterleitung zur Konsole vermeiden. Die Ausgabe wird möglicherweise bereits umgeleitet, oder das Skript wird möglicherweise in einem Kontext ausgeführt, in dem kein tty vorhanden ist. Sie können dies umgehen, indem Sie 3>&1am Kopf des Skripts duplizieren &1 &3und dann einen weiteren Platzhalter &4innerhalb der Funktion bearbeiten . Überall hässlich.
JMB
8

Sie können 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'
Fritz G. Mehner
quelle
6

Um meinen Kommentar zu Andys Antwort zu veranschaulichen, mit zusätzlicher Manipulation des Dateideskriptors, 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]"

Trotzdem böse.

jmb
quelle
3

Die Art und Weise, wie Sie es haben, ist die einzige Möglichkeit, dies zu tun, ohne den Umfang zu beeinträchtigen. Bash hat kein Konzept für Rückgabetypen, sondern nur Exit-Codes und Dateideskriptoren (stdin / out / err usw.)

Daenyth
quelle
3

Adressierung von Vicky Ronnens Kopf hoch unter Berücksichtigung des folgenden Codes:

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

Möglicherweise besteht das normale Szenario darin, die in der test_inside_a_funcFunktion verwendete Syntax zu verwenden. In den meisten Fällen können Sie daher beide Methoden verwenden. Die Erfassung der Ausgabe ist jedoch die sicherere Methode, die in jeder Situation immer funktioniert und den Rückgabewert einer Funktion nachahmt, die Sie können in anderen Sprachen finden, wie Vicky Ronnenrichtig hervorgehoben.

Luca Borrione
quelle
2

Ich denke, die Optionen wurden alle aufgezählt. Die Auswahl eines Stils hängt möglicherweise vom besten Stil für Ihre spezielle Anwendung ab. In diesem Sinne möchte ich einen bestimmten Stil anbieten, den ich für nützlich befunden habe. In Bash befinden sich Variablen und Funktionen nicht im selben Namespace. Die Behandlung der gleichnamigen Variablen als Wert der Funktion ist eine Konvention, die meiner Meinung nach Namenskonflikte minimiert und die Lesbarkeit verbessert, wenn ich sie konsequent 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"
}

Ein Beispiel für die Verwendung solcher Funktionen:

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, können Sie den Rückgabestatus bei Bedarf verwenden oder ignorieren, wenn Sie dies nicht tun. Die "zurückgegebene" Variable kann ebenfalls verwendet oder ignoriert werden, aber natürlich erst, nachdem die Funktion aufgerufen wurde.

Dies ist natürlich nur eine Konvention. Es steht Ihnen frei, den zugehörigen Wert vor der Rückkehr nicht festzulegen (daher meine Konvention, ihn immer am Anfang der Funktion auf Null zu setzen) oder seinen Wert durch erneutes Aufrufen der Funktion (möglicherweise indirekt) mit Füßen zu treten. Trotzdem ist es eine Konvention, die ich sehr nützlich finde, wenn ich Bash-Funktionen stark nutze.

Im Gegensatz zu dem Gefühl, dass dies ein Zeichen ist, das man z. B. "Perl bewegen" sollte, ist meine Philosophie, dass Konventionen immer wichtig sind, um die Komplexität einer Sprache zu verwalten.

Ron Burk
quelle
2

Sie können echoeinen String, aber fangen Sie ihn durch Piping (| die Funktion an etwas anderes ).

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

Apennebaker
quelle
Das Problem ist, dass das Ding rechts neben dem Rohr eine Unterschale ist. So myfunc | read OUTPUT ; echo $OUTPUTergibt sich nichts. myfunc | ( read OUTPUT; echo $OUTPUT )erhält den erwarteten Wert und verdeutlicht, was auf der rechten Seite passiert. Aber natürlich ist OUTPUT dann nicht dort verfügbar, wo Sie es brauchen ...
Ed Randall
2

Das Hauptproblem eines Schemas mit 'benannter Ausgabevariable', bei dem der Aufrufer den Variablennamen (unabhängig davon, ob er evaloder verwendet declare -n) übergibt, ist versehentliches Aliasing, dh Namenskonflikte: Aus Sicht der Kapselung ist es schrecklich, nicht hinzufügen oder umbenennen zu können Eine lokale Variable in einer Funktion, ohne zuerst ALLE Aufrufer der Funktion zu überprüfen , um sicherzustellen, dass sie nicht denselben Namen wie der Ausgabeparameter übergeben möchten. (Oder in die andere Richtung möchte ich nicht die Quelle der aufgerufenen Funktion lesen müssen, nur um sicherzustellen, dass der Ausgabeparameter, den ich verwenden möchte, in dieser Funktion nicht lokal ist.)

Die einzige Möglichkeit, dies zu umgehen , besteht darin, eine einzelne dedizierte Ausgabevariable wie REPLY(wie von Evi1M4chine vorgeschlagen ) oder eine Konvention wie die von Ron Burk vorgeschlagene zu verwenden .

Es ist jedoch möglich, dass Funktionen intern eine feste Ausgabevariable verwenden und dann etwas Zucker darüber hinzufügen, um diese Tatsache vor dem Aufrufer zu verbergen , wie ich es mit der callFunktion im folgenden Beispiel getan habe . Betrachten Sie dies als Proof of Concept, aber die wichtigsten Punkte sind

  • Die Funktion weist immer den Rückgabewert zu REPLYund kann wie gewohnt auch einen Exit-Code zurückgeben
  • Aus Sicht des Aufrufers kann der Rückgabewert jeder Variablen (lokal oder global) zugewiesen werden, einschließlich REPLY(siehe wrapperBeispiel). Der Exit-Code der Funktion wird durchlaufen, also verwenden Sie sie zB in einem ifoderwhile oder ähnliche Konstrukte , wie erwartet funktioniert.
  • Syntaktisch ist der Funktionsaufruf immer noch eine einzelne einfache Anweisung.

Der Grund dafür ist, dass die callFunktion selbst keine Einheimischen hat und keine anderen Variablen verwendet als REPLY, um mögliche Namenskonflikte zu vermeiden. An dem Punkt, an dem der vom Aufrufer definierte Name der Ausgabevariable zugewiesen wird, befinden wir uns effektiv im Bereich des Aufrufers (technisch im identischen Bereich der callFunktion) und nicht im Bereich der aufgerufenen Funktion.

#!/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 a b c d
  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)
Karsten
quelle
1

Bash- Muster, um sowohl skalare als auch Array- Wert-Objekte 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
}
Andrei Pozolotin
quelle
0

In meinen Programmen ist dies konventionell der Zweck der bereits vorhandenen $REPLYVariablen, die readgenau für diesen Zweck verwendet wird.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Das ist echoes

tadaa

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

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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

Evi1M4chine
quelle
-2
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=
agtsoft
quelle