本章目标:会用 Linux 常用命令处理文件、日志和进程,能写一个带参数和判断的 Shell 脚本。 前置知识:第 02 章装好 Git Bash。 预计用时:2–3 小时,边看边敲。
1. 为什么需要它
数据开发的代码最终跑在 Linux 服务器上。任务失败时,你要登录机器看日志;数据文件没到,你要用 find 和 du 找;临时清洗数据,你要写一个 Shell 脚本;调度系统执行的,也是 Shell 命令或 Python 脚本。
Windows 用户不需要装双系统。本章所有命令都能在 Git Bash 里运行;想更接近生产环境,可以装 WSL。Git Bash 是把 Linux 命令搬到 Windows,少数地方不同:路径写成 /c/...,chmod 权限是模拟的,没有 crontab。这些差异会在用到时标出来。
2. 核心概念
2.1 文件系统与路径
Linux 把一切组织成树,根是 /:
/
├── home/ 用户目录,~ 通常指这里
├── etc/ 配置文件
├── var/log/ 日志
└── usr/bin/ 常用命令
- 绝对路径:从
/开始,例如/var/log/app.log。 - 相对路径:从当前目录开始,例如
data/sample/orders_demo.csv。 .表示当前目录,..表示上一级,~表示用户主目录。- 项目里统一用相对路径:命令在项目根目录执行,路径写成
data/sample/。
注意:Git Bash 的盘符路径写法和 Windows 不同。本章和后续章节不要求你写盘符路径,一律在项目根目录用相对路径。
2.2 权限:rwx 与 chmod
ls -l 输出:
-rwxr-x--- 1 alice data 812 Oct 20 10:00 run.sh
第一列共 10 位:第 1 位是文件类型(- 普通文件,d 目录),后面 9 位分三组:所有者、同组用户、其他人。每组 rwx 分别表示读、写、执行。
| 数字 | 权限 | 含义 |
|---|---|---|
| 7 | rwx | 读、写、执行 |
| 6 | rw- | 读、写 |
| 5 | r-x | 读、执行 |
| 4 | r-- | 只读 |
| 0 | --- | 无权限 |
chmod 750 run.sh 等于 rwxr-x---。给脚本加执行权限常用 chmod +x run.sh;chown user:group file 修改所有者,通常需要 root 权限。Git Bash 里的 chmod 只是尽量模拟,Windows 文件系统不一定真正限制访问。
2.3 常用命令
| 命令 | 用途 | 例子 |
|---|---|---|
ls |
列目录 | ls -lh data/sample |
cd |
切目录 | cd data/sample |
pwd |
显示当前路径 | pwd |
cp / mv / rm |
复制 / 移动改名 / 删除 | cp a.csv a.bak、mv a.bak a.csv、rm -i a.bak |
mkdir |
建目录 | mkdir -p output/2018/09 |
find |
按条件找文件 | find data -name "*.csv" |
grep |
按内容搜索 | grep -n "canceled" data/sample/orders_demo.csv |
head / tail |
看前几行 / 后几行或持续看 | head -n 5 a.csv、tail -f app.log |
wc |
统计行/词/字节 | wc -l data/sample/orders_demo.csv |
sort / uniq |
排序 / 相邻去重计数 | sort file、uniq -c(先排序再用) |
awk / sed |
按列处理 / 流式替换截取 | awk -F, '{print $3}' a.csv、sed -n '1,3p' f |
xargs |
把输出变成参数 | xargs 把 find 的结果接到 wc -l |
tar |
打包/解包 | tar -czf raw.tar.gz data/sample |
ps / kill |
看进程 / 结束进程 | ps aux(可接 grep python 过滤)、kill 12345 |
df / du |
磁盘空间 / 目录大小 | df -h、du -sh data/sample |
删除类命令先确认路径:rm -rf 不会进回收站。kill -9 会直接终止进程,生产环境先尝试普通 kill。
2.4 管道与重定向
管道 | 把前一个命令的标准输出交给后一个命令:
awk -F, '{print $3}' data/sample/orders_demo.csv | sort | uniq -c | sort -nr | head
这条命令依次完成:取城市列(第 3 列)、排序、去重计数、按次数倒序、取前 10。
重定向把输出写到文件:
| 写法 | 作用 |
|---|---|
cmd > out.txt |
覆盖写入标准输出 |
cmd >> out.txt |
追加标准输出 |
cmd 2> err.txt |
把错误输出写到文件 |
cmd > out.txt 2>&1 |
标准输出和错误都写到同一个文件 |
cmd < input.txt |
从文件读取标准输入 |
cmd 后接 tee out.txt |
同时打印到屏幕和写入文件 |
/dev/null 是“黑洞”,不需要的输出可以丢掉:cmd > /dev/null 2>&1。
2.5 环境变量与 PATH
环境变量是 Shell 里的键值对。常用操作:
echo "$HOME"
export PROJECT_NAME="shop-warehouse"
env | grep PROJECT_NAME
which python
echo "$PATH"
PATH 是一串用冒号分隔的目录。输入 python 时,Shell 按 PATH 的顺序找可执行文件,找到第一个就执行。命令找不到,通常是没装或没进 PATH。
export 只在当前终端有效。要永久生效,把配置写到 ~/.bashrc,再执行 source ~/.bashrc。不要把密码写进脚本和 Git。
2.6 Shell 脚本入门
脚本第一行写 shebang #!/usr/bin/env bash。核心语法:
set -euo pipefail # 出错退出、未定义变量报错、管道失败算失败
name="${1:-world}" # 位置参数,$1 没传时用 world
if [[ -z "$name" ]]; then # 判断字符串为空
echo "name 不能为空" >&2
exit 1
fi
for i in $(seq 1 3); do # 循环
echo "hello, $name ($i)"
done
要点:变量加双引号;[[ ]] 做条件判断;if 以 fi 结束,for 以 done 结束。保存脚本后用 chmod +x 加执行权限,再 ./脚本名.sh 运行。
2.7 crontab 概念
crontab 用五个字段描述执行时间:
分 时 日 月 周 命令
0 2 * * * cd <项目目录> && python scripts/build_site.py >> logs/build.log 2>&1
上面表示每天 02:00 执行构建脚本,并把输出追加到日志。相关命令是 crontab -e 编辑、crontab -l 查看。Git Bash 没有 crontab;Windows 用任务计划程序,生产环境通常用 Airflow、DolphinScheduler 或 DataWorks,调度章节再展开。
注意:cron 环境和登录 Shell 不一样,PATH 可能更短。生产任务要写清解释器路径、工作目录和日志位置,并保证重跑幂等。
3. 动手示例
3.1 生成一份模拟访问日志
在项目根目录执行。用 day1、day2、day3 代替真实日期,真实日志第一列通常是日期:
mkdir -p shell-lab
cd shell-lab
for i in $(seq 1 300); do
day="day$((i % 3 + 1))"
ip="10.0.0.$((i % 8 + 1))"
code=$(( i % 17 == 0 ? 500 : 200 ))
printf "%s 10:%02d:00 %s GET /item/%d %d\n" \
"$day" "$((i % 60))" "$ip" "$((i % 5 + 1))" "$code"
done > access.log
head -n 3 access.log
wc -l access.log
每行依次是:日期分区、时间、IP、方法、路径、状态码。共 300 行,其中 17 行状态码为 500。
3.2 统计 Top10、状态码和按天切分
访问次数最多的 10 个 IP:
awk '{print $3}' access.log | sort | uniq -c | sort -nr | head -n 10
状态码分布:
awk '{print $6}' access.log | sort | uniq -c
按第一列切分文件:
awk '{print > $1 ".log"}' access.log
ls -l day*.log
wc -l day*.log
$1 ".log" 是字符串拼接,> 是 awk 的输出重定向。执行后会得到 day1.log、day2.log、day3.log。
3.3 写一个可复用脚本
创建 check_log.sh:
#!/usr/bin/env bash
set -euo pipefail
LOG="${1:-access.log}"
[[ -f "$LOG" ]] || { echo "找不到文件:$LOG" >&2; exit 1; }
echo "总行数:$(wc -l < "$LOG")"
echo "500 错误:$(grep -c ' 500$' "$LOG" || true)"
echo "Top 5 访问 IP:"
awk '{print $3}' "$LOG" | sort | uniq -c | sort -nr | head -n 5
执行 chmod +x check_log.sh,再运行 ./check_log.sh access.log。传入不存在的文件时会打印错误并以非零状态退出。grep -c 没匹配时返回非零,|| true 防止 set -e 中断。
4. 常见坑
| 坑 | 表现 | 处理 |
|---|---|---|
| 在错误目录执行 | No such file or directory |
先 pwd,再 ls 确认 |
rm -rf 路径写错 |
删掉整个目录,无法恢复 | 删除前先 ls 确认,能不用 -rf 就不用 |
| 变量不加引号 | 路径有空格时命令被拆开 | 用 "$var" |
| 管道后忘记排序 | uniq -c 结果不对 |
uniq 只处理相邻重复,先 sort |
| 重定向覆盖日志 | > 把历史日志清空 |
追加用 >> |
| 脚本没有执行权限 | Permission denied |
chmod +x script.sh |
| Git Bash 没有 crontab | crontab: command not found |
用 WSL,或知道它是生产环境概念 |
| Windows 路径混用 | 直接在 bash 里写 Windows 盘符路径会出错 | Git Bash 的盘符写法不同,项目内统一用相对路径 |
5. 练习
练习 1:统计 CSV 行数
写一条命令,统计 data/sample/orders_demo.csv 的数据行数(不含表头),并说明为什么结果不是 12 加 1。
参考答案
tail -n +2 data/sample/orders_demo.csv | wc -l
文件有 12 个数据行加 1 行表头;tail -n +2 从第二行开始输出,结果为 12。
练习 2:权限换算
把 run.sh 设成“所有者可读写执行、同组可读、其他人无权限”,写出数字权限和命令。
参考答案
所有者 rwx 是 7,同组 r-- 是 4,其他人 --- 是 0,所以是 740。
chmod 740 run.sh
ls -l run.sh
练习 3:统计订单状态 Top 3
用管道从 data/sample/orders_demo.csv 统计出现次数最多的 3 种 order_status。
参考答案
tail -n +2 data/sample/orders_demo.csv | cut -d, -f2 | sort | uniq -c | sort -nr | head -n 3
结果依次是 paid 8、canceled 2、shipped 1(unavailable 也是 1,排序相同取前 3)。status 是第 2 列。
练习 4:统计 500 错误和 Top3 IP
在 shell-lab 目录中,统计状态码 500 的行数,以及访问次数最多的 3 个 IP。
参考答案
grep -c ' 500$' access.log
awk '{print $3}' access.log | sort | uniq -c | sort -nr | head -n 3
日志一共 300 行,500 错误 17 行;$3 是 IP 列。
练习 5:改造日志脚本
给 check_log.sh 增加一个判断:如果 500 错误数超过 10,打印“错误过多”并以状态码 2 退出。
参考答案
在输出统计结果后加:
if (( errors > 10 )); then
echo "错误过多" >&2
exit 2
fi
(( ... )) 用于整数比较。errors 来自 grep -c,|| true 保证 0 条时变量仍然有值。
练习 6:解释定时任务
解释下面这行 cron 的含义,并说明输出和错误分别去了哪里:
30 1 * * * cd <项目目录> && python scripts/build_site.py >> logs/build.log 2>&1
参考答案
每天 01:30 进入项目目录并执行构建脚本。标准输出追加到 logs/build.log,标准错误通过 2>&1 也追加到同一个文件。
注意 cron 的 PATH 可能不完整,生产上要确认 python 能找到,并保证脚本重跑结果一致。
6. 面试延伸
面试题:755 和 644 分别代表什么?
是什么:755 是 rwxr-xr-x,所有者可读写执行,同组和其他人可读可执行;644 是 rw-r--r--,所有者可读写,其他人只读。
解决什么问题:权限控制决定谁能改文件、谁能执行脚本。脚本需要执行位,配置和数据通常不需要。权限过宽会带来安全风险,过窄会导致任务跑不起来。
我的实操边界:我在本地用 chmod +x 给 Shell 脚本加执行权限,用 chmod 644 处理数据文件;公司级的权限体系和 sudo 策略没有实操过。
面试题:管道和重定向有什么区别?
是什么:管道 | 把前一个命令的标准输出接到后一个命令的标准输入;重定向把输出写到文件或从文件读入。
解决什么问题:管道让多个小命令组合成一条处理链,不需要写中间文件;重定向用于保存结果、分离错误日志、丢弃无用输出。
我的实操边界:我在日志统计里用管道组合 awk、sort、uniq,用 > 和 2>&1 保存任务输出;没有在生产调度平台上配置过日志轮转。
7. 本章验收
- 能说出绝对路径、相对路径、
~、.、..的区别。 - 能解释
rwx和 755、644、740 的含义,并用chmod改权限。 - 能独立使用
ls/cd/find/grep/head/tail/wc/sort/uniq/awk/sed/xargs/ps/du完成一次文件或日志处理。 - 能写出带参数、
if判断、for循环和set -euo pipefail的 Shell 脚本。 - 能完成日志 Top10、状态码统计、按天切分三个操作。
- 能解释 cron 五个字段,并说明 Git Bash 为什么没有 crontab。
下一章:04 Git 与 Python。SQL 部分从 SQL 基础 开始。