Validar entradas é uma prática essencial em scripts Bash para evitar comportamentos inesperados e garantir que o script funcione corretamente com os dados fornecidos pelo usuário. Nesta aula, abordaremos como verificar argumentos da linha de comando, garantir que valores obrigatórios sejam fornecidos e exibir mensagens de uso claras. Essas técnicas são fundamentais para criar scripts profissionais e confiáveis.

Imagine um script que precisa de um nome de arquivo como entrada. Se o usuário esquecer de passá-lo, o script pode falhar de forma confusa. Com validação adequada, podemos fornecer uma mensagem amigável e evitar erros. Vamos explorar isso em detalhes.

Verificando argumentos

Em Bash, os argumentos passados para o script são acessados por variáveis posicionais: $1, $2, etc. O número total de argumentos é armazenado em $#. Para verificar se um argumento específico foi fornecido, podemos testar se a variável correspondente está vazia ou se o número de argumentos é suficiente.

Por exemplo, se esperamos um argumento obrigatório, podemos verificar se $# é igual a zero ou menor que o esperado. Uma abordagem comum é usar uma estrutura condicional if com o operador -z para strings vazias ou comparar $# com um valor numérico.

#!/bin/bash

# Verifica se pelo menos um argumento foi passado
if [ $# -eq 0 ]; then
    echo "Nenhum argumento fornecido."
    exit 1
fi

echo "Primeiro argumento: $1"

Além disso, podemos verificar se um argumento específico está vazio usando -z:

#!/bin/bash

if [ -z "$1" ]; then
    echo "Erro: Argumento obrigatório não fornecido."
    exit 1
fi

echo "Argumento: $1"

Valores obrigatórios

Muitas vezes, um script exige que certos argumentos sejam fornecidos. Podemos validar isso verificando cada argumento individualmente ou usando um loop para processar opções. Para valores obrigatórios, é comum exibir uma mensagem de erro e sair do script se a condição não for atendida.

Além de verificar a existência, podemos validar o formato do valor, como se é um número, um arquivo existente, etc. Por exemplo, para garantir que o argumento seja um arquivo legível:

#!/bin/bash

if [ ! -f "$1" ]; then
    echo "Erro: O arquivo '$1' não existe ou não é um arquivo regular."
    exit 1
fi

echo "Arquivo válido: $1"

Para múltiplos argumentos obrigatórios, podemos usar uma função de validação ou simplesmente verificar um a um:

#!/bin/bash

if [ $# -lt 2 ]; then
    echo "Erro: São necessários dois argumentos."
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "Erro: '$1' não é um arquivo."
    exit 1
fi

if [ -z "$2" ]; then
    echo "Erro: Segundo argumento não pode ser vazio."
    exit 1
fi

echo "Argumentos válidos: $1 e $2"

Mensagens de uso

Uma mensagem de uso (ou ajuda) instrui o usuário sobre como utilizar o script corretamente. Geralmente é exibida quando argumentos inválidos são fornecidos ou quando o usuário passa -h ou --help. A mensagem deve listar os argumentos esperados, opções e exemplos.

É uma boa prática definir uma função usage() que imprime a mensagem e sai do script. Essa função pode ser chamada em diferentes pontos. Exemplo:

#!/bin/bash

usage() {
    echo "Uso: $0 [-o opcao] "
    echo "  -o opcao   Opção opcional (padrão: default)"
    echo "    Caminho para o arquivo de entrada (obrigatório)"
    exit 1
}

# Verifica se -h foi passado
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    usage
fi

# Verifica argumento obrigatório
if [ $# -lt 1 ]; then
    usage
fi

echo "Processando arquivo: $1"

Mensagens de uso devem ser claras e consistentes. Inclua sempre um resumo dos argumentos e opções, além de exemplos práticos. Isso melhora a experiência do usuário e reduz erros.

Exemplos

Vamos combinar tudo em um script completo que valida argumentos, exige um arquivo e uma string, e fornece uma mensagem de uso.

#!/bin/bash

usage() {
    echo "Uso: $0 [-m mensagem] "
    echo "  -m mensagem   Mensagem a ser gravada no arquivo (opcional)"
    echo "       Arquivo de destino (obrigatório)"
    echo "Exemplo: $0 -m 'Hello' /tmp/saida.txt"
    exit 1
}

# Inicializa variáveis
mensagem=""

# Processa opções com getopts
while getopts ":m:h" opt; do
    case $opt in
        m)
            mensagem="$OPTARG"
            ;;
        h)
            usage
            ;;
        \?)
            echo "Opção inválida: -$OPTARG" >&2
            usage
            ;;
        :)
            echo "Opção -$OPTARG requer um argumento." >&2
            usage
            ;;
    esac
done

# Remove opções processadas
shift $((OPTIND-1))

# Verifica argumento posicional obrigatório
if [ $# -lt 1 ]; then
    echo "Erro: Arquivo não especificado." >&2
    usage
fi

arquivo="$1"

# Valida se o arquivo pode ser criado (diretório existe?)
dir=$(dirname "$arquivo")
if [ ! -d "$dir" ] && [ "$dir" != "." ]; then
    echo "Erro: Diretório '$dir' não existe." >&2
    exit 1
fi

# Escreve a mensagem no arquivo
if [ -n "$mensagem" ]; then
    echo "$mensagem" > "$arquivo"
    echo "Mensagem gravada em $arquivo"
else
    echo "Nenhuma mensagem fornecida. Arquivo vazio criado: $arquivo"
    touch "$arquivo"
fi

Teste o script com diferentes combinações: sem argumentos, com -h, com -m, etc. Observe como as validações funcionam e como a mensagem de uso é exibida em caso de erro.

Boas práticas e observações finais

Sempre valide entradas antes de usá-las. Isso evita erros inesperados e torna o script mais seguro. Use exit 1 para indicar falha. Considere usar getopts para opções, pois ele lida com muitos casos automaticamente. Documente seu script com uma mensagem de uso clara e mantenha consistência entre scripts.

Referências

Exercícios

  1. Crie um script que exija exatamente dois argumentos: um nome de arquivo e uma string. Se o número de argumentos for diferente, exiba uma mensagem de uso.

    ✓ Resposta:
    #!/bin/bash
    
    if [ $# -ne 2 ]; then
        echo "Uso: $0 <arquivo> <string>"
        exit 1
    fi
    
    echo "Arquivo: $1, String: $2"
  2. Modifique o script anterior para verificar se o primeiro argumento é um arquivo regular existente. Se não for, exiba um erro.

    ✓ Resposta:
    #!/bin/bash
    
    if [ $# -ne 2 ]; then
        echo "Uso: $0 <arquivo> <string>"
        exit 1
    fi
    
    if [ ! -f "$1" ]; then
        echo "Erro: '$1' não é um arquivo regular."
        exit 1
    fi
    
    echo "Arquivo: $1, String: $2"
  3. Escreva um script que aceite uma opção -n (número) e um argumento posicional (nome). O número deve ser obrigatório (opção -n) e o nome opcional. Se -n não for fornecido, exiba uso.

    ✓ Resposta:
    #!/bin/bash
    
    usage() {
        echo "Uso: $0 -n <número> [nome]"
        exit 1
    }
    
    num=""
    
    while getopts ":n:" opt; do
        case $opt in
            n)
                num="$OPTARG"
                ;;
            \?)
                usage
                ;;
            :)
                echo "Opção -n requer um argumento."
                usage
                ;;
        esac
    done
    
    shift $((OPTIND-1))
    
    if [ -z "$num" ]; then
        usage
    fi
    
    nome="${1:-"sem nome"}"
    echo "Número: $num, Nome: $nome"
  4. Crie um script que valide se o primeiro argumento é um diretório existente. Se for, liste seu conteúdo. Caso contrário, exiba erro.

    ✓ Resposta:
    #!/bin/bash
    
    if [ $# -lt 1 ]; then
        echo "Uso: $0 <diretório>"
        exit 1
    fi
    
    if [ ! -d "$1" ]; then
        echo "Erro: '$1' não é um diretório."
        exit 1
    fi
    
    ls -l "$1"
  5. Implemente um script que aceite dois argumentos: um arquivo de entrada e um arquivo de saída. Valide que o arquivo de entrada existe e que o diretório do arquivo de saída existe. Se alguma validação falhar, exiba mensagem apropriada.

    ✓ Resposta:
    #!/bin/bash
    
    if [ $# -ne 2 ]; then
        echo "Uso: $0 <entrada> <saída>"
        exit 1
    fi
    
    if [ ! -f "$1" ]; then
        echo "Erro: Arquivo de entrada '$1' não existe."
        exit 1
    fi
    
    saida_dir=$(dirname "$2")
    if [ ! -d "$saida_dir" ]; then
        echo "Erro: Diretório de saída '$saida_dir' não existe."
        exit 1
    fi
    
    cp "$1" "$2"
    echo "Arquivo copiado para $2"