kb.erickguedes.com
Bash e Shell Script: Automação Unix/Linux

I/O, Redirecionamento e Pipes

Aula 3 de 6

File Descriptors

Cada processo no Unix tem três streams padrão:

FDNomeDescriçãoDireção
0stdinEntrada padrãoleitura
1stdoutSaída padrãoescrita
2stderrSaída de erroescrita

Redirecionamento

Redirecionamento de saída

# stdout para arquivo (sobrescreve)
ls > lista.txt

# stdout para arquivo (append)
echo "nova linha" >> log.txt

# stderr para arquivo
comando_que_erro 2> erros.log

# stderr append
comando_que_erro 2>> erros.log

# stdout e stderr para o mesmo arquivo
comando &> tudo.log        # bash 4+ (recomendado)
comando > tudo.log 2>&1    # clássico POSIX

# Descartar saída
comando > /dev/null 2>&1
comando &> /dev/null       # bash 4+

Redirecionamento de entrada

# stdin de arquivo
wc -l < arquivo.txt

# Here Document (heredoc)
cat << EOF
Linha 1
Linha 2
EOF

# Heredoc com variáveis expandidas
user="Maria"
cat << EOF
Olá, $user!
EOF

# Heredoc sem expansão (quote o delimitador)
cat << 'EOF'
$HOME não será expandido
EOF

# Heredoc com indentação (<<-)
if true; then
    cat <<- EOF
        Linha indentada com tab
    EOF
fi

Here String

# Passar string como stdin
wc -w <<< "Uma string qualquer"
grep "palavra" <<< "buscando palavra aqui"

Pipes

Pipe simples

# Conectar stdout de um comando ao stdin do próximo
ls -la | grep ".md" | wc -l

# Pipeline de visualização
ps aux | grep nginx | awk '{print $2}'

tee — Dividir saída

# Escrever em arquivo E manter no pipeline
echo "dados" | tee arquivo.txt | wc -c

# Append com -a
echo "mais dados" | tee -a arquivo.txt

# Escrever em múltiplos arquivos
ls | tee arquivo1.txt arquivo2.txt | head

pipefail

# Por padrão, o pipeline retorna o exit code do último comando
false | true
echo $?  # 0

# Com pipefail, retorna o primeiro não-zero
set -o pipefail
false | true
echo $?  # 1

# Prática: usar em scripts
set -euo pipefail

Subshells

# Subshell: executa em processo filho
(cd /tmp && pwd)  # /tmp
pwd                # diretório original

# Subshell não afeta o shell pai
var="fora"
(var="dentro"; echo "$var")  # dentro
echo "$var"                  # fora

Grouping

# Agrupar comandos no mesmo shell (não cria subshell)
{ echo "um"; echo "dois"; } | grep um

# Redirecionar grupo
{
    echo "Início"
    ls -la
    echo "Fim"
} > relatorio.txt

Process Substitution

# <(cmd): tratar saída como arquivo
diff <(ls dir1) <(ls dir2)

# >(cmd): tratar entrada como arquivo
echo "dados" > >(gzip > dados.gz)

# Exemplo prático: comparar saída de comandos
diff <(curl -s http://site1.com/api) <(curl -s http://site2.com/api)

xargs

# Básico: construir e executar comandos
echo "um dois três" | xargs mkdir

# Placeholder {} (com -I)
find . -name "*.txt" | xargs -I {} cp {} /backup/

# Paralelo com -P
seq 1 10 | xargs -P 4 -I {} sleep {}

# Delimitador nulo (seguro para nomes com espaços)
find . -name "*.txt" -print0 | xargs -0 rm -f

# xargs com substituição posicional
echo "src/*.txt" | xargs -n 1 wc -l

Named Pipes (FIFO)

# Criar pipe nomeado
mkfifo meu_pipe

# Terminal 1: escrever no pipe
echo "dados" > meu_pipe

# Terminal 2: ler do pipe
cat meu_pipe

# Útil para conexão entre processos que não compartilham stdin/stdout

# Limpar
rm meu_pipe

Lab: Pipeline de Análise de Logs

cat > analisa-logs.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

LOG_DIR="/var/log"
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

# 1. Coletar logs do sistema (subshell + redirecionamento)
{
    echo "=== LOGS DO SISTEMA ==="
    sudo journalctl -n 100 --no-pager 2>/dev/null || \
    tail -100 /var/log/syslog 2>/dev/null || \
    tail -100 /var/log/messages 2>/dev/null
} > "$TEMP_DIR/coletados.log" 2>&1

# 2. Pipeline de análise
echo "=== TOP 10 IPs ==="
grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "$TEMP_DIR/coletados.log" |
    sort |
    uniq -c |
    sort -rn |
    head -10

echo ""
echo "=== ERROS POR HORA ==="
grep -i "error\|fail\|critical" "$TEMP_DIR/coletados.log" |
    grep -oE '^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}' |
    uniq -c |
    sort -rn |
    head -10

echo ""
echo "=== COMPARAÇÃO COM LOG ANTERIOR ==="
if [[ -f "$LOG_DIR/logs-anteriores.log" ]]; then
    diff <(sort "$TEMP_DIR/coletados.log") <(sort "$LOG_DIR/logs-anteriores.log") || true
fi

# 3. Salvar resultados com tee
echo "Resumo gerado em $(date)" | tee -a "$TEMP_DIR/resumo.txt"
SCRIPT

chmod +x analisa-logs.sh

# Teste
echo "127.0.0.1 - - [01/Jan/2024:10:30:00] error na pagina" | tee test.log
echo "192.168.1.1 - - [01/Jan/2024:11:00:00] failed login" | tee -a test.log
cat test.log | ./analisa-logs.sh

Redirecionamento controla para onde os streams vão. Pipes conectam stdout ao stdin. Process substitution trata comandos como arquivos. xargs paraleliza execuções.