Funções no Bash
Pergunta feita pelo Albano, do do grupo da comunidade debxp no telegram.
Albano pergunta:
A lógica de programação para implementação de funções, procedimentos ou derivações disso são iguais para o Shell como para C ou outras linguagens de programação? Ou existem diferenças além de sintaxe? Desculpem se minha pergunta foi irrelevante, mas realmente não consegui perceber essas diferenças...
Sim, existem várias diferenças que já foram apontadas nos nossos cursos e vídeos:
- No shell, uma função é qualquer comando composto que recebe um identificador:
nome() COMANDO_COMPOSTO
- O comando composto mais comum na definição de funções é o agrupamento com chaves:
nome() {
COMANDOS
}
Ou assim...
nome() { COMANDOS; }
- No shell, uma função não possui a definição de parâmetros formais: ela está limitada a receber parâmetros posicionais, passados como argumentos na sua chamada...
:~$ nome() { echo "Olá, $1!"; }
:~$ nome Fulano
Olá, Fulano!
- Como o shell não expressa valores, a instrução
return
(um comando, na verdade) não retorna nada e só serve para sair da função informando um estado de saída (sucesso ou erro)...
:~$ sub() { ((s=$1-$2)); return; }
:~$ sub 10 5; echo $s; echo $?
5
0
:~$ sub 5 5; echo $s; echo $?
0
1
- A menos que o comando composto seja um agrupamento com parêntesis ou as variáveis sejam definidas na própria função com
local
oudeclare
, todas as variáveis nas funções são de escopo global.
:~$ soma() { local sum; sum=$(($1+$2)); }
:~$ soma 2 4; echo $sum
:~$
Uma coisa que quase ninguém conta e eu estava guardando para o curso Técnicas do Shell: nós podemos definir um redirecionamento junto com a definição da função. Na verdade, a sintaxe completa de uma função é:
nome() COMANDO_COMPOSTO [REDIRECIONAMENTOS]
Observe:
:~$ lista() { printf '%s\n' $1 $2 $3; } >> teste.txt
:~$ lista banana laranja morango
:~$ cat teste.txt
banana
laranja
morango
:~$ lista abacate pitanga pera
:~$ cat teste.txt
banana
laranja
morango
abacate
pitanga
pera
No exemplo, nós definimos a função incluindo um redirecionamento de append da saída para o arquivo teste.txt
. Mas isso nos permite fazer coisas bem mais interessantes, como:
numerar() {
count=$1
while read line; do
echo $count $line
((count++))
done
} < $2
Nesta função, nós podemos informar o número inicial de uma contagem de linhas ($1
) e o arquivo que contém as linhas que serão numeradas na saída padrão ($2
):
~ $ numerar 10 teste.txt
10 banana
11 laranja
12 morango
13 abacate
14 pitanga
15 pera
Ou seja, até o nome do arquivo participante do redirecionamento pode ser passado como argumento e ser expandido de um parâmetro posicional!
Outro exemplo de uso, é esta pequena implementação em Bash do utilitário paste
:
paste_files ()
{
mapfile FILE1;
mapfile FILE2 < $2;
for i in ${!FILE1[@]};
do
printf '%-12s %s\n' ${FILE1[i]} ${FILE2[i]};
done
} < $1
Aqui, o nome do arquivo expandido em $1
será redirecionado para a entrada do comando interno mapfile
, que lê todo um arquivo na entrada padrão e armazena suas linhas em um vetor (no caso, FILE1
). Já o segundo mapfile
, recebe o conteúdo do segundo arquivo pelo redirecionamento do nome expandido em $2
, armazenando-o em FILE2
.
Assim, a mesma função trabalha com dois redirecionamentos de leitura em pontos diferentes: um para um comando específico no corpo da função e outro para toda a função.
O resultado:
~ $ paste_files teste1.txt teste2.txt
banana abacate
laranja pitanga
morango pera
Nossa, fiz a pergunta e vi só agora a resposta! Ficou show de bola professor!