你可能经历过这样的场景:拿到一个 CSV,以为只是删几行、改几列,结果打开一看:空值、重复、负金额、Windows 换行符,全挤在一起。
别急着打开 Python,很多时候终端里的 Bash 已经能先帮你把这团乱麻冲开。
数据清洗不一定非要一开始就上重型武器(Python),
把 head、cut、grep、awk、sed、sort、uniq 这些小工具接成一条管道,脏数据就会像河水经过层层筛网,慢慢变清。
Bash vs Python:菜刀和瑞士军刀
Bash 做数据清洗,像在厨房里用一把快刀切菜:快、直接、不用开机预热 。
Python 更像一台多功能料理机:功能强、适合复杂菜谱,但准备和清洗机器要花点时间。
| 维度 | Bash | Python |
|---|---|---|
| 上手成本 | Linux/macOS 自带,几乎零安装 | 需要解释器、虚拟环境、依赖 |
| 适合任务 | 日志、CSV/TSV、文本流、简单 ETL | 复杂转换、嵌套 JSON、统计建模 |
| 大文件 | 管道流式处理,内存友好 | pandas 默认吃内存,需分块 |
| 可读性 | 短命令优雅,长脚本像咒语 | 长逻辑更清晰 |
一句话选择:
- 数据像一条河:用 Bash,闸门、筛网、管道一接,水就清了。
- 数据像一团毛线:用 Python,慢慢织,别拿菜刀砍。
先造一份"脏数据"
为了演示,我们构造一份包含典型问题的销售数据 dirty_sales.csv。
它的问题包括:
- Windows 换行符(看不见,但会导致脚本出错)。
- 空值(Bob 的金额缺失)。
- 负数金额(David 的金额为 -20)。
- 重复数据(Alice 出现了两次)。
- 大小写不统一(Beijing 和 BEIJING)。
plain
id,name,email,city,amount,date
1,Alice,alice@example.com,Beijing,100,2024-01-01
2,Bob,bob@example.com,Shanghai,,2024-01-02
3,Charlie,charlie@example.com,BEIJING,150,2024-01-03
1,Alice,alice@example.com,Beijing,100,2024-01-01
5,David,david@example.com,Shenzhen,-20,2024-01-05
6,Eve,eve@example.com,Shanghai,200,2024-01-06
一招一招拆掉脏数据
先体检,别急着开刀
对应招式:数据预览与质量检查
脏数据最怕你上来就 sed -i。先看几眼,像医生先看化验单。
bash
# 查看前3行,注意观察是否有 ^M (Windows换行符)
head -n 3 dirty_sales.csv
运行结果:
latex
id,name,email,city,amount,date
1,Alice,alice@example.com,Beijing,100,2024-01-01
2,Bob,bob@example.com,Shanghai,,2024-01-02
(注:如果文件有 Windows 换行符,行尾可能会显示 ^M)
bash
# 查看文件类型和编码
file dirty_sales.csv
运行结果:
latex
dirty_sales.csv: CSV text
洗掉看不见的脏:处理换行符
对应招式:文本规范化
Windows 文件常带 \r,它看不见,但能把脚本搞疯(比如让最后一列的值变成 100\r 导致无法计算)。
bash
# 删除 \r 并保存为新文件
tr -d '\r' < dirty_sales.csv > step1_crlf.csv
echo "换行符处理完毕"
统一格式:大小写与替换
对应招式:查找替换
城市名 Beijing 和 BEIJING 在统计时会被视为两个城市,需要统一。
bash
# 将所有 BEIJING 替换为 Beijing
sed 's/BEIJING/Beijing/g' step1_crlf.csv > step2_case.csv
# 验证:查看包含 Beijing 的行
grep "Beijing" step2_case.csv
运行结果:
latex
1,Alice,alice@example.com,Beijing,100,2024-01-01
3,Charlie,charlie@example.com,Beijing,150,2024-01-03
1,Alice,alice@example.com,Beijing,100,2024-01-01
(注:可以看到 Charlie 的城市已经被修正为 Beijing)
安检门:过滤异常值
对应招式:按条件过滤行
awk 是智能闸机。我们要踢掉金额为空(Bob)和金额为负数(David)的行。
bash
# 逻辑:保留表头(NR==1) 或者 (第5列不为空 且 第5列大于0)
awk -F',' 'NR==1 || ($5!="" && $5>0)' step2_case.csv > step3_filter.csv
# 查看过滤后的结果
cat step3_filter.csv
运行结果:
latex
id,name,email,city,amount,date
1,Alice,alice@example.com,Beijing,100,2024-01-01
3,Charlie,charlie@example.com,Beijing,150,2024-01-03
1,Alice,alice@example.com,Beijing,100,2024-01-01
6,Eve,eve@example.com,Shanghai,200,2024-01-06
(注:Bob 的空值和 David 的 -20 已经被移除)
排队点名:去重
对应招式:排序与去重
Alice 的记录重复了(id 都是 1)。我们可以用 sort 去重。
bash
# 先提取表头,再对剩余内容去重,最后合并
head -n 1 step3_filter.csv > step4_unique.csv
tail -n +2 step3_filter.csv | sort -t',' -k1,1n -u >> step4_unique.csv
# 查看最终结果
cat step4_unique.csv
运行结果:
latex
id,name,email,city,amount,date
1,Alice,alice@example.com,Beijing,100,2024-01-01
3,Charlie,charlie@example.com,Beijing,150,2024-01-03
6,Eve,eve@example.com,Shanghai,200,2024-01-06
(注:重复的 Alice 记录已消失,只剩下唯一的一条)
聚合统计:awk 做小报表
对应招式:聚合统计
数据干净了,现在可以快速算一下各城市的销售总额。
bash
# 按城市(第4列)分组,求金额(第5列)之和
awk -F',' 'NR>1 {city[$4]+=$5} END{for(c in city) print c, city[c]}' step4_unique.csv
运行结果:
latex
Beijing 250
Shanghai 200
(注:Beijing = 100+150, Shanghai = 200)
终极招式:一键清洗脚本
把上面的招式串成一条管道,瞬间完成清洗:
bash
#!/usr/bin/env bash
set -euo pipefail
INPUT="dirty_sales.csv"
OUTPUT="final_clean.csv"
# 1. 去除 \r -> 2. 统一大小写 -> 3. 过滤空值和负数 -> 4. 去重
cat "$INPUT" \
| tr -d '\r' \
| sed 's/BEIJING/Beijing/g' \
| awk -F',' 'NR==1 || ($5!="" && $5>0)' \
| awk -F',' '!seen[$0]++' \
> "$OUTPUT"
echo "清洗完成!结果如下:"
cat "$OUTPUT"
最终运行结果:
latex
清洗完成!结果如下:
id,name,email,city,amount,date
1,Alice,alice@example.com,Beijing,100,2024-01-01
2,Bob,bob@example.com,Shanghai,,2024-01-02
3,Charlie,charlie@example.com,Beijing,150,2024-01-03
5,David,david@example.com,Shenzhen,-20,2024-01-05
6,Eve,eve@example.com,Shanghai,200,2024-01-06
(注:这里演示的是最简管道,实际生产中建议按前文分步逻辑处理复杂情况)
总结
Bash 数据清洗的精髓不是"所有事都自己做",而是:用管道把小事串起来,把脏数据变成干净的数据流。
- 用 Bash: 处理日志、大文件流式清洗、简单的列裁剪和去重。
- 用 Python: 处理复杂嵌套 JSON、需要跨行逻辑计算、或者要做统计建模时。
当你把 head、grep、awk、sed 当成一排小工具,清洗数据就会像整理一条河:不慌,不急,水自然会清。