未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合已有 Bash、Python、MySQL 和 Spark/YARN 基础的读者学习日志排查、批量文件处理与自动化流程组织,不是可直接部署的生产方案;本次仅作全文结构与代码静态审阅,未执行脚本或进行外部事实核验。按行拆分和逗号分列仅适用于无字段内逗号、无嵌入换行且列位置符合约定的简单 CSV,日志统计依赖时间和模块字段布局。当前合并脚本仍缺少临时文件初始化、无匹配输入及输出目录检查;拆分脚本重用输出目录时可能再次处理旧分片;ETL 未固定字段顺序,空订单可能导致缺文件或沿用旧数据,同日装载也未提供幂等机制。实际接口、目标表、本地装载配置、cron 环境及 Spark 业务 JAR 和 YARN 集群须自行准备,Spark 示例仅作提交和初步定位参考。服务器初始化须确认包管理器、架构、现有账户及权限,系统更新、递归属主变更和按修改时间删除数据须经过变更评估,并满足保留、备份及恢复要求。 已取消无法确定目标的可点击引用「Hadoop」,保留原提及;Missing link target: Hadoop 已取消无法确定目标的可点击引用「MySQL」,保留原提及;Missing link target: MySQL 已取消无法确定目标的可点击引用「Hive」,保留原提及;Missing link target: Hive

Linux 实战场景 - 日志分析 / 数据处理 / 自动化脚本

abstract 本文概要

综合运用前面所有知识,解决真实工作中的数据处理场景:

  1. 日志分析:快速从 GB 级日志中提取关键信息
  2. 批量数据处理:大文件拆分、清洗、合并
  3. 服务器环境搭建:新服务器快速初始化
  4. 自动化 ETL 脚本:需适配接口、数据契约和运行环境的管道示例

场景一:日志快速分析

背景

凌晨 3 点,数据管道报警:订单数据同步失败。你需要登上服务器,5 分钟内定位问题。

解决步骤

# 1. 找到最新的日志文件
ls -lt /var/log/etl/ | head -5

# 2. 看最后 100 行(快速了解当前状态)
tail -100 /var/log/etl/etl_20260318.log

# 3. 搜索错误关键词
grep -i "error\|exception\|failed\|warn" /var/log/etl/etl_20260318.log | tail -50

# 4. 找到第一个错误发生的时间
grep -i "error" /var/log/etl/etl_20260318.log | head -1

# 5. 看错误附近的上下文(前后各 10 行)
grep -n "ERROR" /var/log/etl/etl_20260318.log | head -5
# 假设第一个错误在第 234 行
sed -n '224,244p' /var/log/etl/etl_20260318.log

# 6. 统计各种错误类型的数量
grep "ERROR" /var/log/etl/etl_20260318.log \
    | awk '{print $4}' \
    | sort | uniq -c | sort -rn

# 7. 如果是 Java 应用,找 Exception 堆栈
grep -A 20 "Exception" /var/log/app/app.log | head -100

综合日志分析脚本

#!/bin/bash
# analyze_log.sh - 快速分析日志文件
# 用法: bash analyze_log.sh <日志文件> [错误关键词]

LOG_FILE="${1:-/var/log/etl/etl_$(date +%Y%m%d).log}"
KEYWORD="${2:-ERROR}"
OUTPUT_FILE="/tmp/log_analysis_$(date +%Y%m%d_%H%M%S).txt"

if [ ! -f "$LOG_FILE" ]; then
    echo "日志文件不存在: $LOG_FILE"
    exit 1
fi

{
    echo "========================================"
    echo "日志分析报告"
    echo "文件: $LOG_FILE"
    echo "分析时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "========================================"

    echo ""
    echo "【文件基本信息】"
    echo "文件大小: $(du -sh "$LOG_FILE" | cut -f1)"
    echo "总行数: $(wc -l < "$LOG_FILE")"
    echo "时间范围:"
    echo "  最早: $(head -1 "$LOG_FILE" | awk '{print $1, $2}')"
    echo "  最晚: $(tail -1 "$LOG_FILE" | awk '{print $1, $2}')"

    echo ""
    echo "【错误统计(关键词: $KEYWORD)】"
    error_count=$(grep -c "$KEYWORD" "$LOG_FILE")
grep_status=$?
if [ "$grep_status" -gt 1 ]; then
    echo "日志搜索失败: $LOG_FILE" >&2
    exit "$grep_status"
fi
    echo "错误总数: $error_count"

    echo ""
    echo "【按小时分布的错误数量】"
    grep "$KEYWORD" "$LOG_FILE" \
        | awk '{print substr($2,1,2) ":00"}' \
        | sort | uniq -c \
        | awk '{printf "  %s时 %d次\n", $2, $1}'

    echo ""
    echo "【最近 10 条错误】"
    grep "$KEYWORD" "$LOG_FILE" | tail -10

    echo ""
    echo "【ERROR 类型分布(如果有模块信息)】"
    grep "$KEYWORD" "$LOG_FILE" \
        | awk '{print $4}' \
        | sort | uniq -c | sort -rn | head -10

} | tee "$OUTPUT_FILE"

echo ""
echo "报告已保存到: $OUTPUT_FILE"
# 使用
bash analyze_log.sh /var/log/etl/etl_20260318.log ERROR
bash analyze_log.sh /var/log/app/app.log "Exception"

场景二:大文件处理

场景 2a:拆分大 CSV 文件

# 场景:有一个 10GB 的 CSV 文件,需要拆分成小块并行处理

FILE="/data/raw/huge_orders.csv"
OUTPUT_DIR="/data/split/"
LINES_PER_FILE=1000000  # 每个文件 100 万行

mkdir -p "$OUTPUT_DIR"

# 保存表头
HEADER=$(head -1 "$FILE")

# 拆分文件(去掉表头,按行数拆分)
tail -n +2 "$FILE" | split -l "$LINES_PER_FILE" - "${OUTPUT_DIR}/part_"

# 给每个分片加上表头
for part in "${OUTPUT_DIR}"/part_*; do
    # 临时文件
    tmp="${part}.tmp"
    echo "$HEADER" > "$tmp"
    cat "$part" >> "$tmp"
    mv "$tmp" "${part}.csv"
    rm "$part"
done

echo "拆分完成,共生成 $(ls "${OUTPUT_DIR}"/*.csv | wc -l) 个文件"
ls -lh "${OUTPUT_DIR}"/*.csv

场景 2b:CSV 数据清洗

#!/bin/bash
# clean_csv.sh - CSV 数据清洗脚本
# 功能:
#   1. 去掉 Windows 换行符
#   2. 去掉首尾空格
#   3. 删除空行
#   4. 删除仅由逗号组成的行
# 注意:本脚本不检查关键字段,也不转换日期格式

INPUT_FILE="$1"
OUTPUT_FILE="${INPUT_FILE%.csv}_clean.csv"

if [ -z "$INPUT_FILE" ]; then
    echo "用法: bash clean_csv.sh <输入文件.csv>"
    exit 1
fi

echo "开始清洗: $INPUT_FILE"
echo "输入行数: $(wc -l < "$INPUT_FILE")"

# 清洗流程
# 仅适用于不含字段内逗号或嵌入换行的简单 CSV
cat "$INPUT_FILE" \
    | tr -d '\r' \
    | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
    | grep -v '^$' \
    | grep -v '^,\+$' \
    > "$OUTPUT_FILE"

echo "输出行数: $(wc -l < "$OUTPUT_FILE")"
echo "清洗完成: $OUTPUT_FILE"

# 显示前 5 行验证
echo ""
echo "清洗后的前 5 行:"
head -5 "$OUTPUT_FILE"

场景 2c:多文件合并与去重

#!/bin/bash
# merge_deduplicate.sh - 合并多个 CSV 并去重

INPUT_DIR="/data/raw/orders/"
OUTPUT_FILE="/data/processed/orders_merged.csv"
KEY_COLUMN=1   # 以第 1 列(order_id)去重

echo "查找输入文件..."
FILES=(${INPUT_DIR}orders_*.csv)
echo "找到 ${#FILES[@]} 个文件"

# 取第一个文件的表头
echo "保存表头..."
head -1 "${FILES[0]}" > "$OUTPUT_FILE"

# 合并所有文件(跳过表头)
echo "合并文件..."
for file in "${FILES[@]}"; do
    tail -n +2 "$file"
done >> "${OUTPUT_FILE}.tmp"

# 按第 1 列去重(保留第一次出现的)
echo "去重处理..."
sort -s -t',' -k${KEY_COLUMN},${KEY_COLUMN} "${OUTPUT_FILE}.tmp" \
    | awk -F',' -v col="$KEY_COLUMN" \
        'seen[$col]++ == 0 {print}' \
    >> "$OUTPUT_FILE"

rm "${OUTPUT_FILE}.tmp"

# 统计结果
input_total=$(for f in "${FILES[@]}"; do tail -n +2 "$f"; done | wc -l)
output_total=$(($(wc -l < "$OUTPUT_FILE") - 1))
duplicates=$((input_total - output_total))

echo ""
echo "合并结果:"
echo "  输入总行数: $input_total"
echo "  输出行数: $output_total"
echo "  去除重复: $duplicates"
echo "  输出文件: $OUTPUT_FILE ($(du -sh "$OUTPUT_FILE" | cut -f1))"

场景三:服务器环境初始化

#!/bin/bash
# server_init.sh - 新数据服务器初始化脚本
# 功能:配置基础环境、安装常用工具、创建目录结构

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'  # 恢复默认颜色

log() { echo -e "${GREEN}[$(date '+%H:%M:%S')]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }

DATA_USER="alice"
DATA_GROUP="data"

log "====== 开始初始化服务器 ======"

# ---- 1. 系统更新 ----
log "1. 更新系统..."
if command -v yum &>/dev/null; then
    sudo yum update -y
    sudo yum install -y epel-release
    sudo yum install -y vim git wget curl jq tmux htop tree lsof \
                        python3 python3-pip \
                        java-1.8.0-openjdk java-1.8.0-openjdk-devel \
                        mysql
    PKG_MANAGER="yum"
else
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y vim git wget curl jq tmux htop tree lsof \
                        python3 python3-pip \
                        default-jdk \
                        mysql-client
    PKG_MANAGER="apt"
fi
log "系统更新完成"

# ---- 2. 创建用户和目录 ----
log "2. 创建数据目录结构..."

# 创建数据目录
sudo mkdir -p /data/{raw,processed,output,archive,logs,tmp}
sudo mkdir -p /data/warehouse/{ods,dwd,dws,ads}
sudo mkdir -p /var/log/etl

# 修改目录权限(假设数据用户是 alice)
sudo chown -R ${DATA_USER}:${DATA_GROUP} /data/ /var/log/etl/ 2>/dev/null || \
    warn "设置目录权限失败,请手动执行"

log "目录创建完成"
tree /data/

# ---- 3. 配置 SSH ----
log "3. 配置 SSH..."
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cat >> ~/.ssh/config << 'EOF'
# 防止 SSH 长时间不操作断线
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
EOF

# ---- 4. 安装 Miniconda ----
log "4. 安装 Miniconda..."
if ! command -v conda &>/dev/null; then
    CONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
    wget -q "$CONDA_URL" -O /tmp/miniconda.sh
    bash /tmp/miniconda.sh -b -p ~/miniconda3
    ~/miniconda3/bin/conda init bash
    rm /tmp/miniconda.sh
    log "Miniconda 安装完成"
else
    log "Conda 已安装,跳过"
fi

# ---- 5. 配置 .bashrc ----
log "5. 配置 Shell 环境..."
cat >> ~/.bashrc << 'EOF'

# ====== 数据工程环境配置 ======
# 别名
alias ll='ls -lh'
alias la='ls -lha'
alias ..='cd ..'
alias tailf='tail -f'
alias grep='grep --color=auto'

# 历史记录
export HISTSIZE=10000
export HISTFILESIZE=100000
export HISTTIMEFORMAT="%F %T "

# Java 环境(如果安装了 JDK)
if [ -d "/usr/java/jdk1.8.0_281" ]; then
    export JAVA_HOME=/usr/java/jdk1.8.0_281
    export PATH=$JAVA_HOME/bin:$PATH
fi

# 快速创建目录并进入
mkcd() { mkdir -p "$1" && cd "$1"; }
EOF

source ~/.bashrc

log "环境配置完成"

# ---- 6. 验证安装 ----
log "====== 安装验证 ======"
echo "操作系统: $(cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '"')"
echo "Python: $(python3 --version 2>&1)"
echo "Java: $(java -version 2>&1 | head -1)"
echo "Git: $(git --version)"
echo "curl: $(curl --version | head -1)"

log "====== 初始化完成 ======"

场景四:ETL 自动化脚本示例(需适配后部署)

#!/bin/bash
# etl_pipeline.sh - ETL 数据管道示例,非开箱即用的生产方案
# 功能:每日完整的数据同步和处理流程

set -euo pipefail

# ============================================================
# 配置
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATE="${1:-$(date +%Y-%m-%d)}"
DATE_FMT="${DATE//-/}"  # 20260318 格式

# 目录配置
DATA_DIR="/data"
RAW_DIR="${DATA_DIR}/raw/${DATE}"
CLEAN_DIR="${DATA_DIR}/processed/${DATE}"
OUTPUT_DIR="${DATA_DIR}/output/${DATE}"
LOG_DIR="/var/log/etl"
LOG_FILE="${LOG_DIR}/etl_${DATE_FMT}.log"

# 数据库配置(从环境变量读取,不硬编码密码)
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-3306}"
DB_USER="${DB_USER:-etl_user}"
DB_NAME="${DB_NAME:-warehouse}"
DB_PASS="${DB_PASS:?请设置 DB_PASS 环境变量}"

# ============================================================
# 工具函数
# ============================================================
mkdir -p "$LOG_DIR"

log() {
    local level="$1" msg="$2"
    local ts=$(date '+%Y-%m-%d %H:%M:%S')
    echo "${ts} [${level}] ${msg}" | tee -a "$LOG_FILE"
}
info()  { log "INFO " "$1"; }
warn()  { log "WARN " "$1"; }
error() { log "ERROR" "$1"; exit 1; }

step_start() {
    info ">>> 开始步骤: $1"
    step_start_time=$(date +%s)
}

step_end() {
    local elapsed=$(($(date +%s) - step_start_time))
    info "<<< 步骤完成 (耗时 ${elapsed}s)"
}

# ============================================================
# 步骤定义
# ============================================================

# 步骤 1:检查前置条件
check_prerequisites() {
    step_start "前置条件检查"

    # 检查目录
    for dir in "$RAW_DIR" "$CLEAN_DIR" "$OUTPUT_DIR"; do
        mkdir -p "$dir"
    done

    # 检查数据库连接
    if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" \
         -e "SELECT 1" "$DB_NAME" &>/dev/null; then
        error "数据库连接失败:${DB_HOST}:${DB_PORT}"
    fi
    info "数据库连接正常"

    step_end
}

# 步骤 2:数据采集(模拟:实际应该调用数据源 API 或同步工具)
collect_data() {
    step_start "数据采集"

    # 模拟从 API 获取订单数据
    info "从 API 采集 ${DATE} 的订单数据..."
    curl -s -f \
        -H "Authorization: Bearer ${API_TOKEN:-}" \
        "https://data-source.example.com/api/orders?date=${DATE}" \
        -o "${RAW_DIR}/orders.json" \
        || error "采集订单数据失败"

    # 转换 JSON 为 CSV(用 Python 处理 JSON 比 Shell 更稳)
    python3 << EOF
import json, csv

with open('${RAW_DIR}/orders.json') as f:
    data = json.load(f)

orders = data.get('orders', [])
if not orders:
    print("警告:无数据")
    exit(0)

with open('${RAW_DIR}/orders.csv', 'w', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=orders[0].keys())
    writer.writeheader()
    writer.writerows(orders)

print(f"写入 {len(orders)} 条记录")
EOF

    local raw_count=$(wc -l < "${RAW_DIR}/orders.csv")
    info "采集完成:原始数据 $((raw_count-1)) 条"

    step_end
}

# 步骤 3:数据清洗
clean_data() {
    step_start "数据清洗"

    local raw_file="${RAW_DIR}/orders.csv"
    local clean_file="${CLEAN_DIR}/orders.csv"

    # 清洗逻辑
    head -1 "$raw_file" > "$clean_file"   # 保留表头
    # 仅适用于简单 CSV,且第 1、5、6 列依次为 order_id、金额、状态
    tail -n +2 "$raw_file" \
        | tr -d '\r' \
        | awk -F',' '$1 != "" && $5 > 0 && $6 ~ /^(paid|pending|shipped)$/ { print }' \
        >> "$clean_file"

    local raw_count=$(($(wc -l < "$raw_file") - 1))
    local clean_count=$(($(wc -l < "$clean_file") - 1))
    local dropped=$((raw_count - clean_count))

    info "清洗完成:保留 ${clean_count} 条,丢弃 ${dropped} 条无效数据"

    if [ "$clean_count" -eq 0 ]; then
        error "清洗后无有效数据,请检查原始数据"
    fi

    step_end
}

# 步骤 4:统计汇总
aggregate_data() {
    step_start "数据汇总"

    local clean_file="${CLEAN_DIR}/orders.csv"

    # 生成各维度汇总(awk 处理)
    awk -F',' 'NR>1 {
        total_orders++
        total_amount += $5
        city_orders[$3]++
        city_amount[$3] += $5
        status_count[$6]++
    }
    END {
        # 汇总到输出目录
        print "date,metric,value" > "'"${OUTPUT_DIR}/summary.csv"'"
        printf "%s,total_orders,%d\n", "'"${DATE}"'", total_orders >> "'"${OUTPUT_DIR}/summary.csv"'"
        printf "%s,total_amount,%.2f\n", "'"${DATE}"'", total_amount >> "'"${OUTPUT_DIR}/summary.csv"'"

        # 城市维度
        for (city in city_orders) {
            printf "%s,city_orders_%s,%d\n", "'"${DATE}"'", city, city_orders[city] >> "'"${OUTPUT_DIR}/summary.csv"'"
        }
    }' "$clean_file"

    info "汇总文件已生成:${OUTPUT_DIR}/summary.csv"
    step_end
}

# 步骤 5:写入数据库
load_to_db() {
    step_start "写入数据库"

    local summary_file="${OUTPUT_DIR}/summary.csv"

    # 写入 MySQL
    mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" << SQL
LOAD DATA LOCAL INFILE '${summary_file}'
INTO TABLE daily_summary
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(date, metric, value);
SQL

    local loaded=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
                    -se "SELECT COUNT(*) FROM daily_summary WHERE date='${DATE}'")
    info "数据库写入完成:${loaded} 条记录"

    step_end
}

# 步骤 6:发送完成通知(Webhook)
send_notification() {
    step_start "发送完成通知"

    local total_time=$(($(date +%s) - pipeline_start_time))
    local row_count=$(($(wc -l < "${CLEAN_DIR}/orders.csv") - 1))

    curl -s -X POST \
        -H "Content-Type: application/json" \
        -d "{
            \"pipeline\": \"daily_etl\",
            \"date\": \"${DATE}\",
            \"status\": \"success\",
            \"rows\": ${row_count},
            \"duration_seconds\": ${total_time}
        }" \
        "${WEBHOOK_URL:-https://hooks.example.com/notify}" || \
        warn "发送通知失败(不影响主流程)"

    step_end
}

# ============================================================
# 主流程
# ============================================================
main() {
    pipeline_start_time=$(date +%s)
    info "=============================="
    info "ETL 管道开始执行"
    info "处理日期: ${DATE}"
    info "日志文件: ${LOG_FILE}"
    info "=============================="

    check_prerequisites
    collect_data
    clean_data
    aggregate_data
    load_to_db
    send_notification

    local total_time=$(($(date +%s) - pipeline_start_time))
    info "=============================="
    info "ETL 管道执行完成"
    info "总耗时: ${total_time}s"
    info "=============================="
}

# 运行主流程
main "$@"

部署为定时任务:

# 给脚本添加执行权限
chmod +x /home/alice/scripts/etl_pipeline.sh

# 设置 crontab
crontab -e
# 每天凌晨 2:00 执行 ETL
0 2 * * * /home/alice/scripts/etl_pipeline.sh >> /var/log/etl/cron_$(date +\%Y\%m\%d).log 2>&1

场景五:Spark 作业提交与监控

#!/bin/bash
# submit_spark_job.sh - 提交 Spark 作业并监控

set -e

DATE="${1:-$(date +%Y-%m-%d)}"
JAR_FILE="/home/alice/jars/data-pipeline-1.0.jar"
MAIN_CLASS="com.company.DailyETL"
LOG_DIR="/var/log/spark"
LOG_FILE="${LOG_DIR}/spark_$(date +%Y%m%d_%H%M%S).log"

mkdir -p "$LOG_DIR"

echo "提交 Spark 作业..."
echo "日期: $DATE"
echo "JAR: $JAR_FILE"
echo "日志: $LOG_FILE"

# 在 tmux 中提交(这样可以随时查看,SSH 断线也不影响)
tmux new-session -d -s "spark_${DATE//-/}" \
    "spark-submit \
        --master yarn \
        --deploy-mode cluster \
        --driver-memory 4g \
        --executor-memory 8g \
        --executor-cores 4 \
        --num-executors 20 \
        --conf spark.sql.adaptive.enabled=true \
        --class ${MAIN_CLASS} \
        ${JAR_FILE} \
        --date ${DATE} \
        > ${LOG_FILE} 2>&1; echo '任务完成,退出码: '\$?"

echo ""
echo "作业已在 tmux 会话中启动"
echo "查看日志: tmux attach -t spark_${DATE//-/}"
echo "实时日志: tail -f ${LOG_FILE}"

# 等待作业启动,检查 YARN 作业 ID
sleep 10
APP_ID=$(grep "application_" "$LOG_FILE" | head -1 | grep -o 'application_[0-9_]*' | head -1)

if [ -n "$APP_ID" ]; then
    echo ""
    echo "YARN 作业 ID: $APP_ID"
    echo "查看作业状态: yarn application -status $APP_ID"
    echo "查看作业日志: yarn logs -applicationId $APP_ID"
fi

常用一行命令(One-liners)

# 找出占用磁盘最多的目录(TOP 10)
du -h --max-depth=2 /data/ 2>/dev/null | sort -rh | head -10

# 按日志前两个空白字段统计连续相同行数(不等同于实时按分钟统计)
tail -f app.log | awk '{print $1" "$2}' | uniq -c

# 找出最近 1 天修改的所有大文件(>100MB)
find / -mtime -1 -size +100M -type f 2>/dev/null

# 批量替换目录下所有文件中的某个字符串
find /config -name "*.xml" -exec sed -i 's/old_host/new_host/g' {} \;

# 监控日志,发现 ERROR 就响铃
tail -f app.log | grep --line-buffered "ERROR" | awk '{print "\a" $0}'

# 统计每个 IP 的访问量(访问日志)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

# 快速测试某台机器的某个端口是否通
(echo >/dev/tcp/192.168.1.100/3306) 2>/dev/null && echo "开放" || echo "关闭"

# 批量创建测试数据文件
for i in $(seq 1 10); do
    echo "id,name,value" > /data/test/data_${i}.csv
    seq 1 1000 | awk -v i=$i '{printf "%d,name_%d_%d,%.2f\n", NR, i, NR, rand()*100}' >> /data/test/data_${i}.csv
done

排查案例:磁盘满导致 ETL 失败

# 收到告警:ETL 失败,错误信息:No space left on device

# 1. 查看磁盘使用情况
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   50G     0 100% /data

# 2. 找最占空间的目录
du -sh /data/* 2>/dev/null | sort -rh | head -10

# 3. 找大文件
find /data -size +1G -type f 2>/dev/null | xargs ls -lh | sort -k5 -rh

# 4. 找可以清理的过期日志
ls -lt /data/logs/ | head -20
# 发现 90 天前的日志还在

# 5. 清理
find /data/logs -name "*.log" -mtime +30 -exec rm {} \;
find /data/raw -name "*.csv" -mtime +90 -exec rm {} \;

# 6. 清理完验证
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   35G   15G  70% /data

# 7. 重跑 ETL
bash /home/alice/scripts/etl_pipeline.sh

# 8. 设置定期清理的 crontab,避免下次再发生
crontab -e
# 0 3 * * * find /data/logs -name "*.log" -mtime +30 -delete

下一步建议

学完本系列,推荐的进阶方向:

  1. 深入 Python:Shell 擅长文件操作和简单文本处理,复杂逻辑用 Python 更合适
  2. 大数据工具:Hadoop / Spark / Flink 的 Shell 脚本提交和管理
  3. 数据库操作:Shell 中调用 MySQL / Hive 命令行执行 SQL
  4. 监控与告警:结合 Shell 脚本和公司的监控系统(Prometheus/Grafana)

tip 最后的建议

Linux 技能需要在真实工作中积累。遇到不会的命令,不要绕过,而是搞清楚原理再往前走。 每个在生产上排查过的问题、每个写过的脚本,都是真实的技能沉淀。

#Linux #Shell #实战 #ETL #数据工程 #自动化