sábado, 5 de janeiro de 2013

retorno de uma função em shellscript


referencia:
http://www.linuxjournal.com/content/return-values-bash-functions

(estou supondo que o leitor sabe criar arquivos de shellscript e executa-los, sabe criar  e executar funções com shellscripts e conhece sobre escopo de variáveis se nao souber he so googlar sobre shellscript...)


Funções em shellscript nao retornam valor para quem a chamou, elas retornam ou zero (signafica que rodou ok) ou um (que deu algum erro).

Uma alternativa pra obter valores da funcao seria fazer com que elas funcionem como procedimentos,  fazendo que elas apenas alterem uma variável global e apos rodar a funcao voce consulta o valor da variavel global.Por exemplo.


global_current_pid="not-defined-yet"
get_current_pid(){
    global_current_pid=$$;
}
get_current_pid
echo "$global_current_pid"



Mas usar variáveis globais pode ser desafiador para scripts grandes. Por isso é que as linguagens modernas fazer permitem recursos de variáveis locais que podem ser retornadas pela funcao.
Então outra alternativa seria a técnica de substituição de comando.


get_current_pid(){
    echo $$;
}
echo $(get_current_pid)


mas eu percebi que pra isso funcionar, tem que tomar um pequeno cuidado, o que pra mim, em particular passa a ser um problema e vou explicar o porquê depois. Vamos ver o problema primeiro:


get_current_pid(){
    echo "getting current pid..."
    echo $$;
}
echo $(get_current_pid)

meu retorno ficou...

[wagner@fzlbpmshost bin]$ ./fzl_utils.sh
getting current pid... 2842



ou seja, quando a gente coloca alguns echos para imprimir algumas informações sobre o que esta acontecendo durante a execução do código o retorno da função fica poluído com essas informações. Como uma pessoa muito comum eu sempre coloco echos para entender melhor o que esta acontecendo o que faz dessa opcao algo inviavel pra mim (pelo menos enquanto nao encontro uma solucao pra isso).

a outra alternativa é usar eval...
vamos ao exemplo, depois a gente analisa...



get_current_pid(){
    echo "(inside function... ) getting current pid..."
    to_be_returned=$1
    eval $to_be_returned="'$$'"   
}

get_current_pid pidReceivedFromFunc
echo "$pidReceivedFromFunc"



executando isso, obtem-se:

[wagner@fzlbpmshost bin]$ ./fzl_utils.sh
(inside function... ) getting current pid...
4509


to_be_returned=$1 significa que a mesma variavel que a função recebeu será aquela que desejamos retornar, por isso coloquei um nome bem sugestivo "to_be_returned" :)
Mas pra entender bem o que vem depois você vai ter que perceber aqui um detalhe do que está contecendo...
coloque, por curiosidade a seguinte linha no script, pra ele ficar assim...


get_current_pid(){
    echo "(inside function... ) getting current pid..."
    echo $1
    to_be_returned=$1
    eval $to_be_returned="'$$'"   
}

get_current_pid pidReceivedFromFunc
echo "$pidReceivedFromFunc"



executando de novo, obtem-se:

[wagner@fzlbpmshost bin]$ ./fzl_utils.sh
(inside function... ) getting current pid...
pidReceivedFromFunc
4526

Caramba, o que a nossa funcao recebeu na verdade foi o nome da variavel que quem chamou definiu...


opa, agora sim, dá pra falar do eval:


primeiro uma definição:
eval significa que o que vem depois  sera interpretada de novo. de novo. Ou seja,  o script quando esta rodando já esta sendo interpretado pelo bash pra que seja executado de fato, mas quando chega nessa linha o bash interpreta o eval primeiro e quando isso acontece o bash fica sabendo que vai ter que interpretar de novo o que vem depois desse eval pra depois sim rodar isso que vem depois

agora vendo como isso aconteceu no codigo:
o bash quendo vê o eval ele fica bem esperto com o que vem depois desse eval

e o bash vai perceber que  varival local to_be_returned vai estar valendo $1 que é exatamente o nome da variável que a função recebeu, ou seja

to_be_returned=pidReceivedFromFunc

aí o bash vai considerar o seguinte:
"como eu (bash) percebi que tinha um eval antes de 

to_be_returned=pidReceivedFromFunc


então vou interpretar isso de novo...
pidReceivedFromFunc=$$

entao, por fim, o que acontece na verdade é que  a nossa função recebeu um nove de varíavel que acabou recebendo o valor que a função estava querenddo retornar e então quem chamou poderá consultar o que foi atribuído a essa variável e obter o retorno da função.

Qual o efeito colateral disso? Por ser uma "gambiarra" na verdade, destinada a contornar o fato de que a função em shellscript deveria se chamar na verdade procedimento, pelo fato de não retornar valor, é bem provável que haja efeitos colaterais...

veja isso...


get_current_pid(){
    echo "(inside function... ) getting current pid..."
    echo $1
    to_be_returned=$1
    eval $to_be_returned=$$
}

test_if_the_name_of_the_variable_passed_to_function_is_global(){
    echo "$pidReceivedFromFunc";
}

get_current_pid pidReceivedFromFunc
echo "$pidReceivedFromFunc"
test_if_the_name_of_the_variable_passed_to_function_is_global

essa função, que nao tem na a ver com o relacionamento entre a chamada da funcao get_current_pid e a variavel criada para obter o valor do pid corrente, acaba conhecendo essa variável criada o que faz dela uma global da mesma forma... talvez pior porque quando você vai programar com liguagens procedurais tipo cobol você já fica esperto com as variáveis globais, coloca lá uma área do código pra elas e trabalha com cuidado com relaçao ao que está acontecendo com elas enquanto que neste caso você acaba criando variáveis globais "por aí..." durante o código...

a conclusão que tiro de tudo isso é a seguinte... você vai usar shellscript...
é uma lunguagem procedural... trabalha dentro desse paradigma então, evitando gambiarras. Na verdade não há nada de errado com variáveis globais a não ser o cuidado especial que você tem que tomar com elas...

no caso de funções bem simples como essa
get_current_pid onde não é necessário echos pra ajudar entender a execução do código, então a técnica de substituição de variáveis é melhor porque não cria variáveis globais...