Postagem em destaque

Criando bot do Telegram em Shell script com ShellBot

Trabalhando com funções

Visão geral

Como em outras linguagens de programação, o Bash tem sua funções, embora  sua aplicação seja limitada. Uma função é uma sub-rotina, um bloco de código que implementa um conjunto de operações.  Um 'nome' que executa uma tarefa especificada. Sempre que há código repetitivo, quando uma tarefa se repete com apenas pequenas variações no procedimento, é recomendado usar uma função.

Sintaxe

function NomeDaFuncao()
{
comandos ...
}
  ou
NomeDaFuncao()
{
comandos ...
}
A nomenclatura de uma função pode ser constituída de números, letras e símbolos.

A definição da função deve preceder a primeira chamada da mesma. Não existe um método para declarar a função como ocorre em outras linguagens. Funções são chamadas, simplesmente invocando seus nomes sem o uso de parênteses. Uma chamada é equivalente a um comando. Mantenha sempre a boa prática de instanciar suas funções no inicio do Script para melhor organização do código ou até mesmo garantir que a chamada da função esteja posterior a sua instância. A expressão 'function' que precede o nome da função é opcional, mas tenha a boa prática de usá-lo para melhor identificar sua função.



Criando uma função

Você deve escolher um nome que melhor define sua função, com o qual você seja capaz de identificar o seu procedimento e o que faz. Não faz sentido criar uma função chamada 'Contar_Numeros', se ela tem como finalidade criar arquivos. Acredite um 'bom nome' faz toda diferença.

Criando nossa primeira função chamada 'Mensagem'.
function Mensagem()
{
    # Imprimindo mensagem na tela.
    echo 'Isso é uma função.'
    echo 'FIM'
}
Note que o bloco de instruções devem ficar entre chaves '{}'.  As linhas comentadas são ignoradas pelo interpretador de comando.

Chamando a função.
$ Mensagem
Isso é uma função.
A chamada deve ser feita somente com o nome da função sem utilizar os parênteses. Quando a função é chamada, toda a instrução dentro dela é executada.

Função chamada 'NumeroPar', que lista somente os números pares de 0 à 10.
NumeroPar()
{
    for NUM in $(seq 0 2 10)
    do
        echo "Número $NUM"
    done
}

Chamando a função
$ NumeroPar
Número 0
Número 2
Número 4
Número 6
Número 8
Número 10
Foi executado um loop que recebe uma lista dos números pares gerados pelo comando 'seq'.

Funções compactas

Podemos criar funções compactadas, instanciando-as em uma única linha de comando; separando as instruções utilizando o carácter ';' ponto e vírgula. Apesar de funcionar, não é recomendado em casos onde há funções com muitas linhas de instruções; isso dificultaria a compressão do código e até uma manutenção posterior.

Uma função no modo compacto.
function Aviso(){ echo 'É uma função compacta.'; echo 'FIM'; }
Note o carácter (;) ponto e virgula ao fim de cada comando. Ele é o delimitador que separa as instruções a serem executadas.

 

Código de retorno

Os códigos de retorno são importantes para definir o status de execução de uma determinada tarefa. Identifica se um procedimento teve êxito ou se ocorreram erros durante a execução.

Capturar código:
Variável Descrição
$? Armazena o código de retorno do último comando executado.
Códigos de retorno:
Código Descrição
0 Sucesso
1 Falha

Para criar uma função e definir seus códigos de retorno, precisamos utilizar o comando 'return'.

Sintaxe

return N

O comando return faz com que uma função ou script-fonte finalize com o valor de retorno especificado em N. Se N for omitido, o status de retorno é do último comando executado dentro da função ou script.

Criando uma função chamada 'texto' que lê uma mensagem digitada pelo usuário.
texto()
{
    # Informativo
    echo 'Digite sua mensagem'

    # Lê a mensagem digitada.
    read MSG

    # Se 'MSG' for nula, finaliza a função com o código de retorno 1 (FALHA),
                                          # caso contrário retorna 0 (SUCESSO)
    [ -z $MSG ] && return 1 || return 0
}

Para exibir o código de retorno, vou utilizar a linha de comando 'echo $?' após a chamada da função.
$ texto; echo $?
Digite sua mensagem
Aprendendo Shell Script.
0
Observe que após a inserção do dados a função retornou seu código de status 0, indicando que o campo da mensagem não foi nulo e que a função teve êxito.

Executando a função sem inserir os dados.
$ texto; echo $?
Digite sua mensagem

1
Note que agora o código de retorno foi 1, indicando que nenhum dado foi inserido e que houve falha na execução da função.

Também podemos tratar diretamente o código de retorno, testando o valor armazenado na variável '$?'.

Criei uma função chamada 'Validar_Usuario' que verifica se o usuário informado existe no sistema; retornando uma mensagem e o código de status.
Validar_Usuario()
{
    # Lê os dados e armazena em 'USUARIO'
    read -p 'Informe o nome do usuário: ' USUARIO

    # Executa o comando sem imprimir nada na tela.
    id $USUARIO &>/dev/null

    # Verifica o código de retorno do comando 'id' 
    if [ $? -eq 0 ]; then

        # Se o usuário existir, imprime uma mensagem contendo o nome do usuário
        echo "$USUARIO: usuário existe."

        # Finaliza a função com status 0
        return 0
    fi

    # Mensagem padrão
    # Se a condição anterior foi satisfeita, o script jamais chegará aqui.
    echo "$USUARIO: usuário desconhecido."

    # Se chegou até aqui, finaliza a função com status 1
    return 1
}
Atenção ao tratar diretamente a variável '$?'. Certifique-se a ordem em que ela e o comando estão declaradas; pois você pode acabar capturando o código de retorno errado.

Chamando a função.
$ Validar_Usuario ; echo $?
Informe o nome do usuário: juliano
juliano: usuário existe.
0
Como o usuário 'juliano' é cadastrado no sistema,  a função imprimiu a mensagem informando que o usuário existe e retornou o código de status 0.

Testando com um usuário inválido.
$ Validar_Usuario ; echo $?
Informe o nome do usuário: francisco
francisco: usuário inválido.
1

Argumentos


O Shell trata a função como se fosse um comando e a forma de se passar os argumentos não é diferente. Realizando a chamada da função seguida dos argumentos. Esse recurso é bem útil quando precisamos mudar o comportamento de uma função passando parâmetros específicos para processamento.

Sintaxe

funcao arg1 arg2 argN ...

O recebimento dos argumentos são definidos por parâmetros posicionais, ou seja, determina a posição de um argumento na função.

Exemplo
$1 - O primeiro argumento da função
$2 - Segundo argumento da função
$8 - Oitavo argumento da função
....

Parâmetros posicionais:
$1..$9
${10}...${NN}

Função que imprime os parâmetros posicionais.
imprimir_args()
{
    echo "Parâmetros posicionais."
    echo "\$1 = $1"
    echo "\$2 = $2"
    echo "\$3 = $3"
}

Passando os parâmetros com a função.
$ imprimir_args Debian Slackware CentOS
Parâmetros posicionais.
$1 = Debian
$2 = Slackware
$3 = CentOS
Observe que o argumento é armazenado no parâmetro posicional equivalente a posição em que foi passado na função.

Criando uma função chamada 'info_grupo' que retorna informações sobre os grupos do usuário e que a passagem do argumento é obrigatória.
info_grupo()
{
    # Verifica se o primeiro argumento foi omitido.
    if [ -z "$1" ]; then
        echo "Requer nome do usuário."

        # Um código de status indicando que ocorreu um erro
        # na passagem de argumento.
        return 3
    fi
    
    # Obtem informações do usuário armazenado em '$1'
    id $1
 
    # Retorna o código de status do comando 'id'
    return $?
}
Criei apenas uma código de status personalizado, que indica que problema na passagem de parâmetros na função. No final utilizei o código de status do comando id para retorno padrão da função.

Chamado a função de 3 formas diferentes e capturando o código de status.

Com usuário válido.
$ info_grupo juliano; echo $?
uid=1000(juliano) gid=1000(juliano) grupos=1000(juliano),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
0

Com usuário inválido.
$ info_grupo francisco; echo $?
id: “francisco”: no such user
1

Sem argumento.
$ info_grupo; echo $?
Requer nome do usuário.
3

Além de tratar a passagem de argumento, a função possui 3 códigos de status. SUCESSO, FALHA e ERRO DE PARÂMETRO.

Nos exemplos anteriores capturamos os argumentos referenciando sua posição na função. Mas e quando a função possuir um número indeterminado de argumentos ? O que fazer para capturar todos eles ?
Para essa finalidade utilizamos a variável '$*', que lista todos os parâmetros em uma expressão.

Criei a função 'listar_args' que imprime todos os parâmetros posicionais e argumentos passados na função.
listar_args()
{
    # Parâmetro posicional.
    # ** Não é importante, apenas para demonstração ***
    i=1

    # Lista todos os argumentos da função
    for arg in $*
    do
        # Imprime o parâmetro posicional e o rgumento armazenado em 'arg'
        echo "\$$((i++)) = ${arg}"
    done
}

Chamando a função e passando seus argumentos.
$ listar_args Debian Slackware CentOS Ubuntu Fedora Arch Manjaro Suse
$1 = Debian
$2 = Slackware
$3 = CentOS
$4 = Ubuntu
$5 = Fedora
$6 = Arch
$7 = Manjaro
$8 = Suse
Dessa forma conseguimos listar todos os argumentos passados na função e tratar cada uma deles.

Argumentos compostos.

Um argumento composto é expressão que contém espaços entre elas e que precisa ser processada como um único argumento. Para isso precisamos passar o argumento entre aspas duplas (") ou simples ('), porém isso não seria suficiente; pois o laço iria interpretar como elementos separados.

Para demonstrar o que estou dizendo, vou chamar a função 'listar_args' passando um argumento composto.
$ listar_args "Linux Debian" Slackware
$1 = Linux
$2 = Debian
$3 = Slackware
Note que mesmo passando o argumento composto 'Linux Debian' entre aspas, ele foi interpretado como dois argumentos distintos.

Talvez você esteja pensando.
-E se colocar a variável $* entre aspas, desse jeito "$*" ? Não resolveria o problema ?
Posso dizer que pioraria ainda mais, mas vamos ver o resultado.
listar_args()
{
    # Parâmetro posicional.
    # ** Não é importante, apenas para demonstração ***
    i=1

    # MODIFICADO PARA "$*"
    for arg in "$*"
    do
        # Imprime o parâmetro posicional e o rgumento armazenado em 'arg'
        echo "\$$((i++)) = ${arg}"
    done
}

Chamando a função com os mesmos argumentos.
$ listar_args "Linux Debian" Slackware
$1 = Linux Debian Slackware
Agora o laço interpretou como um argumento. Porque ao usar variável "$*" (entre aspas) faz com que o laço leia os elementos como se fosse uma expressão única.

A maneira de resolver esse problema é utilizar a variável "$@" (entre aspas). Dessa forma os elementos são listados como strings separadas e os elementos entre aspas são interpretados com um único argumento.
listar_args()
{
    # Parâmetro posicional.
    # ** Não é importante, apenas para demonstração ***
    i=1

    # MODIFICADO PARA "$@"
    for arg in "$@"
    do
        # Imprime o parâmetro posicional e o rgumento armazenado em 'arg'
        echo "\$$((i++)) = ${arg}"
    done
}

Chamando a função.
$ listar_args "Linux Debian" Slackware
$1 = Linux Debian
$2 = Slackware
Pronto, agora o laço interpretou os parâmetros compostos e simples da função.

Muita atenção no momento que for usar as variáveis ($* e $@) para não perder horas procurando onde está o problema da sua função ou  script no momento em que lê os argumentos passados.
Relembrando
Variável Descrição
$* Lê todos os argumentos como uma expressão única. (junto)
$@ Lê todos os argumentos como strings protegidas. (separadas e composta)

Considerações finais

É possível criar um função dentro de outra.
 
Exemplo
Funcao1()
{
    # Bloco de instruções da primeira função
    echo Debian

    Funcao2()
    {
        # Bloco de instruções da segunda função
        echo Slackware
    }
}

Dessa maneira somente a função 'Funcao1' ficará visível e poderá ser chamada. Enquanto a 'Funcao2' só ficará disponível quando a 'Funcao1' for executada; ou seja, a função só será instanciada quando a função que a antecede for chamada e assim por diante.

Uma função não pode conter somente linhas comentadas ou está vazia.
Exemplo

MinhaFuncao()
{
    # Só tem comentário aqui.
    # Quem sou eu ? Para onde vou ?
}

ou

MinhaFuncao()
{
}
É gerado um erro imediato, pois o Shell aguarda instruções antes do '}' fecha aspas.

Não é possível instanciar uma função em conexões PIPE.
Exemplo
df -h | Capacidade(){ echo $*; }
Não é retornado nenhum erro, porém a função 'Capacidade' não é instanciada e a saída do comando 'df' é perdida por não existir nenhum processo para tratá-la.

Bom pessoal essa foi uma pequena abordagem de como criar funções e tratar seus argumentos. Como sempre, espero que seja útil para vocês. Um grande abraço e até mais. Fuiii !!!

Comentários

Contato

Nome

E-mail *

Mensagem *