Jak zwrócić wartość ciągu z funkcji Bash


461

Chciałbym zwrócić ciąg z funkcji Bash.

Napiszę przykład w języku Java, aby pokazać, co chciałbym zrobić:

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

String variable = getSomeString();

Poniższy przykład działa w trybie bash, ale czy jest na to lepszy sposób?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
function funcName {Nawiasem mówiąc , jest to starsza składnia sprzed POSIX odziedziczona po wczesnym ksh (gdzie miała różnice semantyczne, których bash nie honoruje). funcName() {, functionzamiast, należy użyć zamiast; patrz wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

Ten link mówi o użyciu NAME () COMPOUND-CMD lub funkcji NAME {CMDS; } Tak function myFunction { blah; }jest w porządku; to function myFunction() { blah }nie jest w porządku, tzn. użycie nawiasu z funkcją słowa kluczowego.
Czy

Odpowiedzi:


290

Nie ma lepszego sposobu, o którym wiem. Bash zna tylko kody statusu (liczby całkowite) i łańcuchy zapisane na standardowym wyjściu.


15
+1 @ tomas-f: musisz bardzo uważać na to, co masz w tej funkcji „getSomeString ()”, ponieważ posiadanie dowolnego kodu, który w końcu spowoduje echo, oznacza, że ​​otrzymasz niepoprawny ciąg zwrotny.
Mani

11
To jest po prostu źle. Możesz zwrócić dowolne dane w zmiennej zwrotnej. Co wyraźnie jest lepszym sposobem.
Evi1M4chine

36
@ Evi1M4chine, um ... nie, nie możesz. Możesz ustawić zmienną globalną i nazwać ją „return”, jak widzę w skryptach. Ale to jest umownie, NIE powiązane programowo z wykonywaniem twojego kodu. „wyraźnie lepszy sposób”? Yyy ... nie. Podstawianie poleceń jest o wiele bardziej wyraźne i modułowe.
Wildcard

6
„Podstawianie poleceń jest o wiele bardziej wyraźne i modułowe” byłoby właściwe, gdyby pytanie dotyczyło poleceń; to pytanie brzmi: jak zwrócić ciąg znaków z funkcji bash! Wbudowany sposób na zrobienie tego, o co poprosił PO, jest dostępny od wersji Bash 4.3 (2014?) - zobacz moją odpowiedź poniżej.
zenaan

4
Oryginalne pytanie zawiera najprostszy sposób, aby to zrobić i działa dobrze w większości przypadków. Wartości zwracane przez Bash powinny być prawdopodobnie nazywane „kodami powrotu”, ponieważ są mniej podobne do standardowych wartości zwracanych w skryptach, a bardziej jak kody wyjścia poleceń numerycznych powłoki (możesz robić takie rzeczy somefunction && echo 'success'). Jeśli myślisz o funkcji jak o innym poleceniu, ma to sens; polecenia nie „zwracają” niczego przy wyjściu poza kodem stanu, ale w międzyczasie mogą wyświetlać rzeczy, które można przechwycić.
Beejor

193

Możesz sprawić, aby funkcja wzięła zmienną jako pierwszy argument i zmodyfikowała zmienną za pomocą łańcucha, który chcesz zwrócić.

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

Drukuje „foo bar rab oof”.

Edycja : dodano cytowanie w odpowiednim miejscu, aby umożliwić spację w łańcuchu w celu uwzględnienia komentarza @Luca Borrione.

Edycja : Jako demonstrację zobacz następujący program. Jest to rozwiązanie ogólnego zastosowania: pozwala nawet otrzymać ciąg znaków do zmiennej lokalnej.

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

To drukuje:

+ 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

Edycja : wykazanie, że wartość oryginalnej zmiennej jest dostępna w funkcji, co zostało niepoprawnie skrytykowane przez @Xichen Li w komentarzu.

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

Daje to wynik:

+ 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

1
Ta odpowiedź jest świetna! Parametry mogą być przekazywane przez odwołania, podobnie jak w C ++.
Yun Huang,

4
Byłoby miło otrzymać odpowiedź od eksperta na temat tej odpowiedzi. Nigdy nie widziałem tego używanego w skryptach, może z ważnego powodu. W każdym razie: to +1 To powinno było zostać głosowane za poprawną odpowiedzią
Jan

Czy to nie to samo, co fgmodpowiedź napisana w uproszczony sposób? To nie zadziała, jeśli ciąg foozawiera białe spacje, podczas gdy ten fgmbędzie .. jak pokazuje.
Luca Borrione

4
@XichenLi: dziękuję za pozostawienie komentarza z twoim komentarzem; proszę zobaczyć moją edycję. Możesz uzyskać wartość początkową zmiennej w funkcji za pomocą \$$1. Jeśli szukasz czegoś innego, daj mi znać.
bstpierre

1
@timiscoding To można naprawić za pomocą printf '%q' "$var". % q jest łańcuchem formatu dla ucieczki powłoki. Następnie przekaż to na surowo.
bb010g

99

Wszystkie powyższe odpowiedzi ignorują to, co zostało powiedziane na stronie podręcznika bash.

  • Wszystkie zmienne zadeklarowane w funkcji zostaną udostępnione środowisku wywołującemu.
  • Wszystkie zmienne zadeklarowane jako lokalne nie będą udostępniane.

Przykładowy kod

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

I wyjście

$ 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

Również w pdksh i ksh ten skrypt robi to samo!


10
Ta odpowiedź ma swoje zalety. Przyszedłem tutaj, myśląc, że chcę zwrócić ciąg z funkcji. Ta odpowiedź uświadomiła mi, że to tylko moje nawyki C #. Podejrzewam, że inni mogą mieć to samo doświadczenie.
LOAS

4
@ElmarZander Mylisz się, jest to całkowicie istotne. Jest to prosty sposób na uzyskanie w zakresie globalnym wartości zakresu funkcji, a niektórzy uznaliby to za lepsze / prostsze niż ewaluacyjne podejście do przedefiniowania zmiennej globalnej, jak opisano w bstpierre.
KomodoDave

local nie jest przenośny na skrypty inne niż bash, co jest jednym z powodów, dla których niektórzy go unikają.
don bright

Pytanie: Co ze zmiennymi w pętlach?
anu

1
Na komputerze Mac ($ bash --version GNU bash, wersja 3.2.57 (1) -release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.) poprawne jest, że pasująca zmienna globalna ma wartość zainicjowano, ale kiedy próbuję wywołać efekt uboczny tej samej zmiennej w innej funkcji f2, ten efekt uboczny nie jest utrzymywany. Wydaje się więc bardzo niespójne, a zatem nie jest dobre dla mojego użytkowania.
AnneTheAgile,

45

Bash, od wersji 4.3, lutego 2014 (?), Ma wyraźne wsparcie dla zmiennych referencyjnych lub referencji nazw (namerefs), wykraczających poza „eval”, z tą samą korzystną wydajnością i efektem pośrednim, i które mogą być wyraźniejsze w twoich skryptach, a także trudniejsze aby „zapomnieć o„ ewaluacji ”i naprawić ten błąd”:

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...

i również:

PARAMETRY

Zmiennej można przypisać atrybut nameref za pomocą opcji -n do wbudowanych poleceń deklaracji lub lokalnych poleceń (patrz opisy deklaracji i lokalnych poniżej), aby utworzyć nazwę lub odwołanie do innej zmiennej. Pozwala to na manipulowanie zmiennymi pośrednio. Ilekroć zmienna nameref jest przywoływana lub przypisywana, operacja jest faktycznie wykonywana na zmiennej określonej przez wartość zmiennej nameref. Nazeref jest często używany w funkcjach powłoki w celu odniesienia do zmiennej, której nazwa jest przekazywana jako argument do funkcji. Na przykład, jeśli nazwa zmiennej zostanie przekazana do funkcji powłoki jako pierwszy argument, uruchomiona

      declare -n ref=$1

wewnątrz funkcji tworzy zmienną nameref ref, której wartością jest nazwa zmiennej przekazywana jako pierwszy argument. Referencje i przypisania do referencji są traktowane jak referencje i przypisania do zmiennej, której nazwa została przekazana jako $ 1. Jeśli zmienna kontrolna w pętli for ma atrybut nameref, lista słów może być listą zmiennych powłoki, a odniesienie nazwy zostanie utworzone dla każdego słowa na liście, po kolei, gdy pętla zostanie wykonana. Zmienne tablicowe nie mogą mieć atrybutu -n. Jednak zmienne nameref mogą odwoływać się do zmiennych tablicowych i zmiennych tablic dolnych. Nazwy można ⋅ rozbroić za pomocą opcji -n wbudowanego rozbrojenia. W przeciwnym razie, jeśli polecenie unset zostanie wykonane z nazwą zmiennej nameref jako argumentem,

Na przykład ( EDYCJA 2 : (dziękuję Ron) wstawił (przedrostek) nazwę zmiennej wewnętrznej funkcji, aby zminimalizować kolizje zmiennych zewnętrznych, które ostatecznie powinny poprawnie odpowiedzieć, problem podniesiony w komentarzach Karstena):

# $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
}

i testowanie tego przykładu:

$ return_a_string result; echo $result
The date is 20160817

Zauważ, że wbudowana bash „deklaruj”, gdy jest używana w funkcji, domyślnie powoduje, że zadeklarowana zmienna jest „lokalna”, a „-n” może być również używane z „lokalną”.

Wolę odróżniać zmienne „ważne deklarować” od „nudnych zmiennych lokalnych”, więc użycie „deklarowania” i „lokalnego” w ten sposób działa jako dokumentacja.

EDYCJA 1 - (Odpowiedź na komentarz poniżej przez Karstena) - Nie mogę już dodawać komentarzy poniżej, ale komentarz Karstena zmusił mnie do myślenia, więc zrobiłem następujący test, który DZIAŁA WYGODNIE, AFAICT - Karsten, jeśli to czytasz, proszę podać dokładny zestaw kroków testowych z wiersza poleceń, pokazując, że istnieje problem, który, jak zakładasz, działa dobrze:

$ return_a_string ret; echo $ret
The date is 20170104

(Uruchomiłem to właśnie teraz, po wklejeniu powyższej funkcji do terminu bash - jak widać, wynik działa dobrze).


4
Mam nadzieję, że przeniknie to na szczyt. eval powinno być ostatecznością. Warto wspomnieć, że zmienne nameref są dostępne tylko od wersji bash 4.3 (zgodnie z dziennikiem zmian ) (wydanej w lutym 2014 r. [?]). Jest to ważne, jeśli chodzi o przenośność. Proszę zacytować instrukcję bash dotyczącą faktu, że declarew funkcjach tworzone są zmienne lokalne (ta informacja nie jest podawana przez help declare): „... Gdy są używane w funkcji, deklaruj i składaj, aby każda nazwa była lokalna, tak jak w przypadku polecenia lokalnego, chyba że - dostarczona jest opcja g ... "
init_js

2
Ma to ten sam problem aliasingu, co rozwiązanie eval. Kiedy wywołujesz funkcję i podajesz nazwę zmiennej wyjściowej, musisz unikać podania nazwy zmiennej, która jest używana lokalnie w wywoływanej funkcji. Jest to poważny problem z punktu widzenia enkapsulacji, ponieważ nie można po prostu dodawać lub zmieniać nazw nowych zmiennych lokalnych w funkcji bez, jeśli któryś z funkcji wywołujących chciałby użyć tej nazwy dla parametru wyjściowego.
Karsten

1
@Karsten zgodził się. w obu przypadkach (eval i namerefs) może być konieczne wybranie innej nazwy. Jedną zaletą podejścia nameref w porównaniu z eval jest to, że nie trzeba zajmować się uciekającymi łańcuchami. Oczywiście zawsze możesz zrobić coś takiego K=$1; V=$2; eval "$A='$V'";, ale jeden błąd (np. Parametr pusty lub pominięty) i byłoby to bardziej niebezpieczne. @zenaan problem podniesiony przez @Karsten ma zastosowanie, jeśli wybierzesz „wiadomość” jako nazwę zmiennej zwracanej zamiast „ret”.
init_js

3
Funkcja prawdopodobnie musi być zaprojektowana od samego początku, aby akceptowała argument nazwany, więc autor funkcji powinien być świadomy możliwości kolizji nazw i może użyć jakiejś typowej konwencji, aby tego uniknąć. Np. W funkcji X nazwij zmienne lokalne konwencją „X_LOCAL_name”.
Ron Burk,

34

Podobnie jak powyżej bstpierre , używam i polecam użycie jawnych nazw zmiennych wyjściowych:

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
}

Zwróć uwagę na użycie cytowania $. Pozwoli to uniknąć interpretacji treści $resultjako znaków specjalnych powłoki. Przekonałem się, że jest to rząd wielkości szybszy niż result=$(some_func "arg1")idiom przechwytywania echa. Różnica prędkości wydaje się jeszcze bardziej zauważalna przy użyciu bash na MSYS, gdzie przechwytywanie standardowe z wywołań funkcji jest niemal katastrofalne.

W porządku jest wysyłanie zmiennych lokalnych, ponieważ locals są dynamicznie skalowane w bash:

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

4
Pomaga mi to, ponieważ lubię używać wielu instrukcji echa do celów debugowania / logowania. Idiom przechwytywania echa zawodzi, ponieważ przechwytuje wszystkie z nich. Dziękuję Ci!
AnneTheAgile,

To jest (drugie najlepsze) właściwe rozwiązanie! Czysto, szybko, elegancko, rozsądnie.
Evi1M4chine

+2 za utrzymanie go w czystości. Już miałam powiedzieć. Jak wiele osób może ignorować łączenie echownętrza funkcji w połączeniu z zastępowaniem poleceń!
Anthony Rutledge

22

Możesz także przechwycić dane wyjściowe funkcji:

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

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

Wygląda dziwnie, ale jest lepszy niż użycie zmiennych globalnych IMHO. Przekazywanie parametrów działa jak zwykle, wystarczy umieścić je w nawiasach klamrowych lub tylnych.


11
poza alternatywną uwagą składniową, czy nie jest to dokładnie to samo, co op już napisał we własnym pytaniu?
Luca Borrione

12

Najprostszym i najbardziej niezawodnym rozwiązaniem jest stosowanie podstawiania poleceń, jak napisali inni ludzie:

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

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

Minusem jest wydajność, ponieważ wymaga to osobnego procesu.

Inna technika sugerowana w tym temacie, mianowicie przekazanie nazwy zmiennej do przypisania jako argumentu, ma skutki uboczne i nie poleciłbym jej w swojej podstawowej formie. Problem polega na tym, że prawdopodobnie będziesz potrzebować niektórych zmiennych w funkcji do obliczenia wartości zwracanej, i może się zdarzyć, że nazwa zmiennej przeznaczonej do przechowywania wartości zwracanej będzie zakłócać jedną z nich:

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

Oczywiście nie możesz zadeklarować wewnętrznych zmiennych funkcji jako lokalnych, ale naprawdę powinieneś to zawsze robić, ponieważ w przeciwnym razie możesz przypadkowo zastąpić zmienną niepowiązaną z zakresu nadrzędnego, jeśli istnieje taka o tej samej nazwie .

Jednym z możliwych obejść jest jawne zadeklarowanie przekazywanej zmiennej jako globalnej:

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

Jeśli nazwa „x” zostanie przekazana jako argument, drugi wiersz treści funkcji zastąpi poprzednią deklarację lokalną. Ale same nazwy mogą nadal przeszkadzać, więc jeśli zamierzasz użyć wartości poprzednio zapisanej w przekazanej zmiennej przed zapisaniem wartości zwracanej, pamiętaj, że musisz skopiować ją do innej zmiennej lokalnej na samym początku; w przeciwnym razie wynik będzie nieprzewidywalny! Poza tym będzie to działać tylko w najnowszej wersji BASH, a mianowicie 4.2. Bardziej przenośny kod może wykorzystywać jawne konstrukcje warunkowe z tym samym efektem:

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

Być może najbardziej eleganckim rozwiązaniem jest zarezerwowanie jednej globalnej nazwy dla zwracanych wartości funkcji i konsekwentne stosowanie jej w każdej pisanej funkcji.


3
To ^^^. Nieumyślne aliasing, który przerywa enkapsulację, stanowi duży problem zarówno z rozwiązaniami, jak evali declare -n. Obejście polegające na posiadaniu pojedynczej dedykowanej nazwy zmiennej, takiej jak resultdla wszystkich parametrów wyjściowych, wydaje się jedynym rozwiązaniem, które nie wymaga, aby funkcje znały wszystkich swoich rozmówców, aby uniknąć konfliktów.
Karsten

12

Jak wspomniano wcześniej, „poprawnym” sposobem na zwrócenie ciągu znaków z funkcji jest zastąpienie polecenia. W przypadku, gdy funkcja musi również wyświetlać dane na konsoli (jak wspomniano powyżej @Mani), utwórz tymczasowy plik fd na początku funkcji i przekieruj do konsoli. Zamknij tymczasowy fd przed zwróceniem łańcucha.

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

wykonywanie skryptu bez parametrów powoduje ...

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

mam nadzieję, że to pomaga ludziom

-Andy


6
Ma to swoje zastosowanie, ale ogólnie powinieneś unikać wyraźnego przekierowania do konsoli; dane wyjściowe mogą być już przekierowane lub skrypt może działać w kontekście, w którym nie istnieje tty. Można to obejść, kopiując 3>&1na początku skryptu, a następnie manipulując &1 &3i innym symbolem zastępczym &4w ramach funkcji. Jednak brzydkie.
jmb

8

Możesz użyć zmiennej globalnej:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

To daje

'some other string'

6

Aby zilustrować mój komentarz do odpowiedzi Andy'ego, z dodatkową manipulacją deskryptorem pliku, aby uniknąć użycia /dev/tty:

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

Wciąż jednak paskudne.


3

Sposób, w jaki to masz, jest jedynym sposobem na zrobienie tego bez naruszania zakresu. Bash nie ma pojęcia typów zwracanych, po prostu kody wyjściowe i deskryptory plików (stdin / out / err itp.)


3

Zwracając się do głowy Vicky Ronnen , biorąc pod uwagę następujący kod:

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



da

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

Może normalnym scenariuszem jest użycie składni użytej w test_inside_a_funcfunkcji, dlatego w większości przypadków można użyć obu metod, chociaż przechwytywanie danych wyjściowych jest bezpieczniejszą metodą zawsze działającą w każdej sytuacji, naśladując zwracaną wartość z funkcji, którą można znaleźć w innych językach, jak Vicky Ronnenprawidłowo wskazano.


2

Myślę, że wszystkie opcje zostały wyliczone. Wybór jednego może sprowadzać się do kwestii najlepszego stylu dla konkretnej aplikacji iw tym duchu chcę zaoferować jeden konkretny styl, który uważam za użyteczny. W bash zmienne i funkcje nie są w tej samej przestrzeni nazw. Tak więc traktowanie zmiennej o tej samej nazwie jako wartości funkcji jest konwencją, która według mnie minimalizuje konflikty nazw i poprawia czytelność, jeśli zastosuję ją rygorystycznie. Przykład z prawdziwego życia:

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"
}

I przykład użycia takich funkcji:

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

Jak widać, status zwrotu jest dostępny do użycia, gdy jest to potrzebne, lub zignoruj, jeśli nie potrzebujesz. Zmienną „zwróconą” można również użyć lub zignorować, ale oczywiście dopiero po wywołaniu funkcji.

Oczywiście to tylko konwencja. Możesz powrócić do ustawienia powiązanej wartości przed jej zwróceniem (stąd moja konwencja zawsze zerowania jej na początku funkcji) lub podeptania jej wartości poprzez ponowne wywołanie funkcji (być może pośrednio). Mimo to jest to konwencja, która uważam za bardzo przydatną, jeśli często używam funkcji bash.

W przeciwieństwie do sentymentu, że jest to znak, który należy np. „Przejść do perla”, moja filozofia jest taka, że ​​konwencje są zawsze ważne dla zarządzania złożonością dowolnego języka.


2

Możesz echonapisać, ale złap go poprzez potokowanie ( |) funkcji do czegoś innego.

Możesz to zrobić za pomocą expr, choć ShellCheck zgłasza to użycie jako przestarzałe.


Problem w tym, że po prawej stronie rury jest podpowłoka. Więc myfunc | read OUTPUT ; echo $OUTPUTnic nie daje. myfunc | ( read OUTPUT; echo $OUTPUT )otrzymuje oczekiwaną wartość i wyjaśnia, co dzieje się po prawej stronie. Ale oczywiście WYJŚCIE nie jest wtedy dostępne tam, gdzie jest potrzebne ...
Ed Randall

2

Kluczowym problemem każdego schematu „nazwanej zmiennej wyjściowej”, w którym osoba wywołująca może przekazać nazwę zmiennej (bez względu na to, czy używa evallub declare -n), jest nieumyślne aliasing, tzn. Kolizje nazw: Z punktu widzenia enkapsulacji okropne jest to, że nie można dodać ani zmienić nazwy zmienna lokalna w funkcji bez sprawdzania najpierw WSZYSTKICH wywołujących funkcję, aby upewnić się, że nie chcą przekazać tej samej nazwy co parametr wyjściowy. (Lub w innym kierunku, nie chcę czytać źródła funkcji, którą wywołuję, aby upewnić się, że parametr wyjściowy, którego zamierzam użyć, nie jest lokalny w tej funkcji).

Jedynym sposobem na to jest użycie pojedynczej dedykowanej zmiennej wyjściowej, takiej jak REPLY(jak sugeruje Evi1M4chine ) lub konwencji podobnej do tej zaproponowanej przez Rona Burka .

Jednak możliwe jest, aby funkcje korzystały wewnętrznie ze stałej zmiennej wyjściowej , a następnie dodać trochę cukru na wierzchu, aby ukryć ten fakt przed rozmówcą , jak robiłem z callfunkcji w poniższym przykładzie. Uważaj to za dowód koncepcji, ale najważniejsze są

  • Funkcja zawsze przypisuje wartość zwracaną REPLYi może również zwracać kod wyjścia, jak zwykle
  • Z perspektywy osoby dzwoniącej wartość zwracaną można przypisać do dowolnej zmiennej (lokalnej lub globalnej), w tym REPLY (patrz wrapperprzykład). Kod wyjścia funkcji przechodzi przez, więc używanie ich w np iflub whilelub podobne konstrukty działa zgodnie z oczekiwaniami.
  • Składniowo wywołanie funkcji jest nadal pojedynczą prostą instrukcją.

Powodem tego jest to, że callsama funkcja nie ma lokalnych i nie używa żadnych zmiennych innych niż REPLY, unikając potencjalnego konfliktu nazw. W miejscu, w którym przypisywana jest nazwa zmiennej wyjściowej zdefiniowanej przez program wywołujący, efektywnie znajdujemy się w zakresie programu wywołującego (technicznie w identycznym zakresie callfunkcji), a nie w zakresie wywoływanej funkcji.

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

Wynik:

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

1

wzorzec bash, aby zwrócić zarówno obiekty wartości skalarnych, jak i tablicowych :

definicja

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
}

wezwanie

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
}

0

W moich programach, zgodnie z konwencją, właśnie do tego służy wcześniej istniejąca $REPLYzmienna, która readużywa tego właśnie celu.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

To echoes

tadaa

Ale aby uniknąć konfliktów, wystarczy każda inna zmienna globalna.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Jeśli to nie wystarczy, polecam rozwiązanie Markarian451 .


-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=
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.