Valor de retorno em uma função Bash

305

Estou trabalhando com um script bash e quero executar uma função para imprimir um valor de retorno:

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

Quando executo fun2, ele não imprime "34". Por que esse é o caso?

mindia
fonte
8
returnno seu caso, é essencialmente o mesmo exit codeque o intervalo 0 - 255. Use echocomo sugerido por @septi. Os códigos de saída podem ser capturados com $?.
devnull
1
Nesse caso, é muito mais flexível já usar o eco em diversão1. É a idéia de unix-programming: echo envia os resultados para a saída padrão, que, em seguida, podem ser reutilizados por outras funções com res = $ (fun1) - ou diretamente ser canalizada para outras funções:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
A maneira correta de fazer isso é colocar as coisas de nível superior em uma função e usar um local com a regra de escopo dinâmico do bash. Vou criar uma resposta para demonstrar, não é um recurso conhecido, mas totalmente suportado.
Oliver
Veja também: stackoverflow.com/a/8743103/12887
Jonathan Tran

Respostas:

374

Embora o bash tenha uma returninstrução, a única coisa que você pode especificar com ela é o próprio exitstatus da função (um valor entre 0e 255, 0 significando "sucesso"). Então returnnão é o que você quer.

Você pode converter sua returninstrução em uma echoinstrução - dessa forma, a saída da sua função pode ser capturada usando $()chaves, o que parece ser exatamente o que você deseja.

Aqui está um exemplo:

function fun1(){
  echo 34
}

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

Outra maneira de obter o valor de retorno (se você deseja apenas retornar um número inteiro de 0 a 255) é $?.

function fun1(){
  return 34
}

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

Além disso, observe que você pode usar o valor de retorno para usar a lógica booleana, como fun1 || fun2só será executada fun2se fun1retornar um 0valor. O valor de retorno padrão é o valor de saída da última instrução executada na função.

tamasgal
fonte
2
Você precisa executar fun1e, em seguida, o valor de retorno é armazenado $?. Embora eu não recomendo fazer isso ...
tamasgal
9
Por que não usar $??
Pithikos
147
Não, eu preciso do maldito valor de retorno . Para o inferno com eco.
Tomáš Zato - Reinstate Monica
7
@ Blauhirn neste ambiente, com essa ||construção, um código de saída 0 é considerado sucesso e, portanto, "verdadeiro". Diferente de zero é erro e, portanto, falso. Pense fun1 || fun2como atalho para "se fun1 retorna sucesso ou fun2 retorna sucesso" em vez de um operador nos próprios valores de retorno reais.
Davida
6
O que é irritante é que uma função que deve fornecer dados também não pode ecoar outras coisas para o stdout, porque o chamador que usa $ () também receberá isso e fica confuso ou precisa analisar a saída. As variáveis ​​globais não são ótimas porque é apenas uma questão de tempo antes de você usar a mesma var global em dois locais que estão aninhados e os dados podem se perder. Deve haver canais separados para imprimir dados e enviar dados de volta.
Oliver Oliver
68

$(...)captura o texto enviado ao stdout pelo comando contido nele. returnnão gera saída para stdout. $?contém o código do resultado do último comando.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
fonte
6
Sim returné usado para definir $?qual é o exit status. No exemplo acima, fun1's exit statusseria 34. Além disso, observe que $(...)também captura stderr além de stdout do comando especificado.
swoop81
59

Funções no Bash não são funções como em outro idioma; na verdade são comandos. Portanto, as funções são usadas como se fossem binários ou scripts buscados no seu caminho. Da perspectiva da lógica do seu programa, não deve haver realmente nenhuma diferença.

Os comandos do shell são conectados por pipes (também conhecidos como fluxos), e não por tipos de dados fundamentais ou definidos pelo usuário, como em linguagens de programação "reais". Não existe um valor de retorno para um comando, talvez principalmente porque não há uma maneira real de declará-lo. Pode ocorrer na página do manual ou na --helpsaída do comando, mas ambos são legíveis apenas por humanos e, portanto, são gravados ao vento.

Quando um comando deseja obter entrada, ele o lê em seu fluxo de entrada ou na lista de argumentos. Nos dois casos, as cadeias de texto devem ser analisadas.

Quando um comando deseja retornar algo, ele deve retornar echoao fluxo de saída. Outra maneira frequentemente praticada é armazenar o valor de retorno em variáveis ​​globais dedicadas. A gravação no fluxo de saída é mais clara e flexível, porque também pode levar dados binários. Por exemplo, você pode retornar um BLOB facilmente:

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

Como outros escreveram neste segmento, o chamador também pode usar a substituição de comando $()para capturar a saída.

Paralelamente, a função "retornaria" o código de saída de gpg(GnuPG). Pense no código de saída como um bônus que outros idiomas não possuem ou, dependendo do seu temperamento, como um "Schmutzeffekt" das funções do shell. Esse status é, por convenção, 0 em caso de sucesso ou um número inteiro no intervalo de 1 a 255 para outra coisa. Para deixar isso claro: return(como exit) pode assumir apenas um valor de 0 a 255, e valores diferentes de 0 não são necessariamente erros, como geralmente é afirmado.

Quando você não fornece um valor explícito, returno status é obtido do último comando em uma instrução / função / comando do Bash e assim por diante. Portanto, sempre há um status e returné apenas uma maneira fácil de fornecê-lo.

Andreas Spindler
fonte
4
+1 para explicar funções vs comandos e como isso afeta a noção de enviar dados de volta para o chamador
Oliver
4
+1 para explicar que a programação do shell é sobre como conectar comandos por meio de pipes. Outras linguagens de programação compõem funções por meio de tipos de retorno. O Bash compõe comandos por meio de fluxos de texto.
precisa saber é o seguinte
29

A returninstrução define o código de saída da função, da mesma forma exitque fará para todo o script.

O código de saída para o último comando está sempre disponível na $?variável

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
fonte
21

O problema com outras respostas é que eles usam um global, que pode ser sobrescrito quando várias funções estão em uma cadeia de chamadas ou echosignifica que sua função não pode gerar informações de diagnóstico (você esquecerá que sua função faz isso e o "resultado", ou seja, retorna valor, conterá mais informações do que o chamador espera, levando a erros estranhos) ou evalmuito pesados ​​e invasivos.

A maneira correta de fazer isso é colocar as coisas de nível superior em uma função e usar uma localregra de escopo dinâmico do bash. Exemplo:

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

Isso gera

nothing
hi
bye

O escopo dinâmico significa que ret_valaponta para um objeto diferente, dependendo do chamador! Isso é diferente do escopo lexical, que é o que a maioria das linguagens de programação usa. Este é realmente um recurso documentado , fácil de perder, e não muito bem explicado, eis a documentação para ele (a ênfase é minha):

Variáveis ​​locais para a função podem ser declaradas com o local embutido. Essas variáveis ​​são visíveis apenas para a função e os comandos que ela chama .

Para alguém com experiência em C / C ++ / Python / Java / C # / javascript, este é provavelmente o maior obstáculo: as funções no bash não são funções, são comandos e se comportam como tal: podem gerar saída para stdout/ stderr, podem canalizar / out, eles podem retornar um código de saída. Basicamente, não há diferença entre definir um comando em um script e criar um executável que possa ser chamado a partir da linha de comando.

Então, em vez de escrever seu script assim:

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

escreva assim:

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

onde main()declara ret_valcomo locale todas as outras funções retornam valores via ret_val.

Veja também a seguinte pergunta sobre Unix e Linux: Escopo de variáveis ​​locais nas funções do shell .

Outra solução, talvez ainda melhor, dependendo da situação, é a postada pelo ya.teck que utiliza local -n.

Oliver
fonte
17

Outra maneira de obter isso é as referências de nome (requer o Bash 4.3+).

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

example RESULT
echo $RESULT
ya.teck
fonte
3
alguém se perguntando o que -n <name>=<reference>faz: torna a variável recém-criada uma referência a outra apontada por <reference>. Atribuições adicionais <name>são executadas na variável referenciada.
Valerio
7

Eu gosto de fazer o seguinte se estiver executando em um script em que a função está definida:

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"

Eu gosto disso, pois, em seguida, posso incluir instruções de eco em minhas funções, se eu quiser

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

Como um complemento para as excelentes postagens de outras pessoas, aqui está um artigo que resume essas técnicas:

  • definir uma variável global
  • defina uma variável global, cujo nome você passou para a função
  • defina o código de retorno (e pegue com $?)
  • 'eco' alguns dados (e pegue-os com MYVAR = $ (myfunction))

Retornando valores das funções Bash

Tom Hundt
fonte
Essa é a melhor resposta, pois o artigo discute de maneira clara todas as opções.
mzimmermann 03/03
-2

Git Bash no Windows usando matrizes para vários valores de retorno

CÓDIGO BASH:

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

SAÍDA ESPERADA:

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
fonte