Como retornar um valor de string de uma function Bash

Eu gostaria de retornar uma string de uma function 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) 

    Não há melhor maneira que eu conheça. Bash conhece apenas códigos de status (inteiros) e strings gravados no stdout.

    Você pode fazer com que a function pegue uma variável como o primeiro argumento e modifique a variável com a string que 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 : adicionado citando no local apropriado para permitir que o espaço em branco na seqüência de caracteres para comentar comentário @Luca Borrione.

    Edit : Como uma demonstração, consulte o programa a seguir. Esta é uma solução de propósito 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 function, 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 dá 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 

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

    • Todas as variables ​​declaradas dentro de uma function serão compartilhadas com o ambiente de chamada.
    • Todas as variables ​​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!

    Como bstpierre acima, eu uso e recomendo o uso de nomear explicitamente as variables ​​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 cotar o $. Isso evitará a interpretação do conteúdo em $result como caracteres especiais do shell. Eu descobri que isso é uma ordem de grandeza mais rápida do que o idioma de result=$(some_func "arg1") de capturar um eco. A diferença de velocidade parece ainda mais notável usando o bash no MSYS, onde a captura padrão de chamadas de function é quase catastrófica.

    Não há problema em enviar variables ​​locais, já que os locais estão dinamicamente no escopo do bash:

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

    Bash, desde a versão 4.3, fevereiro de 2014 (?), Tem suporte explícito para referências ou referências de nomes (namerefs), além de “eval”, com o mesmo desempenho benéfico e efeito indireto, e que pode ser mais claro em seus scripts e também mais difícil para “esquecer de ‘eval’ e ter que corrigir esse 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 declare ou local builtin (consulte as descrições de declare e local abaixo) para criar um nameref ou uma referência a outra variável. Isso permite que variables ​​sejam manipuladas indiretamente. Sempre que a variável nameref é referenciada ou atribuída, a operação é realmente executada na variável especificada pelo valor da variável nameref. Um nameref é comumente usado dentro das funções do shell para se referir a uma variável cujo nome é passado como um argumento para a function. Por exemplo, se um nome de variável é passado para uma function de shell como seu primeiro argumento, executando

      declare -n ref=$1 

    dentro da function cria uma variável nameref ref cujo valor é o nome da variável passado como o primeiro argumento. Referências e atribuições para ref são tratadas como referências e atribuições à 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 pode ser uma lista de variables ​​do shell, e uma referência de nome será estabelecida para cada palavra na lista, por sua vez, quando o loop for executado. Variáveis ​​de matriz não podem receber o atributo -n. No entanto, as variables ​​nameref podem fazer referência a variables ​​de matriz e variables ​​de matriz indexadas. Namerefs pode não ser definido usando a opção -n para o builtin unset. Caso contrário, se unset for executado com o nome de uma variável nameref como um argumento, a variável referenciada pela variável nameref será desconfigurada.

    Por exemplo ( EDIT 2 : (obrigado Ron) namespaced (prefixado) o nome da variável interna da function, para minimizar os confrontos das variables ​​externas, que devem finalmente responder adequadamente, a questão levantada nos comentários por 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 

    Note que o bash “declare” embutido, quando usado em uma function, torna a variável declarada “local” por padrão, e “-n” também pode ser usado com “local”.

    Eu prefiro distinguir as variables ​​”declarar importante” das variables ​​”chato local”, então usar “declarar” e “local” dessa maneira funciona como documentação.

    EDIT 1 – (Resposta ao comentário abaixo por Karsten) – Eu não posso adicionar comentários abaixo mais, mas o comentário de Karsten me fez pensar, então eu fiz o seguinte teste que funciona bem, AFAICT – Karsten se você ler isso, por favor fornecer um conjunto exato de etapas de teste da linha de comando, mostrando o problema que você assume, porque estas etapas a seguir funcionam bem:

     $ return_a_string ret; echo $ret The date is 20170104 

    (Eu corri isso agora, depois de colar a function acima em um termo bash – como você pode ver, o resultado funciona bem).

    Você também pode capturar a saída da function:

     #!/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 variables ​​globais IMHO. parameters de passagem funcionam como de costume, basta colocá-los dentro das chaves ou backticks.

    Como mencionado anteriormente, a maneira “correta” de retornar uma string de uma function é com a substituição de comando. No caso de a function também precisar ser enviada para o console (como @Mani menciona acima), crie um fd temporário no início da function 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 params produz …

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

    Espero que isso ajude as pessoas

    -Andy

    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 para designar como argumento, tem efeitos colaterais e eu não a recomendaria em sua forma básica. O problema é que você provavelmente precisará de algumas variables ​​na function 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 

    Você pode, obviamente, não declarar variables ​​internas da function como local, mas você deve sempre fazê-lo, caso contrário, você pode, por outro lado, sobrescrever 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 transmitida 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 function sobrescreverá a declaração local anterior. Mas os nomes em si ainda podem interferir, portanto, se você pretende usar o valor armazenado anteriormente na variável passada antes de gravar o valor de retorno, esteja ciente de que você deve copiá-lo em outra variável local no início; caso contrário, o resultado será imprevisível! Além disso, isso só funcionará 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 apenas reservar um nome global para valores de retorno de function e usá-lo consistentemente em todas as funções que você escreve.

    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' 

    Para ilustrar meu comentário sobre a resposta de Andy, com manipulação adicional do 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.

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

    Endereçando 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 syntax usada na function test_inside_a_func , portanto você pode usar os dois methods na maioria dos casos, embora a captura da saída seja o método mais seguro sempre trabalhando em qualquer situação, imitando o valor de retorno de uma function que você pode encontrar em outras línguas, como Vicky Ronnen corretamente apontou.

    As opções foram todas enumeradas, eu acho. Escolhendo um pode vir a uma questão do melhor estilo para o seu aplicativo específico, e nesse sentido, eu quero oferecer um estilo particular que eu achei útil. No bash, variables ​​e funções não estão no mesmo namespace. Portanto, tratar a variável com o mesmo nome que o valor da function é uma convenção que eu acho que minimiza conflitos de nomes e aumenta a legibilidade, se eu aplicá-la rigorosamente. 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 você precisa, ou ignorar se você precisar. A variável “retornada” também pode ser usada ou ignorada, mas é claro somente após a function ser invocada.

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

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

    O principal problema de qualquer esquema de ‘variável de saída nomeada’ em que o chamador pode passar o nome da variável (usando eval ou declare -n ) é o aliasing inadvertido, ou seja, confrontos de nome: Do ponto de vista do encapsulamento, é horrível não ser possível para adicionar ou renomear uma variável local em uma function sem verificar TODOS os chamadores da function primeiro para garantir que eles não estejam querendo passar o mesmo nome que o parâmetro de saída. (Ou na outra direção, eu não quero ter que ler a fonte da function que estou chamando apenas para garantir que o parâmetro de saída que pretendo usar não seja um local nessa function.)

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

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

    • A function sempre atribui o valor de retorno a REPLY e 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 (consulte o exemplo de wrapper ). O código de saída da function é passado, portanto, usá-los em, por exemplo, um if ou while ou construções semelhantes funciona conforme o esperado.
    • Sintaticamente, a chamada de function ainda é uma simples instrução simples.

    A razão pela qual isso funciona é porque a própria function de call não tem locais e não usa variables ​​além de REPLY , evitando qualquer 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 function de call ), em vez de no escopo da function 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 abcd 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 

    Saída:

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

    Nos meus programas, por convenção, é para isso que a variável $REPLY pré-existente serve, que read usos para esse propósito exato.

     function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY 

    Isso faz echo

     tadaa 

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

     declare result function getSomeString { result="tadaa" } getSomeString echo $result 

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

    Você pode fazer o echo uma string, mas capturá-la canalizando ( | ) a function para outra coisa.

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

    padrão bash para retornar os objects de valor escalar e array :

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