Em shell script, funções podem produzir resultados de duas formas principais: através do exit status (código de retorno) ou através da saída padrão (stdout). Nesta aula, vamos explorar essas abordagens, entender como capturar saídas de funções, o papel das variáveis globais e os padrões para escrever funções limpas e previsíveis.

Dominar o retorno de valores é essencial para criar scripts modulares e reutilizáveis. Vamos ver na prática cada técnica com exemplos.

Exit status vs saída

O exit status é um valor inteiro entre 0 e 255 que uma função (ou comando) retorna para indicar sucesso (0) ou falha (qualquer valor diferente de 0). Esse valor é acessado pela variável especial $? imediatamente após a execução da função. Já a saída é qualquer texto escrito na stdout (usando echo, printf, etc.), que pode ser capturado com substituição de comando.

Muitos iniciantes confundem os dois. O exit status é usado para controle de fluxo (testar sucesso/fracasso), enquanto a saída é usada para transmitir dados. Uma função pode retornar um exit status e também produzir saída, mas é importante ter clareza sobre qual mecanismo usar em cada situação.

#!/bin/bash

minha_funcao() {
    echo "Mensagem na stdout"
    return 42
}

minha_funcao
echo "Exit status: $?"
# A saída será:
# Mensagem na stdout
# Exit status: 42

Capturando saída

Para capturar a saída de uma função (ou comando), usamos a substituição de comando com $(comando) ou com crases `comando`. A saída é armazenada em uma variável. Isso permite que a função retorne strings ou dados complexos.

#!/bin/bash

saudacao() {
    local nome="$1"
    echo "Olá, $nome!"
}

mensagem=$(saudacao "Maria")
echo "$mensagem"  # Olá, Maria!

É possível capturar múltiplas linhas de saída. A variável conterá exatamente o que foi escrito na stdout, incluindo quebras de linha. Para preservar espaços e quebras, sempre coloque a variável entre aspas duplas ao usá-la.

#!/bin/bash

lista() {
    echo "item1"
    echo "item2"
    echo "item3"
}

itens=$(lista)
echo "$itens"
# item1
# item2
# item3

Uma observação importante: a substituição de comando captura apenas a stdout. A stderr não é capturada a menos que seja redirecionada (ex.: $(comando 2>&1)).

Variáveis globais

Outra forma de retornar valores é modificar variáveis globais dentro da função. Embora seja simples, essa prática pode causar efeitos colaterais indesejados, pois a função altera o estado do script fora dela. Por isso, é recomendado usar variáveis locais (local) para evitar poluição do escopo global.

#!/bin/bash

resultado=""  # variável global

calcula() {
    resultado=$(( $1 + $2 ))
}

calcula 10 20
echo "Resultado: $resultado"  # Resultado: 30

O uso de variáveis globais pode ser aceitável em scripts pequenos, mas em projetos maiores é preferível capturar a saída ou usar variáveis de retorno com nomes específicos (ex.: _resultado). Uma boa prática é declarar a variável como local e depois atribuí-la a uma variável global explicitamente, se necessário.

#!/bin/bash

calcula() {
    local soma=$(( $1 + $2 ))
    # opcional: atribuir a uma variável global
    resultado_global="$soma"
}

calcula 5 7
echo "Global: $resultado_global"  # Global: 12

Padrões

Existem padrões consagrados para retornar valores de funções em shell script. Os mais comuns são:

  • Retornar via stdout: Use echo ou printf para enviar o resultado e capture com $(). É a forma mais flexível e recomendada para dados não-numéricos ou complexos.
  • Retornar via variável de referência: Passe o nome de uma variável como argumento e use eval ou printf -v para atribuir. Exemplo: atribuir() { printf -v "$1" "%s" "$2"; }.
  • Retornar via variável global controlada: Use uma variável com prefixo (ex.: return_) para indicar que é um valor de retorno. A função deve documentar que modifica essa variável.
  • Retornar exit status: Use return para códigos de erro/sucesso. Nunca use return para dados não-inteiros.
#!/bin/bash

# Padrão 1: stdout
obter_nome() {
    echo "João"
}
nome=$(obter_nome)
echo "Nome: $nome"

# Padrão 2: variável de referência
set_var() {
    printf -v "$1" "%s" "valor atribuído"
}
minha_var=""
set_var minha_var
echo "minha_var: $minha_var"

# Padrão 3: variável global
return_valor=""
funcao_global() {
    return_valor="resultado global"
}
funcao_global
echo "return_valor: $return_valor"

Escolha o padrão de acordo com a necessidade. Para scripts que serão mantidos por outras pessoas, prefira a captura de stdout, pois é mais explícita e previsível.

Boas práticas

  • Sempre use local para variáveis dentro de funções, a menos que precise explicitamente modificar uma variável global.
  • Evite return para valores não-inteiros; use stdout.
  • Documente se a função modifica variáveis globais ou se espera argumentos especiais.
  • Ao capturar saída, lembre-se de que a substituição de comando remove newlines no final. Use aspas duplas para preservar espaços.
  • Para retornar múltiplos valores, considere imprimir cada valor em uma linha separada e depois ler com read ou array.

Referências

Exercícios

  1. Escreva uma função chamada soma que recebe dois números, imprime a soma na stdout e retorna 0. Capture a saída em uma variável e exiba-a.

    ✓ Resposta:
    #!/bin/bash
    
    soma() {
        echo $(($1 + $2))
        return 0
    }
    
    resultado=$(soma 3 4)
    echo "Soma: $resultado"  # Soma: 7
    
  2. Crie uma função que retorna o maior de dois números usando o exit status: retorne 0 se o primeiro é maior, 1 se o segundo é maior, 2 se são iguais. Teste com exemplos.

    ✓ Resposta:
    #!/bin/bash
    
    maior() {
        if [ $1 -gt $2 ]; then
            return 0
        elif [ $2 -gt $1 ]; then
            return 1
        else
            return 2
        fi
    }
    
    maior 5 3
    echo "Exit: $?"  # 0
    maior 2 8
    echo "Exit: $?"  # 1
    maior 4 4
    echo "Exit: $?"  # 2
    
  3. Escreva uma função que modifica uma variável global chamada global_result com o dobro de um número passado. Em seguida, imprima o valor.

    ✓ Resposta:
    #!/bin/bash
    
    global_result=0
    
    dobro() {
        global_result=$(( $1 * 2 ))
    }
    
    dobro 7
    echo "Global result: $global_result"  # 14
    
  4. Crie uma função que retorna o nome do mês (string) a partir do número (1-12) usando case. Capture a saída e exiba.

    ✓ Resposta:
    #!/bin/bash
    
    mes_por_extenso() {
        case $1 in
            1) echo "Janeiro";;
            2) echo "Fevereiro";;
            3) echo "Março";;
            4) echo "Abril";;
            5) echo "Maio";;
            6) echo "Junho";;
            7) echo "Julho";;
            8) echo "Agosto";;
            9) echo "Setembro";;
            10) echo "Outubro";;
            11) echo "Novembro";;
            12) echo "Dezembro";;
            *) echo "Mês inválido";;
        esac
    }
    
    mes=$(mes_por_extenso 5)
    echo "Mês: $mes"  # Maio
    
  5. Implemente uma função que retorna a soma e o produto de dois números. Use a saída padrão para retornar ambos (um por linha) e depois leia com read em um array.

    ✓ Resposta:
    #!/bin/bash
    
    soma_produto() {
        echo $(( $1 + $2 ))
        echo $(( $1 * $2 ))
    }
    
    IFS=$'\n' read -d '' -ra valores < <(soma_produto 3 4)
    soma="${valores[0]}"
    produto="${valores[1]}"
    echo "Soma: $soma, Produto: $produto"  # Soma: 7, Produto: 12