Restituisce valore in una funzione Bash


305

Sto lavorando con uno script bash e voglio eseguire una funzione per stampare un valore di ritorno:

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

Quando eseguo fun2, non stampa "34". Perché è così?


8
returnnel tuo caso è essenzialmente lo stesso di exit codequale intervallo da 0 - 255. Utilizzare echocome suggerito da @septi. I codici di uscita possono essere acquisiti con $?.
Devnull,

1
In questo caso è molto più flessibile usare già l'eco in fun1. È l'idea della programmazione unix: echo invia i risultati all'output standard che può quindi essere riutilizzato da altre funzioni con res = $ (fun1) - oppure essere reindirizzato direttamente ad altre funzioni:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide

Il modo corretto per farlo è quello di mettere le cose di livello superiore in una funzione e usare un locale con la regola di scoping dinamica di bash. Creerò una risposta per dimostrare, non è una caratteristica ben nota ma pienamente supportata.
Oliver,


Risposte:


374

Sebbene bash abbia returnun'istruzione, l'unica cosa che puoi specificare è lo stato della funzione exit(un valore tra 0e 255, 0 che significa "successo"). Cosìreturn non è quello che vuoi.

Potresti voler convertire la tua returndichiarazione in una echodichiarazione - in questo modo il tuo output di funzione potrebbe essere catturato usando$() parentesi graffe, che sembra essere esattamente quello che vuoi.

Ecco un esempio:

function fun1(){
  echo 34
}

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

Un altro modo per ottenere il valore restituito (se si desidera solo restituire un numero intero 0-255) è $?.

function fun1(){
  return 34
}

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

Inoltre, si noti che è possibile utilizzare il valore restituito per utilizzare la logica booleana come fun1 || fun2verrà eseguita solo fun2se fun1restituisce un 0valore. Il valore di ritorno predefinito è il valore di uscita dell'ultima istruzione eseguita all'interno della funzione.


2
È necessario eseguire fun1e quindi il valore restituito viene archiviato $?. Anche se non consiglierei di farlo ...
tamasgal

9
Perché non usare $??
Pithikos,

147
No, ho bisogno del maledetto valore di ritorno . Al diavolo l'eco.
Tomáš Zato - Ripristina Monica il

7
@Blauhirn in questo ambiente, con questo ||costrutto, un codice di uscita 0 è considerato successo e quindi "vero". Lo zero è zero e quindi falso. Pensa fun1 || fun2a una scorciatoia per "se fun1 restituisce successo o fun2 restituisce successo" piuttosto che un operatore sui valori di ritorno effettivi stessi.
David,

6
La cosa fastidiosa è che una funzione che dovrebbe fornire dati non può anche fare eco ad altre cose su stdout, perché anche il chiamante che usa $ () lo riceverà e si confonderà o dovrà analizzare l'output. Le variabili globali non sono eccezionali perché è solo una questione di tempo prima di utilizzare la stessa var globale in due punti che si verificano nidificati e che i dati potrebbero andare persi. Dovrebbero esserci canali separati per la stampa dei dati rispetto all'invio dei dati.
Oliver

68

$(...)cattura il testo inviato a stdout dal comando contenuto all'interno. returnnon viene emesso su stdout. $?contiene il codice risultato dell'ultimo comando.

fun1 (){
  return 34
}

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

6
returnè usato per impostare $?quale è il exit status. Nell'esempio di cui sopra, fun1's exit statussarebbero 34. Inoltre, nota che $(...)cattura anche stderr oltre a stdout dal comando specificato.
picchiata il

59

Le funzioni in Bash non sono come in un'altra lingua; sono in realtà comandi. Quindi le funzioni vengono utilizzate come se fossero binari o script recuperati dal tuo percorso. Dal punto di vista della logica del programma, non dovrebbe esserci alcuna differenza.

I comandi della shell sono collegati da pipe (ovvero flussi) e non da tipi di dati fondamentali o definiti dall'utente, come nei linguaggi di programmazione "reali". Non esiste una cosa come un valore di ritorno per un comando, forse soprattutto perché non esiste un modo reale per dichiararlo. Potrebbe verificarsi nella pagina man o --helpnell'output del comando, ma entrambi sono solo leggibili dall'uomo e quindi sono scritti al vento.

Quando un comando desidera ottenere input, lo legge dal suo flusso di input o dall'elenco degli argomenti. In entrambi i casi è necessario analizzare le stringhe di testo.

Quando un comando vuole restituire qualcosa, deve echofarlo nel suo flusso di output. Un altro modo spesso praticato è quello di memorizzare il valore di ritorno in variabili globali dedicate. La scrittura nel flusso di output è più chiara e flessibile, poiché può richiedere anche dati binari. Ad esempio, è possibile restituire facilmente un 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

Come altri hanno scritto in questo thread, il chiamante può anche usare la sostituzione dei comandi $()per acquisire l'output.

Parallelamente, la funzione "restituirebbe" il codice di uscita di gpg(GnuPG). Pensa al codice di uscita come a un bonus che altre lingue non hanno, o, a seconda del tuo temperamento, come a uno "Schmutzeffekt" delle funzioni della shell. Questo stato è, per convenzione, 0 in caso di successo o un numero intero nell'intervallo 1-255 per qualcos'altro. Per chiarire questo: return(comeexit ) può prendere solo un valore compreso tra 0 e 255, e valori diversi da 0 non sono necessariamente errori, come spesso affermato.

Quando non si fornisce un valore esplicito con return lo stato viene preso dall'ultimo comando in un'istruzione / funzione / comando Bash e così via. Quindi c'è sempre uno stato ed returnè solo un modo semplice per fornirlo.


4
+1 per la spiegazione delle funzioni rispetto ai comandi e in che modo influisce sulla nozione di rinvio di dati al chiamante
Oliver

4
+1 per spiegare che la programmazione della shell riguarda il collegamento di comandi tramite pipe. Altri linguaggi di programmazione compongono le funzioni tramite tipi di ritorno. Bash compone i comandi tramite flussi di testo.
jrahhali,

29

L' returnistruzione imposta il codice di uscita della funzione, più o meno come exitper l'intero script.

Il codice di uscita per l'ultimo comando è sempre disponibile nella $?variabile.

function fun1(){
  return 34
}

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

  res=$(fun1)
  echo $?  #<-- Outputs 34
}

21

Il problema con altre risposte è che usano un globale, che può essere sovrascritto quando diverse funzioni sono in una catena di chiamate, o echoche significa che la tua funzione non può produrre informazioni diagnostiche (dimenticherai che la sua funzione fa questo e il "risultato", cioè restituisce valore, conterrà più informazioni di quelle che il tuo chiamante si aspetta, portando a strani bug) o evalche è troppo pesante e caotico.

Il modo corretto per farlo è quello di mettere le cose di livello superiore in una funzione e usare una localregola di scoping dinamica con bash. Esempio:

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

Questo produce

nothing
hi
bye

Scoping dinamico significa che ret_valpunta a un oggetto diverso a seconda del chiamante! Questo è diverso dall'ambito lessicale, che è quello che usa la maggior parte dei linguaggi di programmazione. Questa è in realtà una funzionalità documentata , facile da perdere e non molto ben spiegata, ecco la documentazione per essa (l'enfasi è mia):

Le variabili locali alla funzione possono essere dichiarate con l'integrato locale. Queste variabili sono visibili solo alla funzione e ai comandi che invoca .

Per qualcuno con uno sfondo C / C ++ / Python / Java / C # / javascript, questo è probabilmente il più grande ostacolo: le funzioni in bash non sono funzioni, sono comandi e si comportano come tali: possono emettere in stdout/ stderr, possono reindirizzare / out, possono restituire un codice di uscita. Fondamentalmente non c'è alcuna differenza tra la definizione di un comando in uno script e la creazione di un eseguibile che può essere chiamato dalla riga di comando.

Quindi, invece di scrivere la tua sceneggiatura in questo modo:

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

scrivilo così:

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

dove main()dichiara ret_valcome locale tutte le altre funzioni restituiscono valori tramite ret_val.

Vedi anche la seguente domanda Unix e Linux: Ambito delle variabili locali nelle funzioni della shell .

Un'altra soluzione forse ancora migliore a seconda della situazione è quella pubblicata da ya.teck che utilizza local -n.


17

Un altro modo per raggiungere questo obiettivo sono i riferimenti ai nomi (richiede Bash 4.3+).

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

example RESULT
echo $RESULT

3
chiunque si chieda cosa -n <name>=<reference>fa: rende la variabile appena creata un riferimento a un'altra puntata da <reference>. Ulteriori incarichi da <name>eseguire sulla variabile referenziata.
Valerio,

7

Mi piace fare quanto segue se eseguito in uno script in cui è definita la funzione:

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"

Mi piace, perché posso quindi includere istruzioni echo nelle mie funzioni se lo desidero

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}

5

Come aggiunta ai post eccellenti di altri, ecco un articolo che riassume queste tecniche:

  • imposta una variabile globale
  • imposta una variabile globale, il cui nome è stato passato alla funzione
  • imposta il codice di ritorno (e selezionalo con $?)
  • 'echo' alcuni dati (e raccoglierli con MYVAR = $ (myfunction))

Restituzione di valori dalle funzioni Bash


Questa è la risposta migliore, poiché l'articolo tratta in modo chiaro tutte le opzioni.
mzimmermann,

-2

Git Bash su Windows usando array per più valori di ritorno

CODICE BASE:

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

USCITA ATTESA:

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:
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.