Zwraca wartość w funkcji Bash

304

Pracuję ze skryptem bash i chcę uruchomić funkcję, aby wydrukować wartość zwracaną:

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

Kiedy wykonuję fun2, nie drukuje „34”. Dlaczego tak jest?

mindia
źródło
8
returnw twoim przypadku jest zasadniczo taki sam, jak z exit codejakiego zakresu 0 - 255. Użyj echozgodnie z sugestią @septi. Kody wyjścia można przechwytywać za pomocą $?.
devnull
1
W takim przypadku korzystanie z echa w fun1 jest znacznie bardziej elastyczne. Jest to idea programowania unixowego: echo wysyła wyniki do standardowego wyjścia, które następnie mogą być ponownie wykorzystane przez inne funkcje z res = $ (fun1) - lub bezpośrednio do innych funkcji:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
Właściwym sposobem na to jest włączenie funkcji najwyższego poziomu w funkcję i użycie lokalnej z dynamiczną regułą zakresu bash. Stworzę odpowiedź, aby wykazać, że nie jest to dobrze znana funkcja, ale w pełni obsługiwana.
Oliver
Zobacz także: stackoverflow.com/a/8743103/12887
Jonathan Tran

Odpowiedzi:

372

Chociaż bash ma returninstrukcję, jedyną rzeczą, którą możesz określić, jest własny exitstatus funkcji (wartość pomiędzy 0, 0a 2550 oznacza „sukces”). Więc returnnie jest to, czego chcesz.

Możesz przekonwertować swoją returninstrukcję na echoinstrukcję - w ten sposób dane wyjściowe funkcji można przechwycić za pomocą $()nawiasów klamrowych, co wydaje się dokładnie tym, czego chcesz.

Oto przykład:

function fun1(){
  echo 34
}

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

Innym sposobem uzyskania wartości zwracanej (jeśli chcesz po prostu zwrócić liczbę całkowitą 0–255) jest $?.

function fun1(){
  return 34
}

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

Zauważ też, że możesz użyć wartości zwracanej, aby użyć logiki boolowskiej, tak jak fun1 || fun2uruchomi się tylko, fun2jeśli fun1zwróci 0wartość. Domyślna wartość zwracana to wartość wyjściowa ostatniej instrukcji wykonanej w ramach funkcji.

tamasgal
źródło
2
Musisz wykonać, fun1a następnie wartość zwracana jest przechowywana w $?. Chociaż nie poleciłbym tego zrobić…
tamasgal
9
Dlaczego nie użyć $??
Pithikos,
147
Nie, potrzebuję cholernej wartości zwrotu . Do diabła z echem.
Tomáš Zato - Przywróć Monikę
7
@Blauhirn w tym środowisku, przy tej ||konstrukcji kod wyjścia 0 jest uważany za sukces, a zatem „prawdziwy”. Niezerowa to błąd, a zatem fałsz. Pomyśl o fun1 || fun2skrócie „jeśli fun1 zwraca sukces lub fun2 zwraca sukces”, a nie operator samych wartości zwracanych.
davidA
6
Irytujące jest to, że funkcja, która powinna dostarczać dane, nie może również echa innych rzeczy na standardowe wyjście, ponieważ wywołujący używający $ () też to odbierze i zostanie zdezorientowany lub będzie musiał przeanalizować dane wyjściowe. Zmienne globalne nie są świetne, ponieważ to tylko kwestia czasu, zanim użyjesz tej samej zmiennej globalnej w dwóch miejscach, które są zagnieżdżone i dane mogą zostać utracone. Powinny istnieć oddzielne kanały drukowania danych zamiast wysyłania danych z powrotem.
Oliver,
68

$(...)przechwytuje tekst wysłany na standardowe wyjście przez zawarte w nim polecenie. returnnie wyświetla wyjścia standardowego. $?zawiera kod wynikowy ostatniego polecenia.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
źródło
5
Tak returnsłuży do ustawienia, $?które jest exit status. W powyższym przykładzie, fun1„y exit statusbyłby 34. Zauważ $(...)też, że oprócz podanego polecenia przechwytuje także stderr oprócz stdout.
swoop81
58

Funkcje w Bash nie są funkcjami jak w innym języku; to właściwie polecenia. Funkcje są więc używane tak, jakby były plikami binarnymi lub skryptami pobranymi z Twojej ścieżki. Z punktu widzenia logiki programu naprawdę nie powinno być żadnej różnicy.

Polecenia powłoki są połączone potokami (potokami), a nie podstawowymi lub zdefiniowanymi przez użytkownika typami danych, jak w „prawdziwych” językach programowania. Nie ma czegoś takiego jak wartość zwracana dla polecenia, być może głównie dlatego, że nie ma prawdziwego sposobu na jego zadeklarowanie. Może się to zdarzyć na stronie podręcznika lub na --helpwyjściu polecenia, ale oba są tylko czytelne dla człowieka i dlatego są zapisywane na wietrze.

Gdy polecenie chce uzyskać dane wejściowe, odczytuje je ze strumienia wejściowego lub listy argumentów. W obu przypadkach ciągi tekstowe muszą zostać przeanalizowane.

Gdy polecenie chce zwrócić coś, musi echoto zwrócić do strumienia wyjściowego. Innym często praktykowanym sposobem jest przechowywanie wartości zwracanej w dedykowanych zmiennych globalnych. Zapis do strumienia wyjściowego jest wyraźniejszy i bardziej elastyczny, ponieważ może również pobierać dane binarne. Na przykład możesz łatwo zwrócić BLOB:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

Jak napisali inni w tym wątku, osoba dzwoniąca może również użyć podstawienia polecenia $()do przechwycenia wyniku.

Równolegle funkcja „zwróciłaby” kod wyjścia gpg(GnuPG). Potraktuj kod wyjścia jako bonus, którego nie mają inne języki lub, w zależności od twojego temperamentu, jako „Schmutzeffekt” funkcji powłoki. Status ten, zgodnie z konwencją, wynosi 0 w przypadku powodzenia lub jest liczbą całkowitą z zakresu 1-255 dla czegoś innego. Aby to wyjaśnić: return(jak exit) może przyjmować tylko wartość z zakresu 0–255, a wartości inne niż 0 niekoniecznie są błędami, jak się często twierdzi.

Jeśli nie podasz wyraźnej wartości, returnstatus jest pobierany z ostatniego polecenia w instrukcji / funkcji / poleceniu Bash i tak dalej. Tak więc zawsze jest status i returnjest to po prostu łatwy sposób na jego podanie .

Andreas Spindler
źródło
3
+1 za wyjaśnienie funkcji vs polecenia i jak to wpływa na pojęcie wysyłania danych z powrotem do dzwoniącego
Oliver
3
+1 za wyjaśnienie, że programowanie powłoki polega na łączeniu poleceń za pomocą potoków. Inne języki programowania tworzą funkcje za pomocą typów zwrotów. Bash komponuje polecenia za pomocą strumieni tekstu.
jrahhali
29

returnInstrukcja ustawia kod wyjścia funkcji, tak samo jak exitzrobi dla całego skryptu.

Kod wyjścia dla ostatniego polecenia jest zawsze dostępny w $?zmiennej.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Austin Phillips
źródło
19

Problem z innymi odpowiedziami polega na tym, że albo używają globalnej, którą można zastąpić, gdy kilka funkcji znajduje się w łańcuchu połączeń, lub echoco oznacza, że ​​twoja funkcja nie może wyprowadzić informacji diagnostycznych (zapomnisz, że funkcja to robi i „wynik”, tj. Return wartość, będzie zawierać więcej informacji, niż oczekuje Twój rozmówca, co prowadzi do dziwnego błędu) lub evaljest zbyt ciężkie i zhackowane.

Właściwym sposobem na to jest włączenie funkcji najwyższego poziomu w funkcję i użycie localreguły dynamicznego określania zakresu przez bash. Przykład:

func1() 
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

To wychodzi

nothing
hi
bye

Dynamiczny zakres oznacza, że ret_valwskazuje inny obiekt w zależności od dzwoniącego! Różni się to od zakresu leksykalnego, którego używa większość języków programowania. Jest to właściwie udokumentowana funkcja , po prostu łatwa do pominięcia i niezbyt dobrze wyjaśniona, oto jej dokumentacja (nacisk jest mój):

Zmienne lokalne dla funkcji mogą być deklarowane za pomocą wbudowanego lokalnego. Te zmienne są widoczne tylko dla funkcji i wywoływanych przez nią poleceń .

Dla kogoś z tłem C / C ++ / Python / Java / C # / javascript jest to prawdopodobnie największa przeszkoda: funkcje w bash nie są funkcjami, są poleceniami i zachowują się w ten sposób: mogą wysyłać dane do stdout/ stderr, mogą wpuszczać / out, mogą zwrócić kod wyjścia. Zasadniczo nie ma różnicy między zdefiniowaniem polecenia w skrypcie a utworzeniem pliku wykonywalnego, który można wywoływać z wiersza poleceń.

Zamiast pisać taki skrypt:

top-level code 
bunch of functions
more top-level code

napisz to w ten sposób:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

gdzie main()deklaruje ret_valjako locali wszystkie inne funkcje zwracają wartości przez ret_val.

Zobacz także następujące pytanie dotyczące systemów Unix i Linux: Zakres zmiennych lokalnych w funkcjach powłoki .

Inną, być może nawet lepsze rozwiązanie w zależności od sytuacji, jest jednym wysłane przez ya.teck który korzysta local -n.

Oliver
źródło
16

Innym sposobem na osiągnięcie tego jest odwołanie do nazwy (wymaga Bash 4.3+).

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

example RESULT
echo $RESULT
ya.teck
źródło
2
każdy zastanawia się, co -n <name>=<reference>robi: sprawia, że ​​nowo utworzona zmienna jest odniesieniem do innej wskazanej przez <reference>. Dalsze przypisania <name>są wykonywane na odnośnej zmiennej.
Valerio,
7

Lubię wykonywać następujące czynności, jeśli działa w skrypcie, w którym funkcja jest zdefiniowana:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

Podoba mi się to, ponieważ mogę w razie potrzeby dołączyć do moich instrukcji echo

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
dok
źródło
4

Jako dodatek do doskonałych postów innych osób, oto artykuł podsumowujący te techniki:

  • ustaw zmienną globalną
  • ustaw zmienną globalną, której nazwę przekazałeś do funkcji
  • ustaw kod powrotu (i podnieś go za pomocą $?)
  • „echo” niektórych danych (i podnieś je za pomocą MYVAR = $ (moja funkcja))

Zwracanie wartości z funkcji Bash

Tom Hundt
źródło
To najlepsza odpowiedź, ponieważ artykuł czysto omawia wszystkie opcje.
mzimmermann
-2

Git Bash w systemie Windows za pomocą tablic dla wielu zwracanych wartości

KOD PODSTAWOWY:

#!/bin/bash

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


function FN_MULTIPLE_RETURN_VALUES(){

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

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

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

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


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


read -p "Press Enter To Exit:"

OCZEKIWANY WYNIK:

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

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

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

Press Enter To Exit:
JMI MADISON
źródło