编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

    • Shell & Bash 从0到1实战专栏
    • Shell 入门与变量
    • 流程控制与函数
    • 数据与 IO 处理
    • grep 搜索实战
    • sed 与 awk 编程
    • 文件查找与统计
      • 6.1 find 基础查找
        • 6.1.1 按名称搜索 -name / -iname
        • 6.1.2 按类型搜索 -type
        • 6.1.3 按大小搜索 -size
        • 6.1.4 按时间搜索 -mtime / -atime / -ctime
        • 6.1.5 按权限/所有者/深度搜索
        • 6.1.6 逻辑运算组合条件
      • 6.2 find 执行动作
        • 6.2.1 -exec 对找到的每个文件执行命令
        • 6.2.2 xargs 批量处理——比 exec 更高效
        • 6.2.3 -print0 与 -0——处理空格与特殊字符
        • 6.2.4 -delete / -ok / -ls 快捷动作
        • 6.2.5 find 实战案例集锦
      • 6.3 sort 排序
        • 6.3.1 基础排序:字母/数值/逆序
        • 6.3.2 按列排序 -k 与分隔符 -t
        • 6.3.3 多列排序与稳定排序 -s
        • 6.3.4 人类可读排序 -h 与月份排序 -M
        • 6.3.5 去重 -u 与检查排序 -c
      • 6.4 uniq 去重与统计
        • 6.4.1 uniq 的工作原理——必须先排序
        • 6.4.2 -c 计数、-d 重复项、-u 唯一项
        • 6.4.3 实战:日志中的 Top N 统计
      • 6.5 cut / tr / paste 列处理
        • 6.5.1 cut——按字符/字节/分隔符切列
        • 6.5.2 tr——字符级转换与删除
        • 6.5.3 paste——文件横向合并
      • 6.6 wc / 综合实战 / 陷阱
        • 6.6.1 wc 行/词/字节统计
        • 6.6.2 四大工具管道组合实战
        • 6.6.3 新手陷阱 Top 5
        • 6.6.4 综合思考题
    • 日志监控与告警
    • 备份进程与磁盘
    • 用户与服务管理
    • 网络调度与部署
    • 调试与脚本规范
    • 安全与兼容处理
    • 性能与打包分发
  • 工具脚本

  • ScriptHub
  • Shell-Bash
杨充
2024-03-13
目录

文件查找与统计

# 第 6 章 文件查找与统计

# 目录介绍

  • 6.1 find 基础查找
    • 6.1.1 按名称搜索 -name / -iname
    • 6.1.2 按类型搜索 -type
    • 6.1.3 按大小搜索 -size
    • 6.1.4 按时间搜索 -mtime / -atime / -ctime
    • 6.1.5 按权限/所有者/深度搜索
    • 6.1.6 逻辑运算组合条件
  • 6.2 find 执行动作
    • 6.2.1 -exec 对找到的每个文件执行命令
    • 6.2.2 xargs 批量处理——比 exec 更高效
    • 6.2.3 -print0 与 -0——处理空格与特殊字符
    • 6.2.4 -delete / -ok / -ls 快捷动作
    • 6.2.5 find 实战案例集锦
  • 6.3 sort 排序
    • 6.3.1 基础排序:字母/数值/逆序
    • 6.3.2 按列排序 -k 与分隔符 -t
    • 6.3.3 多列排序与稳定排序 -s
    • 6.3.4 人类可读排序 -h 与月份排序 -M
    • 6.3.5 去重 -u 与检查排序 -c
  • 6.4 uniq 去重与统计
    • 6.4.1 uniq 的工作原理——必须先排序
    • 6.4.2 -c 计数、-d 重复项、-u 唯一项
    • 6.4.3 实战:日志中的 Top N 统计
  • 6.5 cut / tr / paste 列处理
    • 6.5.1 cut——按字符/字节/分隔符切列
    • 6.5.2 tr——字符级转换与删除
    • 6.5.3 paste——文件横向合并
  • 6.6 wc / 综合实战 / 陷阱
    • 6.6.1 wc 行/词/字/字节统计
    • 6.6.2 四大工具管道组合实战
    • 6.6.3 新手陷阱 Top 5
    • 6.6.4 综合思考题

# 6.1 find 基础查找

# 6.1.1 按名称搜索 -name / -iname

find 是 Linux 最强文件搜索命令——它真正遍历文件系统,不像 locate 依赖数据库:

# find 基本语法:find [路径] [表达式] [动作]
find . -name "*.log"                       # 当前目录下搜索所有 .log 文件
find /var/log -name "syslog*"              # 指定路径搜索

# ===== -name:区分大小写 =====
find . -name "README.md"                   # 精确匹配(大小写敏感)

# ===== -iname:忽略大小写 =====
find . -iname "readme.md"                  # 匹配 ReadMe.MD、README.md 等

# ===== 通配符(用引号括起,防止 Shell 展开)=====
find . -name "*.py"                        # 以 .py 结尾
find . -name "test*"                       # 以 test 开头
find . -name "file?.txt"                   # ? = 匹配单个字符
find . -name "report[0-9].log"             # 字符集:report0.log ~ report9.log

# ===== 路径匹配 -path(包含目录名)=====
find . -path "*/test/*.py"                 # 只在 test 子目录下找
find . -path "*/node_modules/*" -prune     # 跳过 node_modules

# ⚠️ 不带路径默认从当前目录 . 开始
find -name "*.txt"                         # 默认路径 = .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.1.2 按类型搜索 -type

#!/bin/bash

# ===== 常用类型 =====
find . -type f -name "*.sh"                # f = 普通文件
find . -type d -name "log"                 # d = 目录
find . -type l -name "*.so"                # l = 符号链接
find . -type s                             # s = socket 文件
find . -type p                             # p = 命名管道 FIFO

# ===== 实战:查找所有空目录 =====
find . -type d -empty                      # 空目录

# ===== 查找并列出所有符号链接及目标 =====
find . -type l -exec ls -l {} \;
# 或用 -ls 简写:
find . -type l -ls

# ===== 查找所有可执行脚本 =====
find . -type f -name "*.sh" -perm /111     # 有执行权限的 .sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

-type 速查表:

缩写 文件类型 说明
f 普通文件 最常用
d 目录 排除文件只找目录
l 符号链接 软链接
b 块设备 /dev/sda 等
c 字符设备 /dev/tty 等
s socket 套接字文件
p 命名管道 FIFO

# 6.1.3 按大小搜索 -size

#!/bin/bash

# ===== 大小单位 =====
# c = 字节,k = KB,M = MB,G = GB
# 前缀 +  = 大于,  -  = 小于,  不加 = 精确(近似)

find . -type f -size +100M                 # 大于 100MB
find . -type f -size -10k                  # 小于 10KB
find . -type f -size 0                     # 空文件(0 字节)
find . -type f -size +1G -size -5G         # 1GB~5GB 之间

# ===== 实战:找出大文件 =====
find /var/log -type f -size +50M \
    -exec ls -lh {} \; | sort -k5 -h -r | head -10
# 找 /var/log 下 >50M 的文件,按大小降序显示 Top 10

# ===== 查找并删除空文件 =====
find . -type f -empty -delete              # 删除所有空文件(-delete 是 find 内置)

# ===== 查找非空文件 =====
find . -type f ! -empty                    # ! 取反——非空
find . -type f -size +0                    # 等价写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.1.4 按时间搜索 -mtime / -atime / -ctime

#!/bin/bash

# ===== 三种时间戳 =====
# mtime = 修改时间(内容被修改)——最常用
# atime = 访问时间(文件被读取)
# ctime = 状态变更时间(权限/所有者/链接数等元数据改变)
# 单位:+n = n天前,-n = n天内,n  ≈ n 到 n+1 天前(有小数舍入)

find . -type f -mtime -7                    # 最近 7 天内修改过的文件
find . -type f -mtime +30                   # 30 天前修改过的文件
find . -type f -ctime -1                    # 最近 24 小时内状态变更的

# ===== -mmin / -amin / -cmin:分钟级精度 =====
find . -type f -mmin -60                    # 最近 60 分钟内修改的
find . -type f -mmin +1440                  # 超过 1440 分钟(24h)之前的

# ===== -newer:比某个文件更新/更旧 =====
find . -type f -newer reference.txt         # 比 reference.txt 更新的文件
find . -type f ! -newer reference.txt       # 比 reference.txt 更旧的文件

# ===== -newerXY:精确比较(X=访问/修改/创建,Y=参考文件)=====
find . -type f -newermt "2025-01-01"        # 2025-01-01 之后修改的
find . -type f -newermt "2025-01-01" ! -newermt "2025-06-01"
# 2025年1月1日到6月1日之间修改的

# ===== 实战:清理 N 天前的日志 =====
find /var/log -type f -name "*.log" -mtime +30 -delete
# 删除 30 天前的日志文件
find /var/log -type f -name "*.log" -mtime +7 -exec gzip {} \;
# 压缩 7 天前的日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 6.1.5 按权限/所有者/深度搜索

#!/bin/bash

# ===== 按权限 -perm =====
find . -type f -perm 644                    # 精确匹配权限 644
find . -type f -perm -u=x                   # 所有者有执行权限(- 前缀:至少满足)
find . -type f -perm /u=w,g=w               # 所有者或组有写权限(/ 前缀:任一满足)
find . -type f -perm /o=w                   # 其他用户有写权限(安全隐患!)

# ===== 按所有者 -user / -group =====
find . -type f -user root                   # 属于 root 的文件
find . -type f -group www-data              # 属于 www-data 组
find . -type f -nouser                      # 无主文件(用户已删除)

# ===== 按深度 -maxdepth / -mindepth =====
find . -maxdepth 1 -name "*.txt"            # 只搜索当前目录(不递归)
find . -maxdepth 2 -name "*.go"             # 最多搜索 2 层
find . -mindepth 2 -name "*.py"             # 最少 2 层深(跳过当前目录)
find . -mindepth 1 -maxdepth 3 -type f      # 1~3 层深度

# ===== 按 inode 号搜索 =====
ls -i some_file                             # 查看 inode 号
find . -inum 123456                         # 找硬链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.1.6 逻辑运算组合条件

#!/bin/bash

# ===== 默认 AND:多个条件并列 =====
find . -name "*.log" -size +1M              # .log 且 >1M

# ===== -o:OR(或)=====
find . \( -name "*.py" -o -name "*.go" \)   # .py 或 .go(括号要转义)
find . -name "*.py" -o -name "*.go"         # 省略括号也行(但优先级可能有问题)

# ===== ! :NOT(取反)=====
find . -type f ! -name "*.bak"              # 所有文件但排除 .bak
find . ! -type d                            # 非目录(= 普通文件 + 其他)

# ===== 带括号的复杂逻辑 =====
find . \( -name "*.log" -o -name "*.tmp" \) -size +10M
# 翻译:( .log 或 .tmp ) 且 >10M

find . \( -name "*.jpg" -o -name "*.png" \) ! -size +5M
# 翻译:( .jpg 或 .png ) 且 不大于 5M

# ===== 实战:清理项目中的临时文件 =====
find . \( \
    -name "*.pyc" -o \
    -name "*.pyo" -o \
    -name "__pycache__" -type d -o \
    -name "*.egg-info" -type d \
\) -exec rm -rf {} +
# 批量删除 Python 编译缓存和 egg 信息

# ===== -prune:排除特定目录 =====
find . -path ./node_modules -prune -o -name "*.js" -print
# 搜索 .js 但跳过 node_modules 目录
# -prune 的意思是"如果 -path 匹配,则剪掉不往下递归"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 6.2 find 执行动作

# 6.2.1 -exec 对找到的每个文件执行命令

#!/bin/bash

# ===== 基本格式(两种终止符)=====
# -exec command {} \;      一次处理一个文件(每文件执行一次 command)
# -exec command {} +       一次处理一批文件(把所有文件做参数传入一次 command)

# ===== \; 版本——每文件一个进程 =====
find . -name "*.log" -exec rm {} \;          # 每个文件开一次 rm(慢但安全)
find . -name "*.py" -exec chmod 644 {} \;    # 修改权限
find . -name "*.bak" -exec mv {} /tmp/ \;    # 移动到 /tmp

# ===== + 版本——批量传参(高效)=====
find . -name "*.log" -exec rm {} +          # 一批文件删一次(快)
# 等价于 rm file1.log file2.log file3.log ...(受 ARG_MAX 限制)

# ===== 实战:统计所有 .go 文件总行数 =====
find . -name "*.go" -exec cat {} + | wc -l
# 等价于:cat file1.go file2.go ... | wc -l

# ===== {} 占位符说明 =====
# {} = 当前找到的文件名(find 自动替换)
# \;  = 命令结束标记(必须转义,否则 Shell 会解释)
# +   = 批量模式结束标记

# ===== 实战:查找并打包 =====
find . -name "*.jpg" -mtime -7 -exec tar -cf recent_photos.tar {} +
# 把最近 7 天的 jpg 打包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 6.2.2 xargs 批量处理——比 exec 更高效

xargs 从标准输入读取数据,拼接成命令参数。配合 find 使用是经典组合:

#!/bin/bash

# ===== 为什么要用 xargs?=====
# 管道传递的是 stdout,而很多命令(rm/cp/mv)不接受管道输入
# xargs 把 stdout 转为命令行参数

# ❌ 错误:管道不能直接传给 rm
find . -name "*.bak" | rm                   # rm 不接受管道输入
# rm: missing operand

# ✅ 正确:用 xargs 转成参数
find . -name "*.bak" | xargs rm             # 把文件列表传给 rm

# ===== -I {}:自定义占位符——每次替换一个 =====
find . -name "*.sh" | xargs -I {} cp {} {}.bak
# 等价于 -exec cp {} {}.bak \;
# 将每个 .sh 文件复制为 .sh.bak

# ===== -n N:每批最多 N 个参数 =====
find . -name "*.log" | xargs -n 3 rm
# 每次传 3 个文件给 rm

# ===== -t:打印执行的命令(调试用)=====
find . -name "*.tmp" | xargs -t rm
# 输出:rm ./a.tmp ./b.tmp ./c.tmp  ← 让你看到实际执行了什么

# ===== -p:交互模式——每次确认 =====
find . -name "*.bak" | xargs -p rm
# rm ./a.bak?... y  ← 输入 y 才执行

# ===== -P N:并行执行 N 个进程 =====
find . -name "*.jpg" -size +1M | xargs -P 4 -I {} convert {} -resize 50% thumb_{}
# 用 4 个进程并行压缩图片

# ===== xargs 也可处理任意管道输入 =====
echo "file1.txt file2.txt file3.txt" | xargs ls -l
# 打印 3 个文件的信息

seq 1 10 | xargs -I NUM echo "Number: NUM"
# 输出:Number: 1 ... Number: 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 6.2.3 -print0 与 -0——处理空格与特殊字符

文件名包含空格、换行、引号时,find 与 xargs 的默认行为会出错:

#!/bin/bash

# ===== 问题:文件名含空格 =====
# 文件:a b.txt  c.txt(空格分隔的两个文件名)
# find 输出:./a b.txt\n./c.txt
# xargs 解析为:a, b.txt, c.txt ← 三个参数!
# 实际期望:a b.txt, c.txt ← 两个参数

# ===== 解决方案:-print0 + -0(NUL 字符分隔)=====
find . -name "*.txt" -print0 | xargs -0 rm
# find 用 \0 (NUL) 分隔 → xargs 用 -0 解析 NUL
# 这是「黄金组合」——绝对安全

# ===== 实战:查找所有 .mp4 并移动 =====
find /downloads -name "*.mp4" -print0 | xargs -0 -I {} mv {} /videos/

# ===== 查找含空格和特殊字符的文件 =====
find . -type f -name "* *"                  # 找文件名含空格的文件
find . -type f -print0 | xargs -0 ls -l     # 安全列出所有文件

# ===== 批量重命名(处理含空格文件名)=====
find . -name "* *" -print0 | while IFS= read -r -d '' file; do
    mv "$file" "${file// /_}"               # 空格替换为下划线
done
# while read -d '' 是处理 NUL 分隔的另一种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 6.2.4 -delete / -ok / -ls 快捷动作

#!/bin/bash

# ===== -delete:直接删除(比 -exec rm 更高效)=====
find . -name "*.tmp" -delete                # find 内置删除
# 优点:不需要 fork 新进程,不会因 ARG_MAX 失败
# ⚠️ 务必先不带 -delete 预览结果!

# ===== -ok:每次确认版 -exec =====
find . -name "*.bak" -ok rm {} \;
# 对每个文件提示:< rm ... ./a.bak > ?  ← 输入 y 确认

# ===== -ls:列出详细信息(类似 ls -l)=====
find . -type f -size +1M -ls
# 直接输出 ls -l 格式,比 -exec ls -l 更高效

# ===== -print:默认动作(通常省略也生效)=====
find . -name "*.log"                        # 默认 -print 打印结果
find . -name "*.log" -print                 # 显式写法,等价
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.2.5 find 实战案例集锦

#!/bin/bash

# ===== 案例 1:查找重复文件(按大小+md5)=====
find . -type f -size +0 -printf "%s %p\n" \
    | sort -n \
    | awk '{
        size=$1; file=$2
        if (size in seen && size == last_size) print "check:", file, seen[size]
        else seen[size]=file
        last_size=size
    }'

# ===== 案例 2:查找并统计各类型文件数量 =====
find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10
# 输出:Top 10 文件扩展名及数量
# 1234 js
#  567 ts
#  345 json

# ===== 案例 3:批量修改文件扩展名 =====
find . -name "*.jpeg" -print0 | while IFS= read -r -d '' file; do
    mv "$file" "${file%.jpeg}.jpg"
done

# ===== 案例 4:查找最近修改的配置文件 =====
find /etc -type f -name "*.conf" -mmin -60 2>/dev/null
# 2>/dev/null 忽略权限不够的错误信息

# ===== 案例 5:一键给目录下所有 .sh 加执行权限 =====
find . -type f -name "*.sh" -exec chmod +x {} +

# ===== 案例 6:清理构建产物 =====
find . -type d \( -name "node_modules" -o -name "dist" -o -name ".next" \) \
    -prune -exec rm -rf {} + 2>/dev/null
# -prune 配合 -exec:找到目录后,先剪枝(不递归进去),再删除目录本身

# ===== 案例 7:查找项目中未使用的图片(按引用搜索)=====
find . -name "*.png" -exec basename {} \; \
    | while read img; do
        grep -rq "$img" . --include="*.{js,ts,jsx,tsx,html}" || echo "Unused: $img"
    done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 6.3 sort 排序

# 6.3.1 基础排序:字母/数值/逆序

#!/bin/bash

# ===== sort 默认:按 ASCII 字母顺序 =====
cat words.txt | sort                          # A→Z 升序

# ===== -n:数值排序(否则 10 排在 2 前面)=====
echo -e "10\n2\n1\n20" | sort                # 输出:1 10 2 20(字符串排序)
echo -e "10\n2\n1\n20" | sort -n             # 输出:1 2 10 20(数值排序)

# ===== -r:逆序 reverse =====
echo -e "10\n2\n1\n20" | sort -nr            # 输出:20 10 2 1

# ===== -f:忽略大小写 =====
echo -e "Apple\napple\nBanana" | sort -f     # Apple apple Banana

# ===== -R:随机排序 =====
cat file.txt | sort -R                        # 随机打乱行

# ===== -b:忽略前导空白 =====
echo -e "  apple\n banana\ncat" | sort -b    # 忽略行首空格再排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 6.3.2 按列排序 -k 与分隔符 -t

#!/bin/bash

# ===== -k:指定排序的列(第 N 列)=====
# -k start[,end] 从 start 列排到 end 列(含)

sort -k2 data.txt                              # 按第 2 列排序(空格分隔)
sort -k2,2 data.txt                            # 严格只按第 2 列排

# ===== -t:指定分隔符 =====
sort -t ',' -k2 data.csv                       # 用逗号分隔,按第 2 列
sort -t ':' -k3 -n /etc/passwd                 # 按 UID(第 3 列)数值排序

# ===== 按列内的字符位置排 =====
# -k2.3 = 第 2 列的第 3 个字符开始
sort -k1.5 file.txt                            # 按第 1 列第 5 个字符开始

# ===== 排序选项可附加在 -k 之后 =====
sort -t ',' -k2nr,2 data.csv                   # 按第 2 列数值逆序
sort -t ':' -k3n,3 -k1,1 /etc/passwd          # 先按 UID 数值,再按用户名

# ===== 实战:按文件大小排序 ls 输出 =====
ls -l | sort -k5 -n -r | head -5              # 最大的 5 个文件
ls -l | sort -k5,5 -n -r                       # 严格按第 5 列(文件大小)

# ===== 按月份排序(第4列是 "Jan" "Feb"...)=====
ls -l | sort -k6M                             # 按月份排序(-M = 月份排序)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 6.3.3 多列排序与稳定排序 -s

#!/bin/bash

# ===== 多列排序:多个 -k 选项,优先级从左到右 =====
# 先按第 2 列排,再按第 1 列排
sort -k2,2 -k1,1 data.txt

# ===== 实例:成绩单排序(先按科目,同科目内按分数降序)=====
cat scores.txt                                # 格式:张三  数学  92
sort -k2,2 -k3,3nr scores.txt
# 先按科目(第2列)字母序,同科目按分数(第3列)数值降序

# ===== -u:去重(去掉完全相同的行)=====
sort -u file.txt                               # 排序 + 去重

# ===== -s:稳定排序(stable)=====
# 保证相等元素的相对顺序不变
sort -s -k2 data.txt                          # 相同 key 时保持原始顺序

# ===== 组合:按第二列排序,相同第二列时保持第一列原有顺序 =====
sort -s -k2,2 data.txt

# ===== 实战:统计访问日志的请求方法 =====
awk '{print $6}' access.log | sort | uniq -c | sort -rn
# sort | uniq -c | sort -rn = 统计 + 计数 + 降序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 6.3.4 人类可读排序 -h 与月份排序 -M

#!/bin/bash

# ===== -h:人类可读大小排序(K/M/G)=====
du -h /home | sort -hr | head -10
# du 输出 "1.2G\t/path",sort -h 能正确比较 1.2G > 500M

# 不用 -h 的悲剧:
echo -e "1K\n1G\n1M\n10K" | sort -n           # 靠第一个数字排 = 乱排
echo -e "1K\n1G\n1M\n10K" | sort -h           # 正确:1K 10K 1M 1G

# ===== -V:版本号排序 =====
echo -e "v1.10.1\nv1.2.1\nv1.10.0" | sort -V
# 输出:v1.2.1 v1.10.0 v1.10.1(版本号语义排序)
# 普通排序:v1.10.0 v1.10.1 v1.2.1 ← 错误!

# ===== -M:月份排序(Jan/Feb/Mar...)=====
echo -e "Dec\nJan\nFeb" | sort -M             # Jan Feb Dec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6.3.5 去重 -u 与检查排序 -c

#!/bin/bash

# ===== -u:输出去重后的行 =====
sort -u file.txt                               # 排序后去掉重复行

# 对比 uniq:sort -u 在排序阶段就去重(比 sort|uniq 略快)
# 但如果需要统计重复次数(uniq -c),还是要用 uniq

# ===== -c:检查是否已排序 =====
sort -c file.txt                            # 已排序则无输出,未排序则报错
sort -c -n numbers.txt                        # 检查数值排序

# ===== 实战:检查文件是否已按时间排序 =====
sort -c -t '[' -k2 app.log                    # 检查日志是否按时间戳排序

# ===== -o:输出到文件(可覆盖自身)=====
sort file.txt -o sorted.txt                  # 输出到新文件
sort file.txt -o file.txt                    # ⚠️ 直接覆盖自身(sort 允许)
# 对比:sort file.txt > file.txt 会清空 file.txt!(先重定向再读)
# 所以要用 sort 的 -o 选项来覆盖自身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 6.4 uniq 去重与统计

# 6.4.1 uniq 的工作原理——必须先排序

#!/bin/bash

# ===== uniq 只合并「相邻」的重复行!=====
echo -e "a\na\nb\na\na" | uniq
# 输出:a b a              ← 第三个 a 没被去掉!因为它跟 b 不连续

# ✅ 正确用法:先 sort 再 uniq
echo -e "a\na\nb\na\na" | sort | uniq
# 输出:a b                ← 完全去重

# ===== 这是最常见的组合 =====
some_command | sort | uniq           # 排序 + 去重
# ≈ sort -u,但 sort -u 不能配合 -c/-d 等 uni 标志
1
2
3
4
5
6
7
8
9
10
11
12
13

为什么一定要 sort?直观理解:

原始:a a b a a
      ↓ sort
      a a a a b
      ↓ uniq
      a b
1
2
3
4
5

# 6.4.2 -c 计数、-d 重复项、-u 唯一项

#!/bin/bash

# ===== -c:每行前面加出现次数 =====
echo -e "a\na\nb\na\na" | sort | uniq -c
# 输出:
#   4 a
#   1 b

# ===== -d:只显示重复出现的行(出现 >= 2 次)=====
echo -e "a\na\nb\na\na\nc" | sort | uniq -d
# 输出:a

# ===== -D:显示所有重复行(每行都打印)=====
echo -e "a\na\nb\na\na" | sort | uniq -D
# 输出:a a a a

# ===== -u:只显示唯一出现的行(只出现 1 次)=====
echo -e "a\na\nb\na\na\nc" | sort | uniq -u
# 输出:b c

# ===== -i:忽略大小写 =====
echo -e "APPLE\napple\nBanana" | sort | uniq -ci
# 输出:2 APPLE  1 Banana

# ===== -w N:只比较前 N 个字符 =====
echo -e "error:001\nerror:002\ninfo:003" | sort | uniq -w5 -c
# 输出:2 error:001  1 info:003(只比较前 5 个字符)

# ===== -s N:跳过前 N 个字符 =====
echo -e "[ERR] timeout\n[ERR] retry\n[INFO] ok" | sort | uniq -s5 -c
# 跳过 "[ERR]" (5字符) 后再比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 6.4.3 实战:日志中的 Top N 统计

#!/bin/bash

# ===== 场景 1:访问量 Top 10 的 IP =====
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 管道拆解:
#   awk '{print $1}'       → 取 IP 列
#   sort                   → 排序(uniq 前置条件)
#   uniq -c                → 统计每个 IP 出现次数
#   sort -rn               → 按次数降序
#   head -10               → 取前 10

# ===== 场景 2:被访问 Top 10 的 URL =====
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -10

# ===== 场景 3:404 最多的 URL =====
awk '$9 == 404 {print $7}' access.log | sort | uniq -c | sort -rn | head -10

# ===== 场景 4:统计 HTTP 状态码分布 =====
awk '{print $9}' access.log | sort | uniq -c | sort -rn

# ===== 场景 5:找出只出现过一次的 IP(异常访问)=====
awk '{print $1}' access.log | sort | uniq -u

# ===== 场景 6:重复登录的用户 =====
awk '/login success/ {print $3}' auth.log | sort | uniq -d

# ===== 整个管道记忆口诀 =====
# 提取 → 排序 → 统计 → 再排序 → 截取
# awk  → sort → uniq -c → sort -rn → head
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 6.5 cut / tr / paste 列处理

# 6.5.1 cut——按字符/字节/分隔符切列

#!/bin/bash

# ===== -c:按字符位置切 =====
echo "ABCDEFG" | cut -c3                    # C(第 3 个字符)
echo "ABCDEFG" | cut -c3-5                  # CDE(第 3~5 个)
echo "ABCDEFG" | cut -c3-                   # CDEFG(从第 3 到末尾)
echo "ABCDEFG" | cut -c-3                   # ABC(开头到第 3 个)

# ===== -d + -f:按分隔符切字段 =====
echo "apple,banana,orange" | cut -d ',' -f2      # banana(第 2 字段)
echo "apple,banana,orange" | cut -d ',' -f1,3    # apple,orange
echo "apple,banana,orange" | cut -d ',' -f1-2    # apple,banana
echo "apple,banana,orange" | cut -d ',' -f2-     # banana,orange

# ===== 实战:提取 passwd 的用户名和 Shell =====
cut -d ':' -f1,7 /etc/passwd
# 输出:root:/bin/bash  nobody:/usr/sbin/nologin ...

# ===== 补充选择(不被分隔符包含时区补空)=====
echo "a" | cut -d ',' -f1,3 --complement      # 不选字段 1 和 3
# --complement = 取反选择

# ===== 提取 csv 指定列 =====
cut -d ',' -f1,3,5 data.csv | head -5        # 提取第 1/3/5 列

# ===== cut vs awk 选取列对比 =====
cut -d ':' -f1,7 /etc/passwd                 # 简单列提取(快)
awk -F ':' '{print $1, $7}' /etc/passwd       # 复杂处理(灵活,可加条件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 6.5.2 tr——字符级转换与删除

#!/bin/bash

# ===== tr 是从 stdin 到 stdout 的字符映射(不接受文件名参数)=====

# ===== 替换/转换:tr SET1 SET2 —— 一一对应 =====
echo "abc" | tr 'a' 'X'                     # Xbc
echo "abc" | tr 'abc' 'XYZ'                 # XYZ(a→X, b→Y, c→Z)
echo "hello world" | tr 'a-z' 'A-Z'         # HELLO WORLD(大小写转换)
echo "HELLO" | tr 'A-Z' 'a-z'               # hello

# ===== -d:删除字符 =====
echo "abc123def456" | tr -d '0-9'            # abcdef(删除所有数字)
echo "a b c" | tr -d ' '                     # abc(删除空格)
cat file.txt | tr -d '\r'                    # 删除 Windows 的回车符 \r

# ===== -s:压缩重复字符(squeeze)=====
echo "a    b     c" | tr -s ' '              # a b c(多个空格压缩为一个)
echo "aaabbbccc" | tr -s 'ab'                # abccc(a 和 b 分别压缩)
echo -e "a\n\n\nb\n\nc" | tr -s '\n'         # 压缩空行

# ===== -c(-C):取反(补集)=====
echo "abc123" | tr -cd '0-9'                 # 123(删除非数字)
echo "abc123" | tr -c '0-9' 'X'             # X X X 1 2 3(把非数字替换成 X)
echo "hello 123" | tr -cd 'a-z'              # hello(只保留字母)

# ===== 字符类 =====
echo "hello" | tr '[:lower:]' '[:upper:]'    # HELLO
cat file.txt | tr -d '[:space:]'             # 删除所有空白字符
cat file.txt | tr '[:punct:]' '_'            # 所有标点变下划线

# ===== 实战:Windows 换行 → Unix 换行 =====
tr -d '\r' < windows.txt > unix.txt
# 或用 sed:sed -i 's/\r$//' file.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

tr 常用转义与字符类速查:

表示法 含义
\n 换行
\t Tab
\r 回车
\\ 反斜杠
[:alnum:] 字母 + 数字
[:alpha:] 字母
[:digit:] 数字 0-9
[:lower:] / [:upper:] 小写/大写字母
[:space:] 空格+Tab+换行+回车
[:punct:] 标点符号

# 6.5.3 paste——文件横向合并

#!/bin/bash

# ===== paste 把多个文件横向拼接(默认用 Tab 分隔)=====
# 文件1               文件2                  paste 结果
# a                   x                     a\tx
# b                   y                     b\ty
# c                   z                     c\tz

paste file1.txt file2.txt                    # 横向拼接

# ===== -d:指定分隔符 =====
paste -d ',' names.txt scores.txt            # 用逗号拼接
paste -d '|' col1.txt col2.txt col3.txt     # 用竖线拼接
paste -d '\n' file1.txt file2.txt           # 交替行(用换行拼接)
# 输出:a x b y c z ← 交替排列

# ===== -s:按行拼接(serial)=====
# 把文件的所有行串成一行(用 Tab 分隔)
paste -s file.txt
# 输出:line1\tline2\tline3  ← 一行

# 多文件 -s:把每个文件变成一行
paste -s file1.txt file2.txt
# 输出:
# file1_line1\tfile1_line2\tfile1_line3
# file2_line1\tfile2_line2

# ===== 实战:生成 CSV =====
paste -d ',' names.txt ages.txt cities.txt > users.csv
# names.txt  ages.txt  cities.txt
# Alice      25        Beijing
# Bob        30        Shanghai   → users.csv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 6.6 wc / 综合实战 / 陷阱

# 6.6.1 wc 行/词/字节统计

#!/bin/bash

# ===== wc 默认输出:行数 词数 字节数 文件名 =====
wc file.txt
# 输出:15 120 850 file.txt
#      行  词  字节

# ===== 单项统计 =====
wc -l file.txt                               # 只统计行数 (lines)
wc -w file.txt                               # 只统计词数 (words)
wc -c file.txt                               # 只统计字节数 (bytes)
wc -m file.txt                               # 只统计字符数 (characters,含多字节)

# ⚠️ -c vs -m:对中文等多字节字符有差异
echo "你好" | wc -c                          # 7 (UTF-8: 2×3字节 + 换行 = 7)
echo "你好" | wc -m                          # 3 (2个字符 + 换行 = 3)

# ===== 统计多个文件 =====
wc -l *.md
# 输出每文件行数 + 总计

# ===== 实战:统计项目代码行数 =====
find . -name "*.py" | xargs wc -l | tail -1  # 总行数
# 或:
find . -name "*.py" -exec cat {} + | wc -l

# 按文件类型分别统计:
find . -name "*.py" | xargs wc -l | sort -rn | head -10
# 按行数排 Top 10 的 .py 文件

# ===== 统计目录下文件数量 =====
find . -type f | wc -l                       # 文件数(包含子目录)
ls -1 | wc -l                                # 当前目录文件数(不含子目录)
find . -type d | wc -l                       # 目录数

# ===== 统计进程数 =====
ps aux | wc -l                               # 总进程数(含表头)
ps aux --no-headers | wc -l                  # 去掉表头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 6.6.2 四大工具管道组合实战

#!/bin/bash

# ============================================
# 案例 1:日志文件中 Top 10 出现最多的关键词
# ============================================
grep -oP '\b\w{4,}\b' app.log | sort | uniq -c | sort -rn | head -10
# 拆解:grep -oP 提取所有长度>=4 的单词 → sort → uniq -c → sort -rn → head

# ============================================
# 案例 2:统计项目中各编程语言代码行数
# ============================================
find . -type f \( -name "*.py" -o -name "*.sh" -o -name "*.js" \) \
    -exec wc -l {} + | awk '{
        if (NF==1) total=$1      # 行数
        else {
            # 按扩展名分组累加
            ext=tolower($2); gsub(/.*\./, "", ext)
            lines[ext] += $1
        }
    } END {
        for (ext in lines) printf "%-10s %8d lines\n", ext, lines[ext]
    }' | sort -k2 -rn

# ============================================
# 案例 3:分析 GitHub 提交记录
# ============================================
git log --format="%an" | sort | uniq -c | sort -rn | head -10
# %an = author name
# 输出:Top 10 提交者

git log --format="%ad" --date=format:"%Y-%m" | sort | uniq -c
# 每个月提交次数统计

# ============================================
# 案例 4:查找占用空间最大的目录
# ============================================
du -sh */ | sort -hr | head -10
# du -sh */ = 每个子目录的大小(human readable)
# sort -hr   = 按人类可读大小逆序排列

# ============================================
# 案例 5:文本数据预处理管道
# ============================================
# 功能:提取 csv 第 2 列 → 去空格 → 去重 → 统计 → 排序 → Top 5
cut -d ',' -f2 data.csv | tr -d ' ' | sort | uniq -c | sort -rn | head -5

# ============================================
# 案例 6:监控文件变化(find + 比较)
# ============================================
# 找出 1 小时内修改过的文件
find /var/www -type f -mmin -60 | xargs ls -lh | awk '{print $9, $5, $6, $7}'
# 输出:文件名  大小  日期  时间

# 生成需要备份的文件列表:
find /data -type f -mtime -1 | xargs -I {} cp {} /backup/daily/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 6.6.3 新手陷阱 Top 5

陷阱 1:find 路径顺序影响性能

# ❌ 先找所有文件再过滤名称——遍历所有文件
find . -type f -name "*.log"

# ✅ find 会优化,但养成好习惯:最严格的过滤放前面
find . -name "*.log" -type f
# 某些 find 版本(尤其是带 -O 优化级别)会自动重排条件
1
2
3
4
5
6

陷阱 2:sort | uniq 是必须的,不可省略 sort

# ❌ 没排序直接 uniq——只去相邻重复,去不干净
cat data.txt | uniq -c

# ✅ 永远先 sort
cat data.txt | sort | uniq -c
# 记住:uniq 前必须 sort(或数据本身就是有序的)
1
2
3
4
5
6

陷阱 3:xargs 遇到空格/引号会出错

# ❌ 文件名含空格时会解析错误
find . -name "*.txt" | xargs rm

# ✅ 黄金组合:-print0 + -0
find . -name "*.txt" -print0 | xargs -0 rm
1
2
3
4
5

陷阱 4:cut 不区分连续分隔符

# cut 把连续的多个相同分隔符合并为一个!
echo "a,,b" | cut -d ',' -f2               # 输出:空(想取空字段)
echo "a,,b" | cut -d ',' -f3               # 输出:b(count 跳到了第 3 位)

# ✅ awk 更精确
echo "a,,b" | awk -F ',' '{print $2}'       # 输出:空字符串
echo "a,,b" | awk -F ',' '{print $3}'       # 输出:b
1
2
3
4
5
6
7

陷阱 5:sort file > file 会清空文件

# ❌ Shell 先截断 file(重定向),再执行 sort——此时 file 已空
sort file.txt > file.txt                    # file.txt 变成空文件!

# ✅ 用 -o 选项覆盖自身
sort file.txt -o file.txt                   # sort 内置的输出到文件
1
2
3
4
5

# 6.6.4 综合思考题

  1. find + xargs 管道组合:如何找到最近 7 天修改过的、大于 10MB 的普通文件,并安全删除它们?
  2. 日志分析一条龙:从 Nginx 访问日志中统计"每个小时"的请求量和流量大小(提示:cut 时间字段 + awk 分组累加)。
  3. sort 多列排序:有一个 csv 文件 学生,科目,分数,请写出按科目分组、组内按分数降序排列的命令。
  4. tr 实战:如何把 Windows 风格的文本文件(\r\n 换行)转为 Unix 风格(\n),同时把所有 Tab 转为 4 个空格?
  5. uniq 陷阱:以下命令的输出是什么?
    echo -e "apple\nApple\norange\nAPPLE" | sort -f | uniq -i -c
    
    1
  6. 综合管道:统计当前目录下所有 .md 文件中出现频率最高的 20 个单词(忽略大小写,忽略标点)。
#Shell#文本
上次更新: 2026/06/17, 12:47:39
sed 与 awk 编程
日志监控与告警

← sed 与 awk 编程 日志监控与告警→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式