wake-up-neo.com

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

Ich möchte eine Zeichenfolge aus einer Bash-Funktion zurückgeben.

Ich schreibe das Beispiel in Java, 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 eine bessere Möglichkeit, dies zu tun?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
427
Tomas F

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

267
Philipp

Sie können die Funktion veranlassen, eine Variable als erstes Argument zu verwenden und die Variable mit der Zeichenfolge zu ä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 entsprechender Stelle Anführungszeichen hinzugefügt, damit Leerzeichen in Zeichenfolgen den Kommentar von @Luca Borrione adressieren können.

Edit: Zur Demonstration siehe folgendes 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: Demonstriert, dass der Wert der ursprünglichen Variablen in der Funktion verfügbar ist , wie dies von @Xichen Li in einem Kommentar fälschlicherweise 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 Folgendes aus:

+ 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
186
bstpierre

Alle obigen Antworten ignorieren die Angaben in der Manpage von bash.

  • Alle innerhalb einer Funktion deklarierten Variablen werden mit der aufrufenden Umgebung geteilt.
  • 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!

95
Vicky Ronnen

Bash unterstützt seit Version 4.3, Februar 2014 (?) Explizit Referenzvariablen oder Namensreferenzen (namerefs) jenseits von "eval" mit derselben vorteilhaften Leistung und Indirektionseffekt, die in Ihren Skripten möglicherweise klarer und auch schwieriger sind um "vergessen zu 'auswerten' und diesen Fehler beheben zu müssen":

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 mit der Option -n für die Befehle declare oder local builtin zugewiesen werden (siehe die folgenden Beschreibungen zu declare und local), um ein nameref oder einen Verweis auf eine andere Variable zu erstellen. Dies ermöglicht die indirekte Manipulation von Variablen. Immer wenn auf die nameref-Variable verwiesen oder sie 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 häufig 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-Variable ref 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 name $ 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 mit dem Attribut -n versehen werden. Nameref-Variablen können jedoch auf Array-Variablen und tiefgestellte Array-Variablen verweisen. Namerefs können mit der Option -n deaktiviert werden. Andernfalls wird die Variable, auf die von der nameref-Variablen verwiesen wird, nicht gesetzt, wenn unset mit dem Namen einer nameref-Variablen als Argument ausgeführt wird.

Zum Beispiel (EDIT 2: (danke Ron) Namensraum (Präfix) des funktionsinternen Variablennamens, um externe Variablenkollisionen zu minimieren, die endlich richtig antworten sollten, das in den Kommentaren von Karsten aufgeworfene Problem) :

# $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 teste dieses Beispiel:

$ return_a_string result; echo $result
The date is 20160817

Beachten Sie, dass die in eine Funktion integrierte Bash "declare" die deklarierte Variable standardmäßig zu "local" macht und "-n" auch mit "local" verwendet werden kann.

Ich bevorzuge es, "wichtige deklarierte" Variablen von "langweiligen lokalen" Variablen zu unterscheiden, daher dient die Verwendung von "deklarieren" 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, und ich habe den folgenden Test durchgeführt, der einwandfrei funktioniert, AFAICT - Karsten, wenn Sie ihn lesen In diesem Fall geben Sie bitte eine genaue Reihe von Testschritten in der Befehlszeile an, die das vermutete Problem anzeigen, da die folgenden Schritte problemlos 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-Term eingefügt habe. Wie Sie sehen, funktioniert das Ergebnis einwandfrei.)

43
zenaan

Wie bstpierre oben 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 Anführungszeichen von $. Dadurch wird vermieden, dass der Inhalt in $result als Shell-Sonderzeichen interpretiert wird. Ich habe festgestellt, dass dies eine Größenordnung schneller ist als die result=$(some_func "arg1") -Idiom der Erfassung eines Echos. Der Geschwindigkeitsunterschied scheint bei Verwendung von Bash auf MSYS noch bemerkenswerter zu sein, wo das stdout-Capturen von Funktionsaufrufen fast katastrophal ist.

Es ist in Ordnung, lokale Variablen einzusenden, da die lokalen Variablen dynamisch in bash aufgeteilt werden:

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

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. Legen Sie sie einfach in die geschweiften Klammern oder Backticks.

22
chiborg

Wie bereits erwähnt, erfolgt die "korrekte" Rückgabe einer Zeichenfolge von einer Funktion durch eine Befehlssubstitution. Für den Fall, dass die Funktion auch auf die Konsole ausgeben muss (wie @Mani oben erwähnt), erstellen Sie am Anfang der Funktion ein temporäres fd und leiten Sie es auf die Konsole um. Schließen Sie das temporäre fd, bevor Sie Ihre 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]"

skript ohne Parameter ausführen erzeugt ...

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

hoffe, das hilft den Menschen

-Andy

12
Andy

Die einfachste und robusteste Lösung ist die Verwendung der Befehlssubstitution, wie andere Leute 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 das Übergeben des Namens einer zuzuweisenden Variablen als Argument, 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 dass der Name der Variablen, in der der Rückgabewert gespeichert werden soll, möglicherweise eine davon beeinträchtigt:

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

Natürlich können Sie interne Variablen der Funktion nicht als lokal deklarieren, aber Sie sollten dies unbedingt tun, da Sie andernfalls möglicherweise versehentlich eine nicht verwandte Variable aus dem übergeordneten Bereich überschreiben, wenn es eine Variable 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 sich dennoch störend auswirken. Wenn Sie also den zuvor in der übergebenen Variablen gespeicherten Wert verwenden möchten, bevor Sie den Rückgabewert dort eingeben, müssen Sie ihn 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 dem gleichen 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 Funktion, die Sie schreiben, konsistent zu verwenden.

10
Tomasz Żuk

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'
8
Fritz G. Mehner

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 immer noch böse.

6
jmb

Die Art und Weise, wie Sie es haben, ist die einzige Möglichkeit, dies zu tun, ohne den Rahmen zu sprengen. Bash kennt keine Rückgabetypen, nur Exit-Codes und Dateideskriptoren (stdin/out/err, etc)

3
Daenyth

Anrede Vicky Ronnen 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 Funktion test_inside_a_func verwendete Syntax zu verwenden. Sie können daher in den meisten Fällen beide Methoden verwenden, obwohl das Erfassen der Ausgabe die sicherere Methode ist, die in jeder Situation immer funktioniert und den Rückgabewert von nachahmt Eine Funktion, die Sie in anderen Sprachen finden können, wie Vicky Ronnen richtig hervorhob.

3
Luca Borrione

Sie sind das Hauptproblem eines Schemas für benannte Ausgabevariablen, bei dem der Aufrufer den Variablennamen übergeben kann (unabhängig davon, ob eval oder declare -n verwendet wird). Es ist schrecklich, eine lokale Variable in einer Funktion nicht hinzufügen oder umbenennen zu können, ohne ALL die Aufrufer der Funktion zu überprüfen, um sicherzustellen, dass sie nicht denselben Namen wie der Ausgabeparameter übergeben möchten. (Oder in die andere Richtung, ich möchte nicht die Quelle der Funktion lesen müssen, die ich aufrufe, nur um sicherzustellen, dass der Ausgabeparameter, den ich verwenden möchte, kein lokaler in dieser Funktion ist.)

Der einzige Weg, dies zu umgehen, ist die Verwendung einer einzelnen dedizierten Ausgabevariablen wie REPLY (wie von Evi1M4chine vorgeschlagen) oder einer Konvention wie der von Ron Burk vorgeschlagenen.

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

  • Die Funktion weist REPLY immer den Rückgabewert zu und 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 Beispiel wrapper). Der Exit-Code der Funktion wird durchgereicht, daher werden sie in z. Ein if oder while oder ein ähnliches Konstrukt funktioniert wie erwartet.
  • Syntaktisch ist der Funktionsaufruf immer noch eine einfache Anweisung.

Der Grund dafür ist, dass die Funktion call selbst keine lokalen Variablen enthält und keine anderen Variablen als REPLY verwendet, wodurch potenzielle Namenskonflikte vermieden werden. An dem Punkt, an dem der vom Aufrufer definierte Ausgabevariablenname zugewiesen wird, befinden wir uns effektiv im Gültigkeitsbereich des Aufrufers (technisch im identischen Gültigkeitsbereich der Funktion call) und nicht im Gültigkeitsbereich 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 "[email protected]"
}

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)
2
Karsten

Die Optionen wurden alle aufgezählt, denke ich. Bei der Auswahl eines Stils kommt es möglicherweise darauf an, welchen Stil Sie für Ihre jeweilige Anwendung am besten verwenden. In diesem Sinne möchte ich einen bestimmten Stil anbieten, den ich als nützlich empfunden habe. In bash befinden sich Variablen und Funktionen nicht im selben Namespace. Wenn Sie also die Variable mit dem gleichen Namen wie den Wert der Funktion behandeln, ist dies eine Konvention, die Namenskonflikte minimiert und die Lesbarkeit verbessert, wenn Sie diese konsequent anwenden. 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 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 verwenden, wenn Sie ihn benötigen, oder ihn ignorieren, wenn Sie ihn nicht benötigen. Die Variable "return" kann ebenfalls verwendet oder ignoriert werden, aber natürlich wird nur after die Funktion aufgerufen.

Dies ist natürlich nur eine Konvention. Es steht Ihnen frei, den zugeordneten Wert vor der Rückgabe nicht zu setzen (daher meine Konvention, ihn immer beim Start der Funktion auf Null zu setzen) oder seinen Wert durch erneuten Aufruf der Funktion (möglicherweise indirekt) zu zertrampeln. Trotzdem ist es eine Konvention, die ich sehr nützlich finde, wenn ich feststelle, dass ich Bash-Funktionen stark nutze.

Im Gegensatz zu dem Gefühl, dass dies ein Zeichen ist, sollte man z. "move to Perl", meine Philosophie ist, dass Konventionen immer wichtig sind, um die Komplexität einer beliebigen Sprache zu bewältigen.

2
Ron Burk

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

definition

url_parse() { # parse 'url' into: 'url_Host', 'url_port', ...
   local "[email protected]" # 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
}
1

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

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

1
apennebaker

In meinen Programmen ist dies standardmäßig die bereits vorhandene Variable $REPLY, die read genau für diesen Zweck verwendet.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Dies echoes

tadaa

Um Konflikte zu vermeiden, ist jedoch jede andere globale Variable ausreichend.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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

0
Evi1M4chine