Validar entradas é uma etapa essencial no desenvolvimento de scripts shell robustos. Sem validação adequada, seu script pode falhar de maneiras imprevisíveis ou causar danos acidentais. Nesta aula, vamos explorar técnicas para verificar argumentos, garantir que valores obrigatórios sejam fornecidos e exibir mensagens de uso informativas.

A validação de entrada não apenas previne erros, mas também melhora a experiência do usuário, fornecendo feedback claro sobre como usar o script corretamente. Vamos começar com os fundamentos e avançar para exemplos práticos.

Verificando argumentos

Em scripts shell, os argumentos passados na linha de comando são acessados através das variáveis posicionais: $1, $2, ..., $9 (e ${10} para o décimo em diante). O número total de argumentos é armazenado em $#. A verificação básica envolve checar se $# é igual ao número esperado ou está dentro de um intervalo.

Por exemplo, para verificar se pelo menos um argumento foi fornecido:

if [ $# -lt 1 ]; then
    echo "Erro: É necessário pelo menos um argumento."
    exit 1
fi

Também é possível verificar se um argumento específico não está vazio:

if [ -z "$1" ]; then
    echo "Erro: O primeiro argumento não pode estar vazio."
    exit 1
fi

Outra técnica comum é usar ! -z para verificar se a string não está vazia ou -n (equivalente a não vazio).

Valores obrigatórios

Muitas vezes, um script exige que certos argumentos sejam fornecidos. Você pode definir variáveis com valores padrão ou exigir que o usuário forneça todos os parâmetros necessários. Uma abordagem comum é usar um loop para processar argumentos nomeados (flags) como -f ou --file, mas aqui focaremos em argumentos posicionais.

Suponha um script que requer um nome de arquivo e um diretório de destino:

if [ $# -ne 2 ]; then
    echo "Erro: Uso: $0 <arquivo> <diretorio>"
    exit 1
fi

arquivo="$1"
diretorio="$2"

# Verifica se o arquivo existe
if [ ! -f "$arquivo" ]; then
    echo "Erro: Arquivo '$arquivo' não encontrado."
    exit 1
fi

# Verifica se o diretório existe
if [ ! -d "$diretorio" ]; then
    echo "Erro: Diretório '$diretorio' não existe."
    exit 1
fi

Além de verificar a presença, é importante validar o tipo de dado. Por exemplo, se um argumento deve ser um número, use uma expressão regular ou case:

if ! [[ "$1" =~ ^[0-9]+$ ]]; then
    echo "Erro: O argumento deve ser um número inteiro."
    exit 1
fi

Mensagens de uso

Uma mensagem de uso (ou "usage") informa o usuário sobre como executar o script corretamente. Ela deve ser exibida quando argumentos inválidos são fornecidos ou quando a opção -h ou --help é usada. Uma boa prática é definir uma função para exibir a mensagem.

usage() {
    echo "Uso: $0 [OPÇÕES] <arquivo>"
    echo ""
    echo "OPÇÕES:"
    echo "  -h, --help     Exibe esta ajuda"
    echo "  -v, --verbose  Modo verboso"
    echo ""
    echo "Exemplo:"
    echo "  $0 -v dados.txt"
    exit 1
}

# Processa opções simples
while [[ $# -gt 0 ]]; do
    case $1 in
        -h|--help)
            usage
            ;;
        -v|--verbose)
            verbose=true
            shift
            ;;
        -*)
            echo "Opção desconhecida: $1"
            usage
            ;;
        *)
            break
            ;;
    esac
done

# Verifica se resta um argumento (o arquivo)
if [ $# -ne 1 ]; then
    usage
fi

arquivo="$1"

A função usage pode ser chamada sempre que houver erro de validação, garantindo consistência.

Exemplos

Vamos consolidar os conceitos com um script completo que valida entradas para copiar um arquivo para um diretório, com opção de backup.

#!/bin/bash

# Função de uso
usage() {
    echo "Uso: $0 [OPÇÕES] <arquivo_origem> <diretorio_destino>"
    echo "Copia um arquivo para um diretório."
    echo ""
    echo "OPÇÕES:"
    echo "  -b, --backup  Cria um backup antes de copiar"
    echo "  -h, --help    Exibe esta ajuda"
    exit 1
}

# Variáveis
backup=false

# Processa opções
while [[ $# -gt 0 ]]; do
    case $1 in
        -b|--backup)
            backup=true
            shift
            ;;
        -h|--help)
            usage
            ;;
        -*)
            echo "Opção inválida: $1"
            usage
            ;;
        *)
            break
            ;;
    esac
done

# Verifica argumentos obrigatórios
if [ $# -ne 2 ]; then
    echo "Erro: Número incorreto de argumentos."
    usage
fi

origem="$1"
destino="$2"

# Valida origem
if [ ! -f "$origem" ]; then
    echo "Erro: Arquivo de origem '$origem' não existe."
    exit 1
fi

# Valida destino
if [ ! -d "$destino" ]; then
    echo "Erro: Diretório de destino '$destino' não existe."
    exit 1
fi

# Se backup for solicitado, faz backup
if [ "$backup" = true ]; then
    timestamp=$(date +%Y%m%d%H%M%S)
    backup_file="${destino}/${origem##*/}.backup.$timestamp"
    cp "$origem" "$backup_file"
    echo "Backup criado em $backup_file"
fi

# Copia o arquivo
cp "$origem" "$destino"
echo "Arquivo copiado com sucesso."

Neste exemplo, validamos a existência do arquivo de origem e do diretório de destino, além de verificar o número correto de argumentos. A opção -b é opcional e tratada antes dos argumentos posicionais.

Boas práticas

Sempre valide entradas o mais cedo possível no script. Use saídas com código de erro diferente de zero para indicar falhas. Forneça mensagens de erro claras e específicas. Considere usar set -e para sair em caso de erro, mas com cuidado para não mascarar validações.

Referências

Exercícios

  1. Crie um script que aceite exatamente dois argumentos: um nome de arquivo e uma string. O script deve verificar se o arquivo existe e se a string não está vazia. Em caso de erro, exiba uma mensagem de uso.

    ✓ Resposta:
    #!/bin/bash
    if [ $# -ne 2 ]; then
        echo "Uso: $0 <arquivo> <string>"
        exit 1
    fi
    
    if [ ! -f "$1" ]; then
        echo "Erro: Arquivo '$1' não encontrado."
        exit 1
    fi
    
    if [ -z "$2" ]; then
        echo "Erro: A string não pode estar vazia."
        exit 1
    fi
    
    echo "Sucesso: Arquivo '$1' existe e string '$2' fornecida."
  2. Escreva um script que aceite um número inteiro como argumento. Valide se o argumento é um número inteiro positivo. Caso contrário, exiba uma mensagem de erro e saia.

    ✓ Resposta:
    #!/bin/bash
    if [ $# -ne 1 ]; then
        echo "Uso: $0 <número>"
        exit 1
    fi
    
    if ! [[ "$1" =~ ^[0-9]+$ ]]; then
        echo "Erro: '$1' não é um número inteiro positivo."
        exit 1
    fi
    
    echo "Número válido: $1"
  3. Modifique o script do exercício 1 para aceitar uma opção -v (verbose) que, quando ativada, exiba mensagens detalhadas durante a validação.

    ✓ Resposta:
    #!/bin/bash
    verbose=false
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            -v)
                verbose=true
                shift
                ;;
            -*)
                echo "Opção inválida: $1"
                exit 1
                ;;
            *)
                break
                ;;
        esac
    done
    
    if [ $# -ne 2 ]; then
        echo "Uso: $0 [-v] <arquivo> <string>"
        exit 1
    fi
    
    if [ ! -f "$1" ]; then
        echo "Erro: Arquivo '$1' não encontrado."
        exit 1
    fi
    
    if [ -z "$2" ]; then
        echo "Erro: A string não pode estar vazia."
        exit 1
    fi
    
    if [ "$verbose" = true ]; then
        echo "Arquivo '$1' existe."
        echo "String '$2' não vazia."
    fi
    
    echo "Validação concluída com sucesso."
  4. Crie um script que aceite uma lista de números separados por espaço. Valide se cada argumento é um número inteiro. Se algum não for, exiba uma mensagem de erro e pare.

    ✓ Resposta:
    #!/bin/bash
    for arg in "$@"; do
        if ! [[ "$arg" =~ ^-?[0-9]+$ ]]; then
            echo "Erro: '$arg' não é um número inteiro."
            exit 1
        fi
    done
    echo "Todos os argumentos são números inteiros válidos."
  5. Desenvolva um script que simule um comando de cópia com validação: aceite dois argumentos (origem e destino). Verifique se a origem é um arquivo regular e se o destino é um diretório. Se o destino não existir, crie-o (com permissão padrão). Se a origem não existir, exiba erro.

    ✓ Resposta:
    #!/bin/bash
    if [ $# -ne 2 ]; then
        echo "Uso: $0 <origem> <destino>"
        exit 1
    fi
    
    origem="$1"
    destino="$2"
    
    if [ ! -f "$origem" ]; then
        echo "Erro: Origem '$origem' não é um arquivo regular."
        exit 1
    fi
    
    if [ ! -d "$destino" ]; then
        mkdir -p "$destino" || { echo "Erro ao criar diretório '$destino'."; exit 1; }
        echo "Diretório '$destino' criado."
    fi
    
    cp "$origem" "$destino"
    echo "Arquivo copiado para '$destino'."