Como retornar um valor de seqüência de caracteres de uma função Bash

461

Gostaria de retornar uma string de uma função Bash.

Vou escrever o exemplo em java para mostrar o que gostaria de fazer:

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

String variable = getSomeString();

O exemplo abaixo funciona no bash, mas existe uma maneira melhor de fazer isso?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Tomas F
fonte
4
Como um aparte, function funcName {é a sintaxe legada pré-POSIX herdada do início do ksh (onde havia diferenças semânticas que o bash não honra). funcName() {, com no function, deve ser usado; consulte wiki.bash-hackers.org/scripting/obsolete
Charles Duffy:
Esse link diz para usar NAME () COMPOUND-CMD ou a função NAME {CMDS; } Tudo function myFunction { blah; }bem; não function myFunction() { blah }é bom, ou seja, o uso de parênteses com a função de palavra-chave.
Will

Respostas:

290

Não há melhor maneira que eu conheça. O Bash conhece apenas códigos de status (números inteiros) e seqüências de caracteres gravadas no stdout.

Philipp
fonte
15
+1 @ tomas-f: você precisa ter muito cuidado com o que possui nesta função "getSomeString ()", pois possui qualquer código que eventualmente ecoe significa que você receberá uma string de retorno incorreta.
Mani
11
Isto é simplesmente errado. Você pode retornar dados arbitrários dentro de uma variável de retorno. Qual claramente é um caminho melhor.
Evi1M4chine
36
@ Evi1M4chine, hum ... não, você não pode. Você pode definir uma variável global e chamá-la de "retorno", como eu vejo em seus scripts. Mas isso é por convenção, NÃO na verdade vinculado programaticamente à execução do seu código. "claramente uma maneira melhor"? Hum, não. A substituição de comandos é muito mais explícita e modular.
Curinga
6
"A substituição de comandos é muito mais explícita e modular" seria relevante se a pergunta fosse sobre comandos; esta pergunta é como retornar uma string, de uma função bash! Está disponível uma maneira integrada de fazer o que o OP solicitou desde o Bash 4.3 (2014?) - veja minha resposta abaixo.
Zenaan
4
A pergunta original contém a maneira mais simples de fazer isso e funciona bem na maioria dos casos. Os valores de retorno do bash provavelmente devem ser chamados de "códigos de retorno", porque são menos parecidos com os valores de retorno padrão nos scripts e mais com os códigos de saída de comando numérico do shell (você pode fazer coisas assim somefunction && echo 'success'). Se você pensa em uma função como apenas outro comando, faz sentido; Os comandos não "retornam" nada na saída que não seja um código de status, mas podem produzir coisas nesse meio tempo que você pode capturar.
Beejor
193

Você pode fazer com que a função pegue uma variável como o primeiro argumento e modifique a variável com a string que você deseja retornar.

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

Imprime "foo bar rab oof".

Edit : adicionada citação no local apropriado para permitir espaço em branco na string para tratar do comentário de @Luca Borrione.

Editar : Como demonstração, consulte o seguinte programa. Esta é uma solução de uso geral: permite até receber uma string em uma variável local.

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

Isso imprime:

+ 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

Edit : demonstrando que o valor da variável original está disponível na função, como foi incorretamente criticado por @Xichen Li em um comentário.

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

Isso fornece saída:

+ 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
bstpierre
fonte
1
Esta resposta é ótima! Os parâmetros podem ser passados ​​por referências, semelhante à idéia em C ++.
Yun Huang
4
Seria bom receber uma resposta de um especialista sobre essa resposta. Eu nunca vi isso usado em scripts, talvez por um bom motivo. Enfim: que é uma Deveria ter sido eleito para a resposta correta
John
Não é o mesmo da fgmresposta escrita de maneira simplificada? Isso não funcionará se a string foocontiver espaços em branco, enquanto a string fgmirá .. como ele está mostrando.
Luca Borrione
4
@XichenLi: obrigado por deixar um comentário com seu voto negativo; por favor, veja minha edição. Você pode obter o valor inicial da variável na função with \$$1. Se você estiver procurando por algo diferente, entre em contato.
bstpierre
1
@timiscoding Isso pode ser corrigido com a printf '%q' "$var". % q é uma string de formato para escape do shell. Depois é só passá-lo cru.
Bb010g
99

Todas as respostas acima ignoram o que foi declarado na página de manual do bash.

  • Todas as variáveis ​​declaradas dentro de uma função serão compartilhadas com o ambiente de chamada.
  • Todas as variáveis ​​declaradas locais não serão compartilhadas.

Código de exemplo

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

E saída

$ 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

Também sob pdksh e ksh, este script faz o mesmo!

Vicky Ronnen
fonte
10
Esta resposta tem seus méritos. Eu vim aqui pensando que queria retornar uma string de uma função. Essa resposta me fez perceber que isso era apenas minha conversa com C #. Suspeito que outros possam ter a mesma experiência.
LOAS
4
@ElmarZander Você está errado, isso é totalmente relevante. Essa é uma maneira simples de inserir no escopo global um valor de escopo de função, e alguns considerariam isso melhor / mais simples do que a abordagem de avaliação para redefinir uma variável global, conforme descrito por bstpierre.
KomodoDave 17/05
O local não é portátil para scripts que não sejam do bash, que é uma das razões pelas quais algumas pessoas o evitam.
não brilhante
Pergunta: E as variáveis ​​nos loops?
anu
1
Em um mac ($ bash --version GNU bash, versão 3.2.57 (1) -release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), é correto que uma variável global correspondente seja inicializado, mas quando tento afetar a mesma variável em outra função f2, esse efeito colateral não é persistente. Portanto, parece muito inconsistente e, portanto, não é bom para o meu uso.
AnneTheAgile
45

O Bash, desde a versão 4.3, fevereiro de 2014 (?), Possui suporte explícito para variáveis ​​de referência ou referências de nome (namerefs), além de "eval", com o mesmo desempenho benéfico e efeito indireto, o que pode ser mais claro em seus scripts e mais difícil para "esqueça de 'eval' e precise corrigir este erro":

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

e também:

PARÂMETROS

Uma variável pode ser atribuída ao atributo nameref usando a opção -n para os comandos internos declare ou local (consulte as descrições de declare e local abaixo) para criar um nameref ou uma referência a outra variável. Isso permite que variáveis ​​sejam manipuladas indiretamente. Sempre que a variável nameref for referenciada ou atribuída, a operação é realmente executada na variável especificada pelo valor da variável nameref. Um nameref é comumente usado nas funções de shell para se referir a uma variável cujo nome é passado como argumento para a função. Por exemplo, se um nome de variável for passado para uma função shell como seu primeiro argumento, executando

      declare -n ref=$1

dentro da função cria uma variável nameref ref cujo valor é o nome da variável passado como o primeiro argumento. Referências e designações para ref são tratadas como referências e designações para a variável cujo nome foi passado como $ 1. Se a variável de controle em um loop for tiver o atributo nameref, a lista de palavras poderá ser uma lista de variáveis ​​de shell e uma referência de nome será estabelecida para cada palavra da lista, por sua vez, quando o loop for executado. Variáveis ​​de matriz não podem receber o atributo -n. No entanto, as variáveis ​​nameref podem fazer referência a variáveis ​​de matriz e variáveis ​​de matriz subscritas. Os namerefs podem ser desabilitados usando a opção -n para o embutido desabilitado. Caso contrário, se unset for executado com o nome de uma variável nameref como argumento,

Por exemplo ( EDIT 2 : (obrigado Ron), coloca o nome da variável interna da função (prefixado), para minimizar conflitos de variáveis ​​externas, o que deve finalmente responder adequadamente, a questão levantada nos comentários de Karsten):

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

e testando este exemplo:

$ return_a_string result; echo $result
The date is 20160817

Observe que o bash "declare" builtin, quando usado em uma função, torna a variável declarada "local" por padrão, e "-n" também pode ser usado com "local".

Prefiro distinguir variáveis ​​"importante declarar" de variáveis ​​"chatas locais", portanto, usar "declarar" e "local" dessa maneira atua como documentação.

EDIT 1 - (Resposta ao comentário abaixo por Karsten) - Não consigo mais adicionar comentários abaixo, mas o comentário de Karsten me fez pensar, então eu fiz o seguinte teste que FUNCIONA MUITO BEM, AFAICT - Karsten se você ler isso, forneça um conjunto exato das etapas de teste na linha de comando, mostrando o problema que você supõe existir, porque essas etapas a seguir funcionam perfeitamente:

$ return_a_string ret; echo $ret
The date is 20170104

(Fiz isso agora, depois de colar a função acima em um termo básico - como você pode ver, o resultado funciona muito bem.)

zenaan
fonte
4
É minha esperança que isso chegue ao topo. avaliação deve ser o último recurso. Digno de menção é que as variáveis ​​nameref estão disponíveis apenas desde o bash 4.3 (de acordo com o changelog ) (lançado em fevereiro de 2014 [?]). Isso é importante se a portabilidade for uma preocupação. Por favor, cite o manual do bash sobre o fato de declarecriar variáveis ​​locais dentro de funções (essas informações não são fornecidas por help declare): "... Quando usadas em uma função, declare e typeset torne cada nome local, como no comando local, a menos que - a opção g é fornecida ... "
init_js
2
Isso tem o mesmo problema de aliasing da solução de avaliação. Quando você chama uma função e passa o nome da variável de saída, evita passar o nome de uma variável usada localmente na função que você chama. Esse é um grande problema em termos de encapsulamento, pois você não pode simplesmente adicionar ou renomear novas variáveis ​​locais em uma função sem que algum dos chamadores de funções queira usar esse nome para o parâmetro de saída.
Karsten
1
@Karsten concordou. nos dois casos (eval e namerefs), pode ser necessário escolher um nome diferente. Uma vantagem da abordagem nameref sobre eval é que não é preciso lidar com seqüências de caracteres de escape. Claro, você sempre pode fazer algo como K=$1; V=$2; eval "$A='$V'";, mas um erro (por exemplo, um parâmetro vazio ou omitido), e seria mais perigoso. @zenaan, o problema levantado por @Karsten se aplica se você escolher "message" como o nome da variável de retorno, em vez de "ret".
init_js
3
Uma função presumivelmente deve ser projetada desde o início para aceitar um argumento nameref, para que o autor da função esteja ciente da possibilidade de uma colisão de nomes e possa usar algumas convenções típicas para evitar isso. Por exemplo, dentro da função X, nomeie variáveis ​​locais com a convenção "X_LOCAL_name".
precisa
34

Como bstpierre acima, eu uso e recomendo o uso de nomear explicitamente variáveis ​​de saída:

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
}

Observe o uso de citar o $. Isso evitará a interpretação do conteúdo $resultcomo caracteres especiais do shell. Eu descobri que esta é uma ordem de magnitude mais rápida que aresult=$(some_func "arg1") idioma de capturar um eco. A diferença de velocidade parece ainda mais notável usando o bash no MSYS, onde a captura de stdout de chamadas de função é quase catastrófica.

Não há problema em enviar variáveis ​​locais, pois os locais têm um escopo dinâmico no bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
fonte
4
Isso me ajuda porque eu gosto de usar várias instruções de eco para fins de depuração / registro. O idioma de capturar eco falha, pois captura todos eles. Obrigado!
AnneTheAgile
Esta é a (segunda melhor) solução adequada! Limpo, rápido, elegante, sensível.
Evi1M4chine
+2 para mantê-lo real. Eu estava prestes a dizer. Como tantas pessoas podem ignorar a combinação de um echointerior de uma função, combinada com a substituição de comandos!
Anthony Rutledge
22

Você também pode capturar a saída da função:

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

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

Parece estranho, mas é melhor do que usar variáveis ​​globais IMHO. A passagem de parâmetros funciona como de costume, basta colocá-los dentro das chaves ou dos backticks.

Chiborg
fonte
11
além da nota de sintaxe alternativa, não é exatamente a mesma coisa que o op já escreveu em sua própria pergunta?
Luca Borrione
12

A solução mais direta e robusta é usar a substituição de comandos, como outras pessoas escreveram:

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

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

A desvantagem é o desempenho, pois isso requer um processo separado.

A outra técnica sugerida neste tópico, ou seja, passar o nome de uma variável a ser atribuída como argumento, tem efeitos colaterais, e eu não a recomendaria em sua forma básica. O problema é que você provavelmente precisará de algumas variáveis ​​na função para calcular o valor de retorno, e pode acontecer que o nome da variável destinada a armazenar o valor de retorno interfira em uma delas:

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

Obviamente, você não pode declarar variáveis ​​internas da função como local, mas realmente deve sempre fazê-lo, caso contrário, por outro lado, pode substituir acidentalmente uma variável não relacionada do escopo pai, se houver uma com o mesmo nome .

Uma solução possível é uma declaração explícita da variável passada como global:

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

Se o nome "x" for passado como argumento, a segunda linha do corpo da função substituirá a declaração local anterior. Mas os nomes em si ainda podem interferir; portanto, se você pretende usar o valor anteriormente armazenado na variável passada antes de gravar o valor de retorno, lembre-se de copiá-lo para outra variável local logo no início; caso contrário, o resultado será imprevisível! Além disso, isso funcionará apenas na versão mais recente do BASH, a saber 4.2. Um código mais portátil pode utilizar construções condicionais explícitas com o mesmo efeito:

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

Talvez a solução mais elegante seja reservar um nome global para os valores de retorno da função e usá-lo consistentemente em todas as funções que você escreve.

Tomasz Żuk
fonte
3
Isso ^^^. O aliasing inadvertido que quebra o encapsulamento é o grande problema das soluções evale declare -n. A solução alternativa de ter um único nome de variável dedicado, como resultpara todos os parâmetros de saída, parece a única solução que não requer que uma função conheça todos os seus chamadores para evitar conflitos.
Karsten
12

Como mencionado anteriormente, a maneira "correta" de retornar uma seqüência de caracteres de uma função é com substituição de comando. Caso a função também precise ser enviada para o console (como @Mani menciona acima), crie um fd temporário no início da função e redirecione para o console. Feche o fd temporário antes de retornar sua string.

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

executar script sem parâmetros produz ...

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

espero que isso ajude as pessoas

-Andy

Andy
fonte
6
Isso tem seus usos, mas no geral você deve evitar um redirecionamento explícito para o console; a saída já pode ser redirecionada ou o script pode estar em execução em um contexto em que não existe nenhum tty. Você pode contornar isso duplicando 3>&1no início do script e manipulando &1 &3e outro espaço reservado &4na função. Feio o tempo todo, no entanto.
jmb
8

Você poderia usar uma variável global:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Isto dá

'some other string'
Fritz G. Mehner
fonte
6

Para ilustrar meu comentário sobre a resposta de Andy, com manipulação adicional de descritor de arquivo para evitar o uso de /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]"

Ainda desagradável, no entanto.

jmb
fonte
3

Do jeito que você tem, é a única maneira de fazer isso sem quebrar o escopo. O Bash não tem um conceito de tipos de retorno, apenas códigos de saída e descritores de arquivo (stdin / out / err, etc)

Daenyth
fonte
3

Abordando a cabeça de Vicky Ronnen , considerando o seguinte código:

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



darei

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

Talvez o cenário normal seja usar a sintaxe usada na test_inside_a_funcfunção, portanto, você pode usar os dois métodos na maioria dos casos, embora capturar a saída seja o método mais seguro sempre trabalhando em qualquer situação, imitando o valor retornado de uma função que você pode encontre em outros idiomas, conforme Vicky Ronnenindicado corretamente.

Luca Borrione
fonte
2

As opções foram todas enumeradas, eu acho. A escolha de uma pode se resumir a uma questão do melhor estilo para sua aplicação específica e, nesse sentido, quero oferecer um estilo em particular que achei útil. No bash, variáveis ​​e funções não estão no mesmo espaço para nome. Portanto, tratar a variável com o mesmo nome que o valor da função é uma convenção que acho que minimiza conflitos de nome e melhora a legibilidade, se eu a aplicar com rigor. Um exemplo da vida real:

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

E, um exemplo do uso de tais funções:

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

Como você pode ver, o status de retorno está lá para você usar quando precisar, ou ignorar se não precisar. A variável "retornada" também pode ser usada ou ignorada, mas é claro que somente após a função ser chamada.

Claro, isso é apenas uma convenção. Você é livre para deixar de definir o valor associado antes de retornar (daí a minha convenção de sempre anulá-lo no início da função) ou atropelar seu valor chamando a função novamente (possivelmente indiretamente). Ainda assim, é uma convenção que acho muito útil se eu estiver fazendo uso pesado das funções do bash.

Ao contrário do sentimento de que este é um sinal que se deve, por exemplo, "mudar para perl", minha filosofia é que as convenções são sempre importantes para gerenciar a complexidade de qualquer idioma.

Ron Burk
fonte
2

Você pode criar echouma string, mas pegue-a canalizando ( |) a função para outra coisa.

Você pode fazê-lo expr, embora o ShellCheck reporte esse uso como obsoleto.

apennebaker
fonte
O problema é que a coisa à direita do tubo é uma subcamada. Então myfunc | read OUTPUT ; echo $OUTPUTnão produz nada. myfunc | ( read OUTPUT; echo $OUTPUT )obtém o valor esperado e esclarece o que está acontecendo no lado direito. Mas é claro OUTPUT não estará disponível quando você precisar dele ...
Ed Randall
2

Eles são o principal problema de qualquer esquema de 'variável de saída nomeada' em que o chamador possa passar o nome da variável (usando evalou declare -n) é um aliasing inadvertido, ou seja, conflitos de nome: Do ponto de vista do encapsulamento, é horrível não poder adicionar ou renomear uma variável local em uma função sem verificar primeiro todos os chamadores da função para garantir que eles não desejam passar o mesmo nome que o parâmetro de saída. (Ou na outra direção, não quero ler a fonte da função que estou chamando apenas para garantir que o parâmetro de saída que pretendo usar não seja local nessa função.)

A única maneira de contornar isso é usar uma única variável de saída dedicada como REPLY(como sugerido pelo Evi1M4chine ) ou uma convenção como a sugerida por Ron Burk .

No entanto, é possível que as funções usem internamente uma variável de saída fixa e, em seguida, adicione um pouco de açúcar por cima para ocultar esse fato do chamador , como fiz com a callfunção no exemplo a seguir. Considere isso uma prova de conceito, mas os pontos principais são

  • A função sempre atribui o valor de retorno REPLYe também pode retornar um código de saída como de costume
  • Da perspectiva do chamador, o valor de retorno pode ser atribuído a qualquer variável (local ou global) incluindo REPLY(veja o wrapperexemplo). O código de saída da função é passado, portanto, usá-los em, por exemplo, um ifouwhile ou construções semelhantes obras como esperado.
  • Sintaticamente, a chamada de função ainda é uma única declaração simples.

O motivo disso funcionar é porque a callfunção em si não possui locais e não usa variáveis ​​além de REPLY, evitando potencial para conflitos de nomes. No ponto em que o nome da variável de saída definida pelo chamador é atribuído, estamos efetivamente no escopo do chamador (tecnicamente no escopo idêntico da callfunção), e não no escopo da função que está sendo chamada.

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

Resultado:

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

padrão bash para retornar objetos de valor escalar e de matriz :

definição

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
}

invocação

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
}
Andrei Pozolotin
fonte
0

Nos meus programas, por convenção, é para isso que serve a $REPLYvariável preexistente , que é readusada exatamente para esse propósito.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Isso echoes

tadaa

Mas, para evitar conflitos, qualquer outra variável global servirá.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Se isso não for suficiente, recomendo a solução do Markarian451 .

Evi1M4chine
fonte
-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=
agtsoft
fonte