Ferramenta

Exercícios Práticos

Cenários reais que você vai encontrar como sysadmin ou desenvolvedor. Para cada problema: tente resolver antes de ver a solução.

🛠️ 20 exercícios 📊 5 categorias
ℹ️ Como usar

Leia o cenário e tente resolver antes de expandir a solução. Anote o comando que você tentaria usar. Depois compare com a solução e leia a explicação — entender o porquê importa mais que memorizar o comando.

🔐 Permissões

1
Um script backup.sh existe e está correto, mas ao tentar executá-lo com ./backup.sh você recebe: -bash: ./backup.sh: Permission denied. O que fazer?
Solução
ls -la backup.sh
# -rw-r--r-- 1 usuario usuario 245 Mai 20 backup.sh

chmod +x backup.sh
# ou: chmod 755 backup.sh (se quiser que outros também executem)
# ou: chmod u+x backup.sh (somente o dono)

./backup.sh

O arquivo existe (sem erro de "not found") mas não tem bit de execução. ls -la confirma: rw-r--r-- — nenhum 'x'. chmod +x adiciona execute para todos. Se o arquivo foi criado por um editor de texto, é esperado não ter permissão de execução — editores criam arquivos com 644.

2
Você precisa dar permissão de leitura a um arquivo relatorio.pdf para o grupo analistas sem mudar o dono e sem dar permissão para outros.
Solução
# Verificar situação atual
ls -la relatorio.pdf
# -rw------- 1 joao joao 45200 Mai 20 relatorio.pdf

# Mudar o grupo do arquivo
chown joao:analistas relatorio.pdf

# Adicionar leitura para o grupo
chmod g+r relatorio.pdf

# Verificar resultado
ls -la relatorio.pdf
# -rw-r----- 1 joao analistas 45200 Mai 20 relatorio.pdf

Dois passos: mudar o grupo do arquivo para analistas com chown, depois adicionar permissão de leitura ao grupo com chmod g+r. A notação simbólica g+r adiciona sem alterar as outras permissões.

3
Você criou um diretório compartilhado /dados/equipe/ onde vários usuários do grupo devs precisam criar arquivos. Mas os arquivos criados por diferentes usuários ficam com grupos diferentes, dificultando a colaboração. Como garantir que todo arquivo criado nesse diretório herde o grupo devs?
Solução
# Verificar situação atual
ls -ld /dados/equipe/
# drwxrwxr-x 2 root devs 4096 Mai 20 /dados/equipe/

# Ativar o SGID bit no diretório
chmod g+s /dados/equipe/

# Verificar resultado
ls -ld /dados/equipe/
# drwxrwsr-x 2 root devs 4096 Mai 20 /dados/equipe/
# Note o 's' no lugar do 'x' do grupo

# Agora qualquer arquivo criado aqui herda o grupo 'devs'
touch /dados/equipe/novo.txt
ls -la /dados/equipe/novo.txt
# -rw-rw-r-- 1 usuario devs 0 Mai 20 novo.txt

O SGID bit em diretório (chmod g+s) faz com que arquivos criados dentro herdem o grupo do diretório, não o grupo primário do usuário que criou. Fundamental para diretórios colaborativos.

⚙️ Processos

4
Um processo está consumindo 99% de CPU há horas. Como identificar qual processo é e encerrá-lo de forma limpa?
Solução
# Passo 1: identificar o processo
top           # interativo: pressione P para ordenar por CPU
# ou
ps aux --sort=-%cpu | head -5
# USER       PID %CPU %MEM    VSZ   RSS COMMAND
# usuario   8472 99.2  2.1 345678 87654 python3 minerador.py

# Passo 2: tentar encerrar limpo primeiro (SIGTERM)
kill 8472

# Aguardar alguns segundos...
sleep 3
ps aux | grep 8472

# Passo 3: se ainda existir, encerrar forçado (SIGKILL)
kill -9 8472

# Alternativa: por nome (cuidado — mata todos os processos com esse nome)
killall python3
# ou mais seguro:
pkill -f "minerador.py"

Sempre tente kill PID (SIGTERM) antes de kill -9 (SIGKILL). SIGTERM permite que o processo faça cleanup. SIGKILL mata imediatamente sem chance de salvar estado. pkill -f usa o comando completo, não só o nome do executável.

5
Você precisa rodar um script longo (sync_dados.sh) que deve continuar funcionando mesmo após você fechar o terminal SSH. Como fazer?
Solução
# Opção 1: nohup (simples, saída vai para nohup.out)
nohup ./sync_dados.sh > sync.log 2>&1 &
echo "PID: $!"   # PID do processo em background

# Opção 2: screen (permite reconectar ao terminal depois)
screen -S sync
./sync_dados.sh
# Ctrl+A, D para desconectar sem matar
# screen -r sync   # para reconectar depois

# Opção 3: tmux (mais moderno que screen)
tmux new -s sync
./sync_dados.sh
# Ctrl+B, D para desconectar
# tmux attach -t sync   # para reconectar

# Opção 4: systemd (para serviços persistentes)
# Criar /etc/systemd/system/sync.service e usar systemctl

nohup previne SIGHUP quando o terminal fecha. O & coloca em background. screen e tmux são mais versáteis — permitem "desconectar" e "reconectar" a sessões. Para processos que devem sobreviver a reboots, use systemd.

6
Você precisa ver em tempo real quais arquivos um processo específico (PID 1234) está usando atualmente. Qual comando?
Solução
# Ver file descriptors abertos
ls -la /proc/1234/fd/
# Mostra todos os arquivos abertos como symlinks

# Mais legível:
lsof -p 1234
# COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
# python3 1234 usuario  cwd    DIR    8,1     4096  ... /home/usuario/app
# python3 1234 usuario    0u   CHR  136,0      0t0    3 /dev/pts/0
# python3 1234 usuario    1u   CHR  136,0      0t0    3 /dev/pts/0
# python3 1234 usuario    2u   CHR  136,0      0t0    3 /dev/pts/0
# python3 1234 usuario    3r   REG    8,1    45200  ... /home/usuario/dados.csv
# python3 1234 usuario    4w   REG    8,1        0  ... /tmp/output.tmp

# Apenas arquivos regulares (excluindo sockets, pipes)
lsof -p 1234 | grep REG

/proc/PID/fd/ lista os file descriptors como symlinks — é a forma mais direta. lsof -p (list open files) dá uma visão mais legível com tipos e modos de abertura. FD 0,1,2 são stdin/stdout/stderr.

📁 Filesystem e Busca

7
Você precisa encontrar todos os arquivos .log maiores que 100MB em /var para liberar espaço em disco.
Solução
# Encontrar arquivos .log maiores que 100MB
find /var -name "*.log" -size +100M -type f

# Com tamanho e caminho ordenado por tamanho
find /var -name "*.log" -size +100M -type f \
  -exec du -sh {} \; | sort -rh

# Alternativa: du para ver maiores diretórios primeiro
du -sh /var/log/* | sort -rh | head -10

# Ver uso total do disco
df -h

# Antes de deletar: verifique se o arquivo ainda está em uso
lsof /var/log/nginx/access.log

# Esvaziar arquivo sem deletar (para arquivos abertos por processos)
> /var/log/nginx/access.log
# ou: truncate -s 0 /var/log/nginx/access.log

# Deletar logs antigos com segurança (mais de 30 dias)
find /var/log -name "*.log" -mtime +30 -delete

Importante: se um processo está escrevendo no arquivo (lsof confirma), não delete — esvazie com > arquivo ou truncate -s 0. Deletar um arquivo aberto libera o nome do diretório mas não o espaço até o processo fechar o file descriptor.

8
Você precisa encontrar todos os arquivos que pertencem ao usuário www-data no sistema para auditoria de segurança.
Solução
# Por nome de usuário
find / -user www-data -type f 2>/dev/null

# Por UID (mais rápido — evita resolver nome)
find / -uid 33 -type f 2>/dev/null

# Limitar a locais relevantes (mais rápido)
find /var /etc /home /tmp -user www-data 2>/dev/null

# Arquivos com SUID de qualquer usuário (auditoria de segurança)
find / -perm /4000 -type f 2>/dev/null

# Arquivos sem dono (possível problema após deletar usuário)
find / -nouser -type f 2>/dev/null

# Redirecionar erros de "Permission denied"
find / -user www-data 2>/dev/null | tee arquivos_www-data.txt

O 2>/dev/null suprime mensagens de "Permission denied" em diretórios que você não pode acessar. find / -nouser encontra arquivos cujo UID não corresponde a nenhum usuário — comum após deletar usuários sem remover seus arquivos.

9
O disco está quase cheio (df -h mostra 98%). Você precisa identificar rapidamente quais diretórios/arquivos estão ocupando mais espaço.
Solução
# Passo 1: ver uso por partição
df -h

# Passo 2: identificar maior consumidor no /
du -sh /* 2>/dev/null | sort -rh | head -10

# Passo 3: drill down no diretório suspeito (ex: /var)
du -sh /var/* 2>/dev/null | sort -rh | head -10

# Ferramenta interativa (se disponível)
ncdu /var

# Verificar se há arquivos deletados ainda abertos (por processos)
lsof | grep deleted | awk '{print $7, $9}' | sort -rn | head -10
# Este caso é comum: processo deletou o arquivo mas ainda o mantém aberto
# O espaço só é liberado quando o processo fecha o fd ou é encerrado

O caso dos "arquivos deletados mas ainda abertos" é frequentemente esquecido. Um log gigante deletado não libera espaço se o processo que o escreve ainda está rodando. lsof | grep deleted revela isso. Reinicie o processo para liberar o espaço.

🌐 Rede e Conectividade

10
Um serviço web não está respondendo na porta 80. Como diagnosticar se o problema é o serviço não rodando, o firewall bloqueando, ou outra coisa?
Solução — diagnóstico em camadas
# Camada 1: o serviço está rodando?
systemctl status nginx
ps aux | grep nginx
ss -tulnp | grep :80

# Camada 2: está ouvindo no endereço certo?
ss -tulnp | grep :80
# Se 127.0.0.1:80: só aceita conexões locais
# Se 0.0.0.0:80: aceita de qualquer interface

# Camada 3: testar localmente (sem firewall)
curl -v http://localhost:80
# Se funciona localmente mas não externamente → problema de firewall

# Camada 4: verificar firewall
sudo ufw status
sudo iptables -L -n | grep 80

# Camada 5: conectividade de rede
ping -c3 8.8.8.8           # Internet acessível?
ip route show default       # Gateway configurado?

# Camada 6: logs do serviço
sudo journalctl -u nginx -n 50
sudo tail -f /var/log/nginx/error.log

Diagnóstico em camadas é o método correto. Não chute. Confirme cada camada: o serviço existe → está rodando → está ouvindo → o firewall permite → a rede alcança. Os logs geralmente têm a resposta exata.

11
Você precisa copiar um diretório inteiro de arquivos para um servidor remoto de forma eficiente, sincronizando apenas o que mudou (não copia tudo toda vez).
Solução
# rsync: sincroniza apenas diferenças
rsync -avz /local/pasta/ usuario@servidor:/remoto/pasta/
# -a: archive mode (preserva permissões, timestamps, symlinks)
# -v: verbose
# -z: comprime em trânsito

# Com exclusões
rsync -avz --exclude='.git' --exclude='node_modules' \
  /local/projeto/ usuario@servidor:/var/www/projeto/

# Dry run (simula sem executar)
rsync -avz --dry-run /local/pasta/ usuario@servidor:/remoto/pasta/

# Deletar arquivos remotos que não existem mais localmente
rsync -avz --delete /local/pasta/ usuario@servidor:/remoto/pasta/

# Mostrar progresso
rsync -avz --progress /local/pasta/ usuario@servidor:/remoto/pasta/

# Via porta SSH não-padrão
rsync -avz -e "ssh -p 2222" /local/ usuario@servidor:/remoto/

rsync é a ferramenta certa para sincronização eficiente — transfere apenas as diferenças usando checksums. Atenção à barra no final do caminho de origem: /pasta/ (com barra) copia o conteúdo; /pasta (sem barra) copia o diretório em si.

📜 Shell e Scripting

12
Você tem um arquivo usuarios.csv com linhas no formato nome,email,departamento e precisa extrair apenas os emails (segunda coluna) de usuários do departamento "TI" (terceira coluna).
Solução
# Conteúdo do arquivo:
# joao,joao@empresa.com,TI
# maria,maria@empresa.com,RH
# pedro,pedro@empresa.com,TI

# Com awk (mais robusto para CSV simples)
awk -F',' '$3 == "TI" {print $2}' usuarios.csv
# Saída:
# joao@empresa.com
# pedro@empresa.com

# Com grep + cut
grep ',TI$' usuarios.csv | cut -d',' -f2

# Para salvar em arquivo
awk -F',' '$3 == "TI" {print $2}' usuarios.csv > emails_ti.txt

# Contar quantos são do TI
awk -F',' '$3 == "TI"' usuarios.csv | wc -l

# Para CSV com espaços ou campos entre aspas, use Python ou csvkit

awk é a ferramenta certa para processamento de campos. -F',' define a vírgula como separador. $3 == "TI" filtra a linha; {print $2} imprime o segundo campo. Para CSVs complexos (aspas, quebras de linha em campos), use Python com o módulo csv.

13
Você precisa trocar a string localhost por db.producao.com em todos os arquivos .conf de um diretório, sem abrir cada arquivo manualmente.
Solução
# Com sed (in-place com backup)
sed -i.bak 's/localhost/db.producao.com/g' *.conf

# -i: edita in-place (modifica o arquivo)
# .bak: cria backup com extensão .bak
# s/antigo/novo/g: substitui todas as ocorrências (g = global)

# Verificar antes (dry run — sem -i)
sed 's/localhost/db.producao.com/g' app.conf

# Sem criar backup
sed -i 's/localhost/db.producao.com/g' *.conf

# Em subdiretórios também (combinando find e sed)
find . -name "*.conf" -exec sed -i.bak 's/localhost/db.producao.com/g' {} \;

# Verificar mudanças
diff app.conf app.conf.bak

# Remover backups após confirmar
find . -name "*.bak" -delete

Sempre faça backup (-i.bak) antes de edições em massa. Teste o padrão sem -i primeiro para confirmar que está substituindo o que quer. Em macOS, sed -i requer argumento: sed -i '' 's/...'.

14
Um script bash está falhando silenciosamente — às vezes funciona, às vezes não, e você não consegue ver o erro. Como debugar?
Solução
# Opção 1: adicionar set -x no início (trace de execução)
#!/bin/bash
set -x  # imprime cada comando antes de executar
set -e  # encerra em qualquer erro

# Opção 2: rodar com bash -x
bash -x ./script.sh

# Opção 3: usar set -x em seção específica
set -x   # inicia trace
comandos_suspeitos
set +x   # termina trace

# Opção 4: verificar exit codes
#!/bin/bash
set -euo pipefail
# set -e: encerra em erro
# set -u: erro em variável não definida
# pipefail: propaga erros em pipes

# Opção 5: adicionar logs
log() { echo "[$(date '+%H:%M:%S')] $*" >&2; }
trap 'log "Erro na linha $LINENO"' ERR

log "Iniciando backup..."
# comandos...
log "Backup concluído"

# Verificar saída com shellcheck (ferramenta)
shellcheck script.sh  # em shellcheck.net se não tiver instalado

set -x é o primeiro recurso. trap 'echo erro na linha $LINENO' ERR captura erros e mostra onde ocorreram. ShellCheck (shellcheck.net) analisa estaticamente o script e encontra bugs comuns antes de executar.

15
Você precisa contar quantas vezes cada endereço IP aparece em um arquivo de log de acesso web (access.log) e mostrar os 5 mais frequentes.
Solução
# access.log formato nginx/apache:
# 192.168.1.1 - - [20/Mai/2026:10:30:00] "GET / HTTP/1.1" 200 1234

# Extrair IPs, contar ocorrências e mostrar top 5
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -5

# Saída:
# 1523 192.168.1.100
#  891 10.0.0.50
#  234 192.168.1.1
#   89 172.16.0.5
#   45 192.168.1.200

# Passo a passo explicado:
# awk '{print $1}'  → extrai primeiro campo (IP)
# sort              → ordena (necessário para uniq funcionar)
# uniq -c           → conta ocorrências consecutivas
# sort -rn          → ordena por número, decrescente
# head -5           → pega os 5 primeiros

# Apenas IPs com mais de 1000 requests (possível ataque?)
awk '{print $1}' access.log | sort | uniq -c | awk '$1 > 1000 {print}'

Este pipeline é um clássico do Unix: extrair → ordenar → contar → ordenar inversamente → pegar os N primeiros. uniq -c só conta sequências consecutivas iguais — por isso o sort antes é obrigatório.

👤 Usuários e Sistema

16
Você precisa criar um usuário deploy que só pode executar o script /opt/scripts/deploy.sh com sudo, mas não tem acesso a outros comandos privilegiados.
Solução
# Criar o usuário sem shell de login (conta de serviço)
sudo useradd -m -s /bin/bash deploy

# Editar sudoers com visudo (NUNCA edite /etc/sudoers diretamente)
sudo visudo

# Adicionar no final do arquivo:
# deploy ALL=(ALL) NOPASSWD: /opt/scripts/deploy.sh

# Ou melhor: criar arquivo separado em /etc/sudoers.d/
echo 'deploy ALL=(ALL) NOPASSWD: /opt/scripts/deploy.sh' | \
  sudo tee /etc/sudoers.d/deploy
sudo chmod 440 /etc/sudoers.d/deploy

# Verificar sintaxe antes de fechar
sudo visudo -c

# Testar como o usuário deploy
sudo -u deploy sudo /opt/scripts/deploy.sh
sudo -u deploy sudo /bin/bash  # deve falhar — não autorizado

NUNCA edite /etc/sudoers diretamente — um erro de sintaxe pode bloquear sudo em todo o sistema. Use sempre visudo, que valida a sintaxe antes de salvar. Arquivos em /etc/sudoers.d/ são mais organizados para regras específicas.

17
Após migrar um servidor, você percebe que há muitos arquivos -rw-r--r-- 1 1005 1005 — o usuário foi deletado mas os arquivos ficaram. Como reatribuir esses arquivos "órfãos" para um novo usuário?
Solução
# Verificar arquivos sem dono (UID não existe no sistema)
find / -nouser -type f 2>/dev/null | head -20

# Ou por UID específico
find / -uid 1005 -type f 2>/dev/null

# Reatribuir para novo usuário
find / -uid 1005 -exec chown novousuario:novogrupo {} \; 2>/dev/null

# Ou reatribuir só em uma área específica (mais seguro)
find /home /var/www -uid 1005 -exec chown www-data:www-data {} \; 2>/dev/null

# Verificar resultado
find /home -nouser -type f 2>/dev/null  # deve retornar vazio agora

# Boas práticas: ao deletar usuário, deletar ou reatribuir arquivos
# userdel -r usuario    # deleta usuário E home
# find / -user usuario -exec chown novouser {} \;  # antes de deletar

Arquivos órfãos são um problema de segurança: se um novo usuário receber o mesmo UID (1005), ele herdará automaticamente todos esses arquivos. Sempre limpe arquivos ao deletar usuários, ou reatribua antes.

18
Você precisa verificar se alguém além de você fez login no servidor nas últimas 24 horas e ver de qual IP vieram as conexões.
Solução
# Últimos logins bem-sucedidos
last | head -20
# usuario  pts/0  192.168.1.50  Mon May 20 10:30  still logged in
# root     pts/1  10.0.0.5      Mon May 20 09:15 - 09:45 (00:30)

# Tentativas de login falhas (possível ataque de força bruta)
sudo lastb | head -20
# ou
sudo grep "Failed password" /var/log/auth.log | tail -20

# IPs com mais tentativas falhas
sudo grep "Failed password" /var/log/auth.log | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -10

# Logins SSH bem-sucedidos
sudo grep "Accepted" /var/log/auth.log | tail -20

# Quem está logado AGORA
who
w  # mais detalhado: mostra o que está fazendo

last/var/log/wtmp. lastb/var/log/btmp (tentativas falhas). Se você vê muitas tentativas do mesmo IP, considere banir com ufw: sudo ufw deny from IP_SUSPEITO to any. Fail2ban automatiza isso.

19
Um serviço (nginx) parou de funcionar. Como diagnosticar o problema usando systemd?
Solução
# Verificar status do serviço
sudo systemctl status nginx
# ● nginx.service - A high performance web server
#    Loaded: loaded (/lib/systemd/system/nginx.service)
#    Active: failed (Result: exit-code)
#  Main PID: 12345 (code=exited, status=1/FAILURE)

# Ver logs do serviço (mais detalhado)
sudo journalctl -u nginx -n 50
# ou acompanhar em tempo real:
sudo journalctl -u nginx -f

# Logs desde a última falha
sudo journalctl -u nginx --since "1 hour ago"

# Tentar iniciar e ver o erro
sudo systemctl start nginx
# Se falhar, o erro aparece

# Verificar configuração do nginx
sudo nginx -t
# nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)

# Verificar o que está usando a porta
sudo ss -tulnp | grep :80
sudo lsof -i :80

# Após corrigir, iniciar e verificar
sudo systemctl start nginx
sudo systemctl enable nginx  # para iniciar no boot
sudo systemctl status nginx

journalctl -u serviço é o primeiro lugar a olhar. nginx -t (e equivalentes em outros serviços) valida a configuração antes de reiniciar. A mensagem "Address already in use" é muito comum — outra instância ou outro serviço está na mesma porta.

20
Você precisa agendar um script de backup para rodar todo dia às 2h da manhã automaticamente. Como configurar com cron?
Solução
# Editar o crontab do usuário atual
crontab -e

# Sintaxe: minuto hora dia-do-mês mês dia-da-semana comando
# Campos:  0-59  0-23  1-31        1-12 0-7(0=dom)
# * = qualquer valor, */n = a cada n, a,b = valores específicos

# Rodar backup.sh todo dia às 2:00
0 2 * * * /home/usuario/scripts/backup.sh >> /var/log/backup.log 2>&1

# Rodar às 2:30 de segunda a sexta
30 2 * * 1-5 /home/usuario/scripts/backup.sh

# A cada 15 minutos
*/15 * * * * /home/usuario/scripts/monitor.sh

# No primeiro dia de cada mês às 3h
0 3 1 * * /home/usuario/scripts/relatorio-mensal.sh

# Ver crontab atual
crontab -l

# Crontab do sistema (com campo de usuário)
sudo nano /etc/crontab
# 0 2 * * * root /usr/local/bin/backup.sh

# Verificar se o cron está rodando
sudo systemctl status cron  # Debian/Ubuntu
sudo systemctl status crond # Red Hat/Fedora

# Logs do cron
sudo grep CRON /var/log/syslog | tail -20

Cron não tem o mesmo $PATH do seu shell interativo — use caminhos absolutos para tudo (/usr/bin/python3 não apenas python3). Redirecione stdout e stderr para um arquivo de log para poder diagnosticar quando algo falha. Use crontab.guru para validar expressões cron.