Fundamentos de Shell Scripting

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 especificam explicitamente o interpretador, colocam as expansões de variáveis entre aspas, 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 progressivamente. Você executará scripts pelo Bash e como arquivos executáveis, usará variáveis e argumentos posicionais, lerá entradas interativas, criará ramificações com if e testes de arquivos, escolherá códigos de saída, iterará com for e combinará essas ideias em um simples resumidor de logs.

Crie e execute seu primeiro script

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

Entre no 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 para o Bash. Isso funciona mesmo antes de adicionar a permissão de execução:

bash hello.sh

Adicione a permissão de execução e inspecione 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 de forma segura

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 manter Linux for Noobs como um único valor.

Execute o script com um argumento que contenha espaços:

bash variables.sh "beginner learner"

A variável especial $# contém o número de argumentos posicionais, e $1 contém o primeiro argumento. ${1:-not provided} fornece um valor alternativo quando o primeiro argumento não foi informado.

Salve a saída para inspecioná-la:

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 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 não é igual a um. >&2 envia o texto de uso para o erro padrão, e exit 2 informa que o uso é inválido.

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

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

O status esperado é 2. 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 uma entrada interativa

Nesta etapa, você solicitará um valor ao usuário, lerá uma linha com segurança e tratará uma 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 as 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 salvar um resultado reproduzível, envie uma linha de entrada pelo pipe para o mesmo script:

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

Crie ramificações com 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 é um diretório, e -f verifica se é um arquivo regular. A ordem é importante: o uso inválido é tratado antes do tipo do caminho.

Teste os três resultados possíveis para o caminho:

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 bem-sucedido:

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ê iterará sobre 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

O loop atribui um caminho correspondente a log_file em cada iteração. 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 e estrito.

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 operação foi bem-sucedida ou falhou.

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 destino ou esvazia seu conteúdo com segurança antes do loop. Execute o script com 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 deve ser 0, enquanto o status do diretório inexistente deve 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. Você também criou ramificações com if/elif/else, testou arquivos e diretórios, iterou sobre logs com for e combinou essas ideias em um resumidor reutilizável.

Esses fundamentos tornam a automação posterior mais fácil de ler, mais segura para executar novamente e mais simples de ser avaliada por outros comandos ou tarefas agendadas.