Fundamentos de scripts de shell

ShellBeginner
Pratique Agora

Introdução

Um script de shell armazena comandos em um arquivo para que uma tarefa possa ser repetida de forma consistente. Scripts confiáveis para iniciantes deixam explícito o interpretador, usam aspas nas expansões de variáveis, validam a entrada antes de agir e retornam um status de saída que outros comandos conseguem interpretar.

Neste laboratório, você criará pequenos scripts Bash de forma incremental. Você executará scripts por meio do Bash e como arquivos executáveis, usará variáveis e argumentos posicionais, lerá entradas interativas, tomará decisões com if e testes de arquivos, escolherá códigos de saída, percorrerá arquivos com for e combinará esses conceitos em um simples resumidor de logs.

Crie e execute seu primeiro script

Nesta etapa, você criará um script Bash, executá-lo-á por meio de um interpretador e o tornará diretamente executável.

Acesse o diretório de trabalho do laboratório:

cd /home/labex/project/shell-lab

Crie hello.sh com um here-document. A primeira linha é um shebang que seleciona o Bash quando o arquivo é executado diretamente:

cat > hello.sh <<'EOF'
#!/bin/bash

echo "Hello from a Bash script"
pwd
EOF

Inspecione o arquivo antes de executá-lo:

cat hello.sh

Passe o arquivo explicitamente ao Bash. Isso funciona mesmo antes de adicionar a permissão de execução:

bash hello.sh

Adicione a permissão de execução e verifique o modo do arquivo:

chmod u+x hello.sh
ls -l hello.sh

Execute o arquivo a partir do diretório atual. ./ fornece um caminho; normalmente, o shell não pesquisa automaticamente no diretório atual:

./hello.sh

Use variáveis e aspas com segurança

Nesta etapa, você atribuirá valores a variáveis, expandirá essas variáveis e verá por que as aspas duplas protegem valores que contêm espaços.

Crie um script com duas variáveis:

cat > variables.sh <<'EOF'
#!/bin/bash

course="Linux for Noobs"
workspace="/home/labex/project/shell-lab"

echo "Course: $course"
echo "Workspace: $workspace"
printf 'Quoted argument count: %s\n' "$#"
printf 'First argument: %s\n' "${1:-not provided}"
EOF

Não coloque espaços ao redor de = em uma atribuição do shell. As aspas duplas permitem expandir $course e, ao mesmo tempo, manter Linux for Noobs como um único valor.

Execute o script com um argumento que contém espaços:

bash variables.sh "beginner learner"

A variável especial $# representa o número de argumentos posicionais, enquanto $1 representa o primeiro argumento. ${1:-not provided} fornece um valor alternativo quando o argumento está ausente.

Salve a saída para inspeção:

bash variables.sh "beginner learner" > variable-output.txt
cat variable-output.txt

Valide argumentos posicionais

Nesta etapa, você exigirá um argumento, exibirá instruções de uso e retornará status de saída diferentes para indicar falha e sucesso.

Crie greet-argument.sh:

cat > greet-argument.sh <<'EOF'
#!/bin/bash

if [ "$#" -ne 1 ]; then
  echo "Usage: $0 NAME" >&2
  exit 2
fi

name=$1
echo "Hello, $name"
exit 0
EOF

O comando de teste [ ... ] verifica se a quantidade de argumentos é diferente de um. >&2 envia o texto de uso para a saída de erro padrão, e exit 2 informa que o uso foi inválido.

Execute o script sem argumento e, em seguida, exiba imediatamente $?, que contém o status de saída do comando anterior:

bash greet-argument.sh
echo "missing argument status: $?"

O status esperado é 2. Agora execute o script corretamente:

bash greet-argument.sh "Ada Lovelace"
echo "valid argument status: $?"

Salve a saudação bem-sucedida:

bash greet-argument.sh "Ada Lovelace" > argument-greeting.txt

Leia entradas interativas

Nesta etapa, você solicitará um valor ao usuário, lerá uma linha com segurança e tratará o caso de entrada vazia.

Crie ask-name.sh:

cat > ask-name.sh <<'EOF'
#!/bin/bash

read -r -p "Enter your name: " name

if [ -z "$name" ]; then
  echo "A name is required" >&2
  exit 1
fi

echo "Welcome, $name"
EOF

read -r lê uma linha sem tratar barras invertidas de forma especial. -p exibe um prompt, e -z verifica se o valor entre aspas está vazio.

Execute o script:

bash ask-name.sh

No prompt, digite:

Grace Hopper

Você deverá ver Welcome, Grace Hopper. Para gerar um resultado salvo e reproduzível, encaminhe uma linha de entrada para o mesmo script:

printf 'Grace Hopper\n' | bash ask-name.sh > interactive-greeting.txt
cat interactive-greeting.txt

Tome decisões usando testes de arquivos

Nesta etapa, você usará if, elif e else com testes de arquivos para classificar um caminho.

Crie classify-path.sh:

cat > classify-path.sh <<'EOF'
#!/bin/bash

path=${1:-}

if [ -z "$path" ]; then
  echo "Usage: $0 PATH" >&2
  exit 2
elif [ -d "$path" ]; then
  echo "directory: $path"
elif [ -f "$path" ]; then
  echo "file: $path"
else
  echo "missing: $path" >&2
  exit 1
fi

exit 0
EOF

-d verifica se o caminho corresponde a um diretório, e -f verifica se corresponde a um arquivo comum. A ordem é importante: o uso inválido é tratado antes da classificação do caminho.

Teste os três resultados possíveis:

bash classify-path.sh logs
bash classify-path.sh notes.txt
bash classify-path.sh missing.txt
echo "missing path status: $?"

Salve juntos os resultados bem-sucedidos. && executa o segundo comando somente se o primeiro for concluído com sucesso:

bash classify-path.sh logs > path-results.txt && bash classify-path.sh notes.txt >> path-results.txt
cat path-results.txt

Processe vários arquivos com um loop for

Nesta etapa, você percorrerá arquivos de log e contará os registros correspondentes em cada um deles.

Crie count-errors.sh:

cat > count-errors.sh <<'EOF'
#!/bin/bash

log_dir=${1:-logs}

if [ ! -d "$log_dir" ]; then
  echo "Log directory not found: $log_dir" >&2
  exit 1
fi

for log_file in "$log_dir"/*.log; do
  error_count=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s errors=%s\n' "$(basename "$log_file")" "$error_count"
done
EOF

A cada iteração, o loop atribui a log_file um dos caminhos correspondentes. Colocar "$log_file" entre aspas protege os espaços, enquanto basename remove o prefixo do diretório. grep -c conta as linhas correspondentes; || true impede que a ausência de correspondências interrompa um fluxo de trabalho maior configurado para falhar estritamente.

Execute o loop e salve sua saída:

bash count-errors.sh logs | tee error-counts.txt

Você deverá ver um resultado para cada arquivo .log.

Combine validação, loops e status de saída

Nesta etapa, você criará um script reutilizável que valida um diretório, resume seus logs e informa claramente se a execução foi bem-sucedida ou não.

Crie summarize-logs.sh:

cat > summarize-logs.sh <<'EOF'
#!/bin/bash

input_dir=${1:-}
output_file=${2:-}

if [ -z "$input_dir" ] || [ -z "$output_file" ]; then
  echo "Usage: $0 INPUT_DIR OUTPUT_FILE" >&2
  exit 2
fi

if [ ! -d "$input_dir" ]; then
  echo "Input directory not found: $input_dir" >&2
  exit 1
fi

: > "$output_file"

for log_file in "$input_dir"/*.log; do
  lines=$(wc -l < "$log_file")
  errors=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s lines=%s errors=%s\n' "$(basename "$log_file")" "$lines" "$errors" >> "$output_file"
done

echo "Wrote summary to $output_file"
exit 0
EOF

O comando : > "$output_file" cria o arquivo de destino, caso ele não exista, ou limpa seu conteúdo antes do loop. Execute o script fornecendo os dois argumentos obrigatórios:

chmod u+x summarize-logs.sh
./summarize-logs.sh logs log-summary.txt
echo "summary status: $?"
cat log-summary.txt

Teste o caminho de falha usando um diretório inexistente:

./summarize-logs.sh missing log-summary.txt
echo "missing directory status: $?"

O status de sucesso deverá ser 0, enquanto o status para um diretório inexistente deverá ser 1.

Resumo

Você criou e executou scripts Bash, usou shebangs e permissões, expandiu variáveis entre aspas, leu entradas posicionais e interativas e retornou status de saída significativos. Também tomou decisões com if/elif/else, testou arquivos e diretórios, percorreu logs com for e combinou esses conceitos em um resumidor reutilizável.

Esses fundamentos tornam as automações futuras mais fáceis de ler, mais seguras para executar novamente e mais simples de avaliar por outros comandos ou tarefas agendadas.