Valores de Retorno de Funções Bash

ShellBeginner
Pratique Agora

Introdução

Este tutorial abrangente explora os valores de retorno de funções Bash, fornecendo conhecimento e técnicas essenciais para trabalhar eficazmente com funções em scripts Bash. Seja você iniciante em shell scripting ou buscando aprimorar suas habilidades, este guia oferece uma sólida compreensão de como definir, chamar e lidar com valores de retorno de funções. Essas habilidades permitem que você escreva scripts shell mais robustos e flexíveis para diversas tarefas de automação.

Criando Sua Primeira Função Bash

Funções Bash são blocos de código reutilizáveis que ajudam a organizar seus scripts de forma mais eficiente. Antes de mergulharmos nos valores de retorno, vamos primeiro entender como criar e chamar uma função.

Criando uma Função Básica

Vamos criar nossa primeira função Bash. Abra uma janela de terminal e digite:

cd ~/project
mkdir -p bash_functions
cd bash_functions

create directory

Agora, vamos criar uma função simples em um novo script. Crie um arquivo chamado first_function.sh usando o nano:

touch first_function.sh

Adicione o seguinte conteúdo ao arquivo:

#!/bin/bash

## Define a simple greeting function
say_hello() {
  echo "Hello, world!"
}

## Call the function
say_hello

## Define a function that accepts arguments
greet_person() {
  echo "Hello, $1!"
}

## Call the function with an argument
greet_person "Alice"
greet_person "Bob"

Torne o script executável:

chmod +x first_function.sh

Agora execute o script:

./first_function.sh

Você deve ver esta saída:

Hello, world!
Hello, Alice!
Hello, Bob!

Sintaxe da Função Explicada

No Bash, existem duas maneiras de definir funções:

  1. Usando a sintaxe padrão:
function_name() {
  ## Commands
}
  1. Usando a palavra-chave function:
function function_name {
  ## Commands
}

Ambos os estilos funcionam da mesma maneira, mas o primeiro é mais comumente usado e compatível com POSIX.

Acessando Argumentos de Função

Dentro de uma função, você pode acessar argumentos passados para a função usando parâmetros posicionais:

  • $1, $2, $3, etc. referem-se ao primeiro, segundo, terceiro argumento, e assim por diante
  • $0 refere-se ao nome da função ou nome do script
  • $# fornece o número de argumentos
  • $@ contém todos os argumentos como strings separadas
  • $* contém todos os argumentos como uma única string

Vamos criar um novo arquivo para praticar com argumentos de função:

touch function_args.sh

Adicione o seguinte conteúdo:

#!/bin/bash

show_args() {
  echo "Function name: $0"
  echo "First argument: $1"
  echo "Second argument: $2"
  echo "Number of arguments: $#"
  echo "All arguments: $@"
}

echo "Calling function with three arguments:"
show_args apple banana cherry

Salve, torne executável e execute o script:

chmod +x function_args.sh
./function_args.sh

Você deve ver uma saída semelhante a:

Calling function with three arguments:
Function name: ./function_args.sh
First argument: apple
Second argument: banana
Number of arguments: 3
All arguments: apple banana cherry

Esta compreensão básica das definições de função e do tratamento de argumentos fornece a base para trabalhar com valores de retorno de função nos próximos passos.

Entendendo os Códigos de Retorno de Funções

Todo comando no Bash, incluindo funções, produz um código de retorno (também chamado de status de saída). Este valor numérico indica se o comando foi bem-sucedido ou falhou. Este código de retorno é fundamental para o tratamento de erros em scripts Bash.

Códigos de Retorno Básicos

No Bash:

  • Um código de retorno de 0 indica sucesso
  • Qualquer valor diferente de zero (1-255) indica um erro ou condição anormal

Vamos criar um script para demonstrar isso:

cd ~/project/bash_functions
touch return_codes.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Function that always succeeds
succeed() {
  echo "This function succeeds"
  return 0
}

## Function that always fails
fail() {
  echo "This function fails"
  return 1
}

## Call the functions and check their return codes
succeed
echo "Return code of succeed: $?"

fail
echo "Return code of fail: $?"

Salve, torne executável e execute o script:

chmod +x return_codes.sh
./return_codes.sh

Você deve ver:

This function succeeds
Return code of succeed: 0
This function fails
Return code of fail: 1

Capturando Códigos de Retorno

A variável especial $? contém o código de retorno do comando ou função executado mais recentemente. Este valor é importante para execução condicional e tratamento de erros.

Vamos criar outro script para praticar o uso de códigos de retorno para lógica condicional:

touch check_file.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Function to check if a file exists
file_exists() {
  local filename="$1"

  if [ -f "$filename" ]; then
    echo "File $filename exists"
    return 0
  else
    echo "File $filename does not exist"
    return 1
  fi
}

## Test the function with files that exist and don't exist
file_exists "return_codes.sh"
if [ $? -eq 0 ]; then
  echo "Great! The file was found."
else
  echo "Too bad. The file was not found."
fi

echo ""

file_exists "non_existent_file.txt"
if [ $? -eq 0 ]; then
  echo "Great! The file was found."
else
  echo "Too bad. The file was not found."
fi

Salve, torne executável e execute o script:

chmod +x check_file.sh
./check_file.sh

Você deve ver uma saída semelhante a:

File return_codes.sh exists
Great! The file was found.

File non_existent_file.txt does not exist
Too bad. The file was not found.

Usando Códigos de Retorno em Condicionais

O código de retorno pode ser usado diretamente em expressões condicionais usando os operadores && (AND) e || (OR):

touch conditional_return.sh

Adicione o seguinte conteúdo:

#!/bin/bash

check_number() {
  local num=$1

  if [ $num -gt 10 ]; then
    return 0 ## Success if number is greater than 10
  else
    return 1 ## Failure if number is not greater than 10
  fi
}

## Using conditional operators with return codes
check_number 15 && echo "Number is greater than 10"
check_number 5 || echo "Number is not greater than 10"

## This line runs only if check_number succeeds
check_number 20 && {
  echo "Number is greater than 10"
  echo "Performing additional operations..."
}

## This line runs only if check_number fails
check_number 3 || {
  echo "Number is not greater than 10"
  echo "Taking alternative actions..."
}

Salve, torne executável e execute o script:

chmod +x conditional_return.sh
./conditional_return.sh

A saída deve ser:

Number is greater than 10
Number is not greater than 10
Number is greater than 10
Performing additional operations...
Number is not greater than 10
Taking alternative actions...

Entender como os códigos de retorno funcionam é essencial para escrever scripts robustos que podem lidar com erros corretamente e tomar decisões com base no sucesso ou falha das operações.

Trabalhando com Valores de Retorno Personalizados

Embora os códigos de retorno sejam úteis para indicar sucesso ou falha, eles são limitados a números entre 0 e 255. Para retornar dados reais de funções, precisamos usar outras técnicas.

Método 1: Usando Echo para Retornar Valores

A maneira mais comum de retornar valores reais de funções é usando echo ou outros comandos de saída e, em seguida, capturando essa saída.

Vamos criar um script para demonstrar esta técnica:

cd ~/project/bash_functions
touch return_values.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Function that returns a value using echo
get_username() {
  echo "labex"
}

## Function that returns a calculated value
add_numbers() {
  local sum=$(($1 + $2))
  echo $sum
}

## Capture the returned values
username=$(get_username)
echo "The username is: $username"

result=$(add_numbers 5 7)
echo "The sum of 5 and 7 is: $result"

## You can also use the returned value directly
echo "Calculating again: $(add_numbers 10 20)"

Salve, torne executável e execute o script:

chmod +x return_values.sh
./return_values.sh

Você deve ver:

The username is: labex
The sum of 5 and 7 is: 12
Calculating again: 30

Método 2: Usando Variáveis Globais

Outra abordagem é modificar variáveis globais dentro da função:

touch global_return.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Declare global variables
FULL_NAME=""
USER_AGE=0

## Function that sets global variables
set_user_info() {
  FULL_NAME="$1 $2"
  USER_AGE=$3

  ## Return success
  return 0
}

## Call the function
set_user_info "John" "Doe" 30

## Use the global variables that were set by the function
echo "Full name: $FULL_NAME"
echo "Age: $USER_AGE"

Salve, torne executável e execute o script:

chmod +x global_return.sh
./global_return.sh

Saída:

Full name: John Doe
Age: 30

Método 3: Retornando Múltiplos Valores

Vamos explorar como retornar múltiplos valores de uma função:

touch multiple_returns.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Function that returns multiple values separated by a delimiter
get_system_info() {
  local hostname=$(hostname)
  local kernel=$(uname -r)
  local uptime=$(uptime -p)

  ## Return multiple values separated by semicolons
  echo "$hostname;$kernel;$uptime"
}

## Capture the output and split it
system_info=$(get_system_info)

## Split the values using IFS (Internal Field Separator)
IFS=';' read -r host kernel up <<< "$system_info"

## Display the values
echo "Hostname: $host"
echo "Kernel version: $kernel"
echo "Uptime: $up"

## Alternative method using an array
get_user_details() {
  local details=("John Doe" "john@example.com" "Developer")
  printf "%s\n" "${details[@]}"
}

## Capture the output into an array
mapfile -t user_details < <(get_user_details)

echo ""
echo "User information:"
echo "Name: ${user_details[0]}"
echo "Email: ${user_details[1]}"
echo "Role: ${user_details[2]}"

Salve, torne executável e execute o script:

chmod +x multiple_returns.sh
./multiple_returns.sh

A saída deve mostrar as informações do seu sistema, seguidas pelos detalhes do usuário:

Hostname: ubuntu
Kernel version: 5.15.0-1033-azure
Uptime: up 2 hours, 15 minutes

User information:
Name: John Doe
Email: john@example.com
Role: Developer

O hostname, a versão do kernel e o tempo de atividade reais variarão dependendo do seu sistema.

Esses métodos demonstram diferentes maneiras de retornar valores de funções além de simples códigos de retorno. Cada abordagem tem suas vantagens, dependendo de suas necessidades específicas.

Uso Prático de Funções em um Script

Agora que entendemos como definir funções e lidar com seus valores de retorno, vamos construir um script prático que demonstra esses conceitos em ação. Criaremos um utilitário de gerenciamento de arquivos que usa funções com diferentes métodos de retorno.

Criando o Utilitário de Gerenciamento de Arquivos

Vamos criar um script abrangente que realiza várias operações de arquivo:

cd ~/project/bash_functions
touch file_manager.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Function to check if a file exists
## Returns 0 if file exists, 1 if it doesn't
file_exists() {
  if [ -f "$1" ]; then
    return 0
  else
    return 1
  fi
}

## Function to get file size in bytes
## Returns the size via echo
get_file_size() {
  if file_exists "$1"; then
    ## Use stat to get file size in bytes
    local size=$(stat -c %s "$1")
    echo "$size"
  else
    echo "0"
  fi
}

## Function to count lines in a file
## Returns line count via echo
count_lines() {
  if file_exists "$1"; then
    local lines=$(wc -l < "$1")
    echo "$lines"
  else
    echo "0"
  fi
}

## Function to get file information
## Returns multiple values using a delimiter
get_file_info() {
  local filename="$1"

  if file_exists "$filename"; then
    local size=$(get_file_size "$filename")
    local lines=$(count_lines "$filename")
    local modified=$(stat -c %y "$filename")
    local permissions=$(stat -c %A "$filename")

    ## Return all info with semicolons as delimiters
    echo "$size;$lines;$modified;$permissions"
  else
    echo "0;0;N/A;N/A"
  fi
}

## Function to create a test file
create_test_file() {
  local filename="$1"
  local lines="$2"

  ## Create or overwrite the file
  > "$filename"

  ## Add the specified number of lines
  for ((i = 1; i <= lines; i++)); do
    echo "This is line $i of the test file." >> "$filename"
  done

  ## Return success if file was created
  if file_exists "$filename"; then
    return 0
  else
    return 1
  fi
}

## Main script execution starts here
echo "File Management Utility"
echo "----------------------"

## Create a test file
TEST_FILE="sample.txt"
echo "Creating test file with 10 lines..."
if create_test_file "$TEST_FILE" 10; then
  echo "File created successfully."
else
  echo "Failed to create file."
  exit 1
fi

## Check if file exists
echo ""
echo "Checking if file exists..."
if file_exists "$TEST_FILE"; then
  echo "File '$TEST_FILE' exists."
else
  echo "File '$TEST_FILE' does not exist."
fi

## Get file size
echo ""
echo "Getting file size..."
size=$(get_file_size "$TEST_FILE")
echo "File size: $size bytes"

## Count lines
echo ""
echo "Counting lines in file..."
lines=$(count_lines "$TEST_FILE")
echo "Line count: $lines"

## Get all file information
echo ""
echo "Getting complete file information..."
file_info=$(get_file_info "$TEST_FILE")

## Split the returned values
IFS=';' read -r size lines modified permissions <<< "$file_info"

echo "File: $TEST_FILE"
echo "Size: $size bytes"
echo "Lines: $lines"
echo "Last modified: $modified"
echo "Permissions: $permissions"

echo ""
echo "File content preview:"
head -n 3 "$TEST_FILE"
echo "..."

Salve, torne executável e execute o script:

chmod +x file_manager.sh
./file_manager.sh

Você deve ver uma saída semelhante a:

File Management Utility
----------------------
Creating test file with 10 lines...
File created successfully.

Checking if file exists...
File 'sample.txt' exists.

Getting file size...
File size: 300 bytes

Counting lines in file...
Line count: 10

Getting complete file information...
File: sample.txt
Size: 300 bytes
Lines: 10
Last modified: 2023-11-04 12:34:56.789012345 +0000
Permissions: -rwxrwxr-x

File content preview:
This is line 1 of the test file.
This is line 2 of the test file.
This is line 3 of the test file.
...

Os valores exatos para o tamanho do arquivo, tempo de modificação e permissões variarão.

Detalhando o Script

Nosso utilitário de gerenciamento de arquivos demonstra vários conceitos-chave:

  1. Códigos de retorno - As funções file_exists() e create_test_file() retornam 0 para sucesso e 1 para falha
  2. Retornando valores com echo - As funções get_file_size() e count_lines() retornam valores numéricos via echo
  3. Retornando múltiplos valores - A função get_file_info() retorna múltiplos valores usando um delimitador
  4. Composição de funções - Algumas funções chamam outras funções, demonstrando como construir funcionalidades complexas
  5. Execução condicional - O script usa instruções if com códigos de retorno para controlar o fluxo do programa

Este exemplo prático mostra como combinar várias técnicas de função para criar um utilitário útil. O script demonstra o tratamento adequado de erros, a composição de funções e diferentes métodos para retornar valores.

Tratamento de Erros e Melhores Práticas para Funções

Para nossa seção final, vamos explorar técnicas de tratamento de erros e as melhores práticas para funções Bash. O tratamento adequado de erros é crucial para criar scripts robustos e de fácil manutenção.

Criando um Script com Tratamento de Erros

Vamos criar um novo script que demonstra o tratamento robusto de erros:

cd ~/project/bash_functions
touch error_handling.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Enable error handling
set -e ## Exit immediately if a command exits with non-zero status

## Define a function to log messages
log_message() {
  local level="$1"
  local message="$2"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message"
}

## Function to validate a number is positive
validate_positive() {
  local num="$1"
  local name="$2"

  ## Check if the argument is a number
  if ! [[ "$num" =~ ^[0-9]+$ ]]; then
    log_message "ERROR" "$name must be a number"
    return 1
  fi

  ## Check if the number is positive
  if [ "$num" -le 0 ]; then
    log_message "ERROR" "$name must be positive"
    return 2
  fi

  return 0
}

## Function that divides two numbers
divide() {
  local numerator="$1"
  local denominator="$2"

  ## Validate inputs
  validate_positive "$numerator" "Numerator" || return $?
  validate_positive "$denominator" "Denominator" || return $?

  ## Check for division by zero
  if [ "$denominator" -eq 0 ]; then
    log_message "ERROR" "Division by zero is not allowed"
    return 3
  fi

  ## Perform division
  local result=$(echo "scale=2; $numerator / $denominator" | bc)
  echo "$result"
  return 0
}

## Function to safely get user input
get_number() {
  local prompt="$1"
  local input

  while true; do
    read -p "$prompt: " input

    if validate_positive "$input" "Input"; then
      echo "$input"
      return 0
    else
      log_message "WARN" "Invalid input. Please try again."
    fi
  done
}

## Disable automatic exit on error for the main script
set +e

## Main script logic
log_message "INFO" "Starting division calculator"

## Test with valid values
result=$(divide 10 2)
exit_code=$?

if [ $exit_code -eq 0 ]; then
  log_message "INFO" "10 / 2 = $result"
else
  log_message "ERROR" "Division failed with code $exit_code"
fi

## Test with invalid values
echo ""
log_message "INFO" "Testing with invalid values"
divide 0 5
log_message "INFO" "Exit code: $?"

divide 10 0
log_message "INFO" "Exit code: $?"

divide abc 5
log_message "INFO" "Exit code: $?"

## Interactive mode
echo ""
log_message "INFO" "Interactive mode"
echo "Let's perform a division. Enter positive numbers."

## Get user input safely
num1=$(get_number "Enter first number")
num2=$(get_number "Enter second number")

## Perform division
result=$(divide "$num1" "$num2")
exit_code=$?

if [ $exit_code -eq 0 ]; then
  log_message "INFO" "$num1 / $num2 = $result"
else
  log_message "ERROR" "Division failed with code $exit_code"
fi

log_message "INFO" "Calculator finished"

Salve, torne executável e execute o script:

chmod +x error_handling.sh
./error_handling.sh

Você verá uma saída semelhante à seguinte e será solicitado a inserir números:

[2023-11-04 13:45:23] [INFO] Starting division calculator
[2023-11-04 13:45:23] [INFO] 10 / 2 = 5.00

[2023-11-04 13:45:23] [INFO] Testing with invalid values
[2023-11-04 13:45:23] [ERROR] Numerator must be positive
[2023-11-04 13:45:23] [INFO] Exit code: 2
[2023-11-04 13:45:23] [ERROR] Division by zero is not allowed
[2023-11-04 13:45:23] [INFO] Exit code: 3
[2023-11-04 13:45:23] [ERROR] Numerator must be a number
[2023-11-04 13:45:23] [INFO] Exit code: 1

[2023-11-04 13:45:23] [INFO] Interactive mode
Let's perform a division. Enter positive numbers.
Enter first number:

Insira um número, por exemplo, 20. Em seguida, você será solicitado a inserir o segundo número:

Enter second number:

Insira outro número, por exemplo, 4, e você deverá ver:

[2023-11-04 13:45:30] [INFO] 20 / 4 = 5.00
[2023-11-04 13:45:30] [INFO] Calculator finished

Melhores Práticas para Funções Bash

Com base em nossos exemplos, aqui estão algumas melhores práticas para trabalhar com funções Bash:

  1. Adicione Comentários Descritivos - Documente o que cada função faz, seus parâmetros e valores de retorno
  2. Use Nomes de Funções Significativos - Escolha nomes que indiquem claramente o propósito da função
  3. Valide Parâmetros de Entrada - Verifique as entradas para evitar erros
  4. Use Variáveis Locais - Evite colisões de nomes de variáveis com a palavra-chave local
  5. Retorne Códigos de Saída Apropriados - Use códigos de retorno convencionais (0 para sucesso, diferente de zero para erros)
  6. Implemente o Tratamento Adequado de Erros - Registre erros e trate-os de forma adequada
  7. Mantenha as Funções Focadas - Cada função deve fazer uma coisa bem
  8. Use a Composição de Funções - Construa funcionalidades complexas combinando funções mais simples
  9. Documente os Valores de Retorno - Documente claramente como os valores são retornados (via echo, código de retorno, etc.)
  10. Teste Casos Extremos - Certifique-se de que as funções lidam com entradas incomuns corretamente

Seguindo essas práticas, você pode criar funções Bash mais confiáveis, de fácil manutenção e reutilizáveis.

Criando uma Biblioteca de Funções

Para o exercício final, vamos criar uma biblioteca de funções reutilizável:

touch math_functions.lib

Adicione o seguinte conteúdo:

#!/bin/bash
## math_functions.lib - A library of mathematical functions

## Add two numbers
add() {
  echo $(($1 + $2))
}

## Subtract second number from first
subtract() {
  echo $(($1 - $2))
}

## Multiply two numbers
multiply() {
  echo $(($1 * $2))
}

## Divide first number by second (with decimal precision)
divide() {
  if [ "$2" -eq 0 ]; then
    return 1
  fi
  echo "scale=2; $1 / $2" | bc
  return 0
}

## Calculate power: first number raised to second number
power() {
  echo $(($1 ** $2))
}

## Check if a number is even
is_even() {
  if (($1 % 2 == 0)); then
    return 0
  else
    return 1
  fi
}

## Check if a number is odd
is_odd() {
  if is_even "$1"; then
    return 1
  else
    return 0
  fi
}

Agora, crie um script que usa esta biblioteca:

touch use_library.sh

Adicione o seguinte conteúdo:

#!/bin/bash

## Source the math functions library
source math_functions.lib

## Display a header
echo "Math Functions Demo"
echo "------------------"

## Test the functions
echo "Addition: 5 + 3 = $(add 5 3)"
echo "Subtraction: 10 - 4 = $(subtract 10 4)"
echo "Multiplication: 6 * 7 = $(multiply 6 7)"

## Test division with error handling
div_result=$(divide 20 5)
if [ $? -eq 0 ]; then
  echo "Division: 20 / 5 = $div_result"
else
  echo "Division error: Cannot divide by zero"
fi

## Test division by zero
div_result=$(divide 20 0)
if [ $? -eq 0 ]; then
  echo "Division: 20 / 0 = $div_result"
else
  echo "Division error: Cannot divide by zero"
fi

echo "Power: 2 ^ 8 = $(power 2 8)"

## Test the even/odd functions
echo ""
echo "Number properties:"
for num in 1 2 3 4 5; do
  echo -n "Number $num is "

  if is_even $num; then
    echo "even"
  else
    echo "odd"
  fi
done

Salve, torne executável e execute o script:

chmod +x use_library.sh
./use_library.sh

Você deve ver:

Math Functions Demo
------------------
Addition: 5 + 3 = 8
Subtraction: 10 - 4 = 6
Multiplication: 6 * 7 = 42
Division: 20 / 5 = 4.00
Division error: Cannot divide by zero
Power: 2 ^ 8 = 256

Number properties:
Number 1 is odd
Number 2 is even
Number 3 is odd
Number 4 is even
Number 5 is odd

Essa abordagem de biblioteca demonstra como você pode criar coleções de funções reutilizáveis que podem ser importadas em vários scripts, promovendo a reutilização e a capacidade de manutenção do código.

Resumo

Neste tutorial, você aprendeu os conceitos essenciais de valores de retorno de funções Bash. Começando com a criação básica de funções e o tratamento de argumentos, você progrediu para entender os códigos de retorno e como eles indicam sucesso ou falha. Você explorou vários métodos para retornar dados reais de funções, incluindo o uso de echo, variáveis globais e delimitadores para múltiplos valores.

Por meio de exemplos práticos, você implementou um utilitário de gerenciamento de arquivos que demonstrou a composição adequada de funções e o tratamento de erros. Por fim, você aprendeu as melhores práticas para criar funções robustas e reutilizáveis e como organizá-las em bibliotecas.

As habilidades adquiridas neste tutorial fornecem uma base sólida para escrever scripts Bash mais sofisticados com tratamento de erros adequado, modularidade e reutilização. Essas técnicas permitem que você crie scripts de shell de fácil manutenção para várias tarefas de automação, melhorando sua produtividade geral como administrador de sistema ou desenvolvedor.