Como trabalhar com funções em bash
Introdução
Pretendo deixar mais claro como utilizar funções em shell, também
pretendo abordar temas como retorno da função, declaração de constantes,
variáveis locais, variáveis globais, argumentos para as funções etc.
Espero que essa contribuição possa ajudar algumas pessoas no futuro. Todos os testes e exemplos mostrados aqui foram feitos em:
Debian GNU/Linux SID
Bash version 3.1.17
Uma coisa que não está na definição acima é que a função é sempre encapsulada, ou seja, ela tem uma estrutura própria que varia de linguagem para linguagem, porém normalmente segue uma forma mais ou menos parecida em todas estas.
function minha_funcao(){ echo 'minha funcao'; }
Com esta declaração criamos uma função chamada "minha função", como pode ser visto ela apenas imprime a frase 'minha funcao'.
A palavra "function" que precede o nome da função é opcional, porém para melhor visualização é sempre interessante mantê-la, afinal escrever está pequena palavra não atrapalhará a sua vida, mas facilitará na hora de rever o script.
Após isso, para usar a função basta colocar no seu script o nome dela:
minha_funcao
Não use '()' quando chamá-la, para o bash a função é como um comando, e esse é o motivo de apenas usarmos o nome, e não como na maioria das linguagens acabamos usando 'minha_funcao(args)'.
Um ponto importante é que a função deve ser declarada antes de ser usada, algumas linguagens deixam que a função esteja em qualquer lugar do código, em shell ela sempre deve vir antes da primeira utilização, por motivos de organização deixe as funções no começo do script, não crie funções no meio, nem no fim, coloque-as sempre no começo, essa é com certeza uma boa prática.
Vamos testar e ver o resultado:
$ function minha_funcao(){ echo 'minha funcao'; }
$ minha_funcao
minha funcao
Tudo funcionou como imaginávamos, agora vamos ver como o bash armazenou a função:
minha_funcao ()
{
echo 'minha funcao'
}
Como pode ser visto, ele criou uma estrutura sem o 'function' precedendo o nome da função, e também inseriu quebras de linhas.
Espero que essa contribuição possa ajudar algumas pessoas no futuro. Todos os testes e exemplos mostrados aqui foram feitos em:
Debian GNU/Linux SID
Bash version 3.1.17
O que são funções
Função nada mais é que um código escrito para resolver uma pequena parte de um problema, o que resolve o problema por inteiro é chamado de software, o que faz pequenas coisas em um software são as funções.Uma coisa que não está na definição acima é que a função é sempre encapsulada, ou seja, ela tem uma estrutura própria que varia de linguagem para linguagem, porém normalmente segue uma forma mais ou menos parecida em todas estas.
Como declarar e usar uma função dentro de um shell script
Para se criar uma função dentro de um shell script basta fazer:function minha_funcao(){ echo 'minha funcao'; }
Com esta declaração criamos uma função chamada "minha função", como pode ser visto ela apenas imprime a frase 'minha funcao'.
A palavra "function" que precede o nome da função é opcional, porém para melhor visualização é sempre interessante mantê-la, afinal escrever está pequena palavra não atrapalhará a sua vida, mas facilitará na hora de rever o script.
Após isso, para usar a função basta colocar no seu script o nome dela:
minha_funcao
Não use '()' quando chamá-la, para o bash a função é como um comando, e esse é o motivo de apenas usarmos o nome, e não como na maioria das linguagens acabamos usando 'minha_funcao(args)'.
Um ponto importante é que a função deve ser declarada antes de ser usada, algumas linguagens deixam que a função esteja em qualquer lugar do código, em shell ela sempre deve vir antes da primeira utilização, por motivos de organização deixe as funções no começo do script, não crie funções no meio, nem no fim, coloque-as sempre no começo, essa é com certeza uma boa prática.
Vamos testar e ver o resultado:
$ function minha_funcao(){ echo 'minha funcao'; }
$ minha_funcao
minha funcao
Tudo funcionou como imaginávamos, agora vamos ver como o bash armazenou a função:
minha_funcao ()
{
echo 'minha funcao'
}
Como pode ser visto, ele criou uma estrutura sem o 'function' precedendo o nome da função, e também inseriu quebras de linhas.
Como passar argumentos para a função? E o retorno da função?
Como disse anteriormente, o shell trata uma função como se fosse um
comando, ou seja, para se passar os argumentos basta fazer como fazemos
todos os dias no terminal:
minha_funcao arg1 arg2 .. argn
O recebimento dos argumentos é feito como se estivéssemos passando estes para um script, temos então que os argumentos serão passados para 'minha_funcao' como $0 $1 .. $n. Não vou falar aqui como tratar os argumentos, vou apenas ensinar como são utilizados, às vezes não precisamos nos preocupar como estes acabam sendo passados, porém podemos querer maior controle sobre os argumentos, e talvez utilizar um getopts seja uma boa solução, mas prefiro não entrar em detalhes aqui.
Vamos "melhorar" um pouco a função 'minha_funcao':
Agora vamos executá-la:
$ minha_funcao a b c
a
b
c
É bem simples a parte de passagem de argumentos, vamos só ver novamente como o bash armazenou a função:
minha_funcao ()
{
for i in $*;
do
echo $i;
done
}
Vamos lidar agora com o retorno da função, como a maioria das linguagens o shell tem o famoso 'return' nas suas funções, porém com algumas diferenças que devem ser lembradas.
A primeira coisa a saber é que o 'return' só pode retornar inteiros, não há nenhum outro valor que possa ser usado, isso é uma limitação do shell, se você pensava em retornar strings, números(reais), arrays, bem isso pode ser feito, porém não com o return.
Para ser mais exato a função do 'return' fica muito parecida com uma função boolean em C, no nosso caso vamos usar um esquema desses, mais precisamente '0' funcionou, '1' não funcionou. O valor que é retornado pelo 'return' vai para a variável '?', vamos mostrar um exemplo de uma função que use 'return':
Agora uma execução da função:
$ verdade a
$ echo $?
0
$ verdade
$ echo $?
1
Basicamente essa função retorna 0 se existir um argumento, e retorna 1 caso não exista.
Vamos agora brincar com retornos de outros tipos, vamos modificar a função 'verdade' para o nosso exemplo:
Vamos mostrar a execução:
$ verdade
mentira
$ verdade a
verdade
Agora temos um retorno de qualquer tipo, normalmente armazenamos em uma variável:
$ TRUE=$(verdade)
$ echo $TRUE
mentira
Cuidado ao fazer isso, pois todos os echos serão postos na variável como no exemplo abaixo:
Execução:
$ verdade
verdade
mentira
buga
$ TRUE=$(verdade)
$ echo $TRUE
verdade mentira buga
minha_funcao arg1 arg2 .. argn
O recebimento dos argumentos é feito como se estivéssemos passando estes para um script, temos então que os argumentos serão passados para 'minha_funcao' como $0 $1 .. $n. Não vou falar aqui como tratar os argumentos, vou apenas ensinar como são utilizados, às vezes não precisamos nos preocupar como estes acabam sendo passados, porém podemos querer maior controle sobre os argumentos, e talvez utilizar um getopts seja uma boa solução, mas prefiro não entrar em detalhes aqui.
Vamos "melhorar" um pouco a função 'minha_funcao':
function minha_funcao(){
for i in $*
do
echo $i
done
}
for i in $*
do
echo $i
done
}
Agora vamos executá-la:
$ minha_funcao a b c
a
b
c
É bem simples a parte de passagem de argumentos, vamos só ver novamente como o bash armazenou a função:
minha_funcao ()
{
for i in $*;
do
echo $i;
done
}
Vamos lidar agora com o retorno da função, como a maioria das linguagens o shell tem o famoso 'return' nas suas funções, porém com algumas diferenças que devem ser lembradas.
A primeira coisa a saber é que o 'return' só pode retornar inteiros, não há nenhum outro valor que possa ser usado, isso é uma limitação do shell, se você pensava em retornar strings, números(reais), arrays, bem isso pode ser feito, porém não com o return.
Para ser mais exato a função do 'return' fica muito parecida com uma função boolean em C, no nosso caso vamos usar um esquema desses, mais precisamente '0' funcionou, '1' não funcionou. O valor que é retornado pelo 'return' vai para a variável '?', vamos mostrar um exemplo de uma função que use 'return':
function verdade(){
[ $1 ] && return 0 || return 1
}
[ $1 ] && return 0 || return 1
}
Agora uma execução da função:
$ verdade a
$ echo $?
0
$ verdade
$ echo $?
1
Basicamente essa função retorna 0 se existir um argumento, e retorna 1 caso não exista.
Vamos agora brincar com retornos de outros tipos, vamos modificar a função 'verdade' para o nosso exemplo:
function verdade(){
[ $1 ] && echo 'verdade' || echo 'mentira'
}
[ $1 ] && echo 'verdade' || echo 'mentira'
}
Vamos mostrar a execução:
$ verdade
mentira
$ verdade a
verdade
Agora temos um retorno de qualquer tipo, normalmente armazenamos em uma variável:
$ TRUE=$(verdade)
$ echo $TRUE
mentira
Cuidado ao fazer isso, pois todos os echos serão postos na variável como no exemplo abaixo:
function verdade(){ echo verdade; echo mentira; echo buga; }
Execução:
$ verdade
verdade
mentira
buga
$ TRUE=$(verdade)
$ echo $TRUE
verdade mentira buga
Variáveis globais X variáveis locais X constantes
Variáveis globais são aquelas que podem ser vistas por qualquer função, e/ou por qualquer parte do script.
Variáveis locais são usadas somente dentro de uma função e tem precedência sobre as variáveis globais, isso quer dizer que se declararmos VAR globalmente, e depois declararmos VAR localmente, o shell usará a variável local, lembre-se disso.
Constante, como o próprio nome diz, é uma constante, ela não pode ter seu valor alterado de nenhuma forma no decorrer da execução do script.
Para criarmos uma variável global basta declararmos normalmente a variável, exemplo:
Com isso as duas variáveis acima podem ser vistas em qualquer lugar do código.
Para criamos uma variável local devemos primeiro declará-las dentro de uma função, e antes do nome da variável devemos usar a palavra local, exemplo:
Vamos ver o funcionamento da precedência:
$ TRUE='0'
$ FALSE='1'
$ function minha_funcao(){ local TRUE='verdade'; local FALSE='falso'; echo $TRUE; echo $FALSE; }
$ echo $TRUE
0
$ echo $FALSE
1
$ minha_funcao
verdade
falso
O uso de variáveis locais é muito importante, pois assim facilita a reutilização do código, além de ajudar na criação de uma biblioteca de funções, pois usando variáveis locais você evitará o conflito entre variáveis, então é altamente recomendado que sempre que for criar uma variável dentro de uma função que ela seja local, evite o máximo possível o uso de variáveis globais.
Vamos agora ver um pouco sobre as constantes. As constantes podem estar em qualquer parte do código, porém assim como as variáveis globais é preferível que elas sejam declaradas no começo do código, isso se aplica as funções, ou seja, se precisar de uma constante coloque-a no começo da função e de preferência sendo uma constante local.
Para se declarar uma constante devemos fazer:
declare -r <nome_da_constante>=<valor>
Exemplo:
O comando declare é uma função interna do bash, há outras opções, mas novamente prefiro não entrar em detalhes. No nosso caso a opção '-r', diz que a variável é somente leitura (readonly). Vamos ver o que ocorre se tentamos mudar ou deletar uma constante:
$ declare -r CONSTANTE='sempre igual'
$ CONSTANTE='mude'
bash: CONSTANTE: readonly variable
$ unset CONSTANTE
bash: unset: CONSTANTE: cannot unset: readonly variable
Para declararmos uma constante local fazemos:
A execução resultante é:
$ minha_funcao
verdade
falso
bash: TRUE: readonly variable
Variáveis locais são usadas somente dentro de uma função e tem precedência sobre as variáveis globais, isso quer dizer que se declararmos VAR globalmente, e depois declararmos VAR localmente, o shell usará a variável local, lembre-se disso.
Constante, como o próprio nome diz, é uma constante, ela não pode ter seu valor alterado de nenhuma forma no decorrer da execução do script.
Para criarmos uma variável global basta declararmos normalmente a variável, exemplo:
TRUE='0'
FALSE='1'
FALSE='1'
Com isso as duas variáveis acima podem ser vistas em qualquer lugar do código.
Para criamos uma variável local devemos primeiro declará-las dentro de uma função, e antes do nome da variável devemos usar a palavra local, exemplo:
function minha_funcao(){
local TRUE='verdade'
local FALSE='falso'
echo $TRUE
echo $FALSE
}
local TRUE='verdade'
local FALSE='falso'
echo $TRUE
echo $FALSE
}
Vamos ver o funcionamento da precedência:
$ TRUE='0'
$ FALSE='1'
$ function minha_funcao(){ local TRUE='verdade'; local FALSE='falso'; echo $TRUE; echo $FALSE; }
$ echo $TRUE
0
$ echo $FALSE
1
$ minha_funcao
verdade
falso
O uso de variáveis locais é muito importante, pois assim facilita a reutilização do código, além de ajudar na criação de uma biblioteca de funções, pois usando variáveis locais você evitará o conflito entre variáveis, então é altamente recomendado que sempre que for criar uma variável dentro de uma função que ela seja local, evite o máximo possível o uso de variáveis globais.
Vamos agora ver um pouco sobre as constantes. As constantes podem estar em qualquer parte do código, porém assim como as variáveis globais é preferível que elas sejam declaradas no começo do código, isso se aplica as funções, ou seja, se precisar de uma constante coloque-a no começo da função e de preferência sendo uma constante local.
Para se declarar uma constante devemos fazer:
declare -r <nome_da_constante>=<valor>
Exemplo:
declare -r CONSTANTE='sempre igual'
O comando declare é uma função interna do bash, há outras opções, mas novamente prefiro não entrar em detalhes. No nosso caso a opção '-r', diz que a variável é somente leitura (readonly). Vamos ver o que ocorre se tentamos mudar ou deletar uma constante:
$ declare -r CONSTANTE='sempre igual'
$ CONSTANTE='mude'
bash: CONSTANTE: readonly variable
$ unset CONSTANTE
bash: unset: CONSTANTE: cannot unset: readonly variable
Para declararmos uma constante local fazemos:
function minha_funcao(){
local -r TRUE='verdade'
local -r FALSE='falso'
echo $TRUE
echo $FALSE
TRUE='nem vem'
FALSE='isso ai cara'
}
local -r TRUE='verdade'
local -r FALSE='falso'
echo $TRUE
echo $FALSE
TRUE='nem vem'
FALSE='isso ai cara'
}
A execução resultante é:
$ minha_funcao
verdade
falso
bash: TRUE: readonly variable
WORKAROUNDs
Um dos problemas que tive que vale a pena ser documentado aqui foi
quando uma das minhas funções tinha o mesmo nome que um comando, o bash
sempre dá preferência a função e nunca ao comando, você têm algumas
opções, a primeira e a mais simples é mudar o nome da função, porém
digamos que na verdade essa função está em uma biblioteca que você fez, e
que diversas pessoas já estão utilizando, mudar o nome quebraria
diversos programas que estão funcionando corretamente, infelizmente você
foi bem infeliz e deu o nome de 'sort' para a sua função.
Bem, alguém precisa usar o comando sort, e sua biblioteca juntos, o que fazer se não pudermos mudar o nome?
A primeira coisa que pensei foi em fazer um SORT=$(which sort), essa é a melhor solução que encontrei para esse problema.
Outra situação que me deparei foi bem parecida com essa, o grande problema é que alguém teve a infeliz idéia de chamar a função de 'cd'; como o comando cd é do próprio bash, ele não retorna nada quando fazemos 'which cd'; sendo assim a solução acima não funcionaria, o que fiz foi utilizar outro comando do shell o 'builtin', esse comando chama qualquer builtin do bash ignorando a função do mesmo nome.
Bem, alguém precisa usar o comando sort, e sua biblioteca juntos, o que fazer se não pudermos mudar o nome?
A primeira coisa que pensei foi em fazer um SORT=$(which sort), essa é a melhor solução que encontrei para esse problema.
Outra situação que me deparei foi bem parecida com essa, o grande problema é que alguém teve a infeliz idéia de chamar a função de 'cd'; como o comando cd é do próprio bash, ele não retorna nada quando fazemos 'which cd'; sendo assim a solução acima não funcionaria, o que fiz foi utilizar outro comando do shell o 'builtin', esse comando chama qualquer builtin do bash ignorando a função do mesmo nome.
Nenhum comentário:
Postar um comentário