跳到主要内容

文本处理

Linux 系统提供了强大的文本处理工具链,可以高效地搜索、过滤、转换和分析文本数据。这些工具是系统管理、日志分析和数据处理的核心技能。

工具概览

Linux 文本处理工具可以分为几类:

工具类型核心功能
grep搜索过滤按模式搜索文本行
sed流编辑器文本替换、删除、插入
awk数据处理字段处理、格式化输出、计算
sort排序按各种条件排序文本行
uniq去重去除重复行
cut列提取按字段或字符提取内容
tr字符转换字符替换、删除、压缩
wc统计统计行数、字数、字节数

工具协作理念:Linux 的文本处理工具遵循"做好一件事"的 Unix 哲学。每个工具专注于单一功能,通过管道(|)组合使用,可以完成复杂的文本处理任务。

grep - 文本搜索

grep(Global Regular Expression Print)是最常用的文本搜索工具,用于在文件中查找匹配指定模式的行。

基本工作原理

grep 逐行读取输入,将每一行与给定的模式进行匹配,如果匹配成功则输出该行。它支持三种正则表达式语法:

  • BRE(Basic Regular Expression):基本正则表达式,默认模式
  • ERE(Extended Regular Expression):扩展正则表达式,使用 -E 选项
  • PCRE(Perl Compatible Regular Expression):Perl 兼容正则表达式,使用 -P 选项

在 GNU grep 中,BRE 和 ERE 功能相同,只是语法略有差异。ERE 语法更简洁,推荐使用。

基本用法

# 在文件中搜索字符串
grep "error" /var/log/syslog

# 搜索多个文件
grep "error" file1.txt file2.txt

# 递归搜索目录
grep -r "function" ./src/

# 从标准输入搜索
cat file.txt | grep "pattern"

# 使用扩展正则表达式
grep -E "pattern1|pattern2" file.txt

解释

  • grep 默认输出匹配的整行内容
  • 当搜索多个文件时,输出会包含文件名前缀
  • -r 选项会递归搜索子目录中的所有文件

常用选项

匹配控制

# -i:忽略大小写
grep -i "ERROR" file.txt # 匹配 error, ERROR, Error 等

# -v:反向匹配,显示不匹配的行
grep -v "^#" config.conf # 排除注释行

# -w:匹配整个单词
grep -w "log" file.txt # 匹配 "log" 但不匹配 "logging" 或 "catalog"

# -x:匹配整行
grep -x "exact line" file.txt # 只匹配完全相同的整行

# -F:固定字符串模式(不解析正则表达式)
grep -F "a*b" file.txt # 字面匹配 "a*b",而不是正则表达式

# -e:指定多个模式
grep -e "error" -e "warning" -e "critical" file.txt

# -f:从文件读取模式
grep -f patterns.txt file.txt # patterns.txt 每行一个搜索模式

使用场景

  • -i 在搜索配置文件或日志时很有用,因为大小写可能不一致
  • -v 常用于过滤掉不需要的内容,如注释行或空白行
  • -w 可以避免部分匹配,比如搜索 "cat" 时不匹配 "category"
  • -F 在搜索包含正则元字符的字符串时使用,避免转义

输出控制

# -c:只输出匹配行数
grep -c "error" file.txt # 输出:15(匹配了 15 行)

# -l:只输出匹配的文件名
grep -l "TODO" *.py # 列出包含 TODO 的 Python 文件

# -L:只输出不匹配的文件名
grep -L "copyright" *.py # 列出不包含 copyright 的文件

# -n:显示行号
grep -n "error" file.txt # 输出:42:error message here

# -o:只输出匹配的部分
grep -o "[0-9]\+" file.txt # 只输出数字部分

# -q:静默模式(不输出,只返回状态码)
if grep -q "pattern" file.txt; then
echo "找到匹配"
fi

# -s:抑制错误信息
grep -s "pattern" nonexistent.txt # 不输出"文件不存在"的错误

解释

  • -c 常用于统计,如统计日志中的错误数量
  • -l-L 适合批量处理文件时筛选
  • -n 帮助定位匹配行在文件中的位置
  • -q 主要用于脚本中的条件判断

上下文显示

# -A n:显示匹配行及其后 n 行
grep -A 3 "error" file.txt # 显示匹配行和后面 3 行

# -B n:显示匹配行及其前 n 行
grep -B 2 "error" file.txt # 显示匹配行和前面 2 行

# -C n:显示匹配行及其前后各 n 行
grep -C 5 "error" file.txt # 显示匹配行和前后各 5 行

# 组合使用
grep -B 2 -A 3 "error" file.txt # 前面 2 行,后面 3 行

使用场景:查看日志时,错误信息通常与上下文相关,使用上下文选项可以帮助理解错误的完整情况。

搜索范围控制

# -r:递归搜索目录
grep -r "pattern" /path/to/dir/

# -R:递归搜索,跟随符号链接
grep -R "pattern" /path/to/dir/

# --include:只搜索特定文件
grep -r --include="*.py" "import" ./src/

# --exclude:排除特定文件
grep -r --exclude="*.log" "error" /var/log/

# --exclude-dir:排除目录
grep -r --exclude-dir=".git" "TODO" ./

# --include-from:从文件读取包含规则
grep -r --include-from=include.txt "pattern" ./

正则表达式

正则表达式是描述文本模式的形式语言。grep 支持丰富的正则表达式语法。

基本元字符

# . 匹配任意单个字符(除换行符)
grep "a.c" file.txt # 匹配 abc, a1c, a@c 等

# * 匹配前一个字符 0 次或多次
grep "ab*c" file.txt # 匹配 ac, abc, abbc, abbbc 等

# + 匹配前一个字符 1 次或多次(需要 -E)
grep -E "ab+c" file.txt # 匹配 abc, abbc, abbbc 等(不匹配 ac)

# ? 匹配前一个字符 0 次或 1 次(需要 -E)
grep -E "colou?r" file.txt # 匹配 color 或 colour

# {n} 匹配前一个字符恰好 n 次(需要 -E)
grep -E "a{3}" file.txt # 匹配 aaa

# {n,m} 匹配前一个字符 n 到 m 次(需要 -E)
grep -E "a{2,4}" file.txt # 匹配 aa, aaa, aaaa

# {n,} 匹配前一个字符至少 n 次(需要 -E)
grep -E "a{2,}" file.txt # 匹配 aa, aaa, aaaa, ...

解释

  • 在基本正则表达式(BRE)中,+?{} 需要转义:\+\?\{n\}
  • 使用 -E 选项启用扩展正则表达式,可以直接使用这些元字符
  • * 在正则表达式中的含义与 shell 通配符不同,它表示"前一个字符的重复"而不是"任意字符"

字符类

# [...] 匹配方括号中的任意一个字符
grep "[aeiou]" file.txt # 匹配任意元音字母
grep "[0-9]" file.txt # 匹配任意数字
grep "[a-zA-Z]" file.txt # 匹配任意字母

# [^...] 匹配不在方括号中的任意字符
grep "[^0-9]" file.txt # 匹配任意非数字字符

# 预定义字符类
grep "[[:digit:]]" file.txt # 匹配数字 [0-9]
grep "[[:alpha:]]" file.txt # 匹配字母 [a-zA-Z]
grep "[[:alnum:]]" file.txt # 匹配字母和数字 [a-zA-Z0-9]
grep "[[:space:]]" file.txt # 匹配空白字符
grep "[[:upper:]]" file.txt # 匹配大写字母
grep "[[:lower:]]" file.txt # 匹配小写字母
grep "[[:punct:]]" file.txt # 匹配标点符号

解释:预定义字符类如 [[:digit:]][0-9] 更具可移植性,在不同字符编码的系统上表现一致。

位置锚点

# ^ 匹配行首
grep "^error" file.txt # 匹配以 error 开头的行
grep "^#" file.txt # 匹配以 # 开头的行(注释行)

# $ 匹配行尾
grep "error$" file.txt # 匹配以 error 结尾的行
grep "^$" file.txt # 匹配空行

# \< 匹配单词开头
grep "\<log" file.txt # 匹配 log, logging 但不匹配 catalog

# \> 匹配单词结尾
grep "log\>" file.txt # 匹配 log, catalog 但不匹配 logging

# \b 匹配单词边界
grep -E "\blog\b" file.txt # 精确匹配单词 log

解释:锚点本身不匹配任何字符,它们只匹配位置。这在精确匹配时非常有用。

分组和引用

# (...) 分组(需要 -E)
grep -E "(ab)+" file.txt # 匹配 ab, abab, ababab 等

# \1, \2, ... 引用前面的分组
grep -E "(.)(.)\2\1" file.txt # 匹配 abba, 1221, xyxy 等回文模式

# | 或运算(需要 -E)
grep -E "cat|dog" file.txt # 匹配 cat 或 dog
grep -E "(error|warning|critical)" file.txt

解释:分组将多个字符视为一个整体,可以进行重复或引用。反向引用可以匹配前面分组捕获的内容,常用于检测重复模式。

实用案例

日志分析

# 统计日志中的错误数量
grep -c "ERROR" /var/log/app.log

# 查找包含特定日期的错误
grep "2024-03-15.*ERROR" /var/log/app.log

# 查找特定 IP 的访问记录
grep "192.168.1.100" /var/log/nginx/access.log

# 提取日志中的所有 IP 地址
grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log | sort | uniq -c | sort -nr | head -10

# 查找错误并显示上下文
grep -C 10 "FATAL" /var/log/app.log

代码搜索

# 查找所有 TODO 注释
grep -rn "TODO" ./src/

# 查找函数定义
grep -rn "function\s\+\w\+" ./src/

# 查找未使用的变量(示例)
grep -rn "var\s\+\w\+\s*=" ./src/ | grep -v "used"

# 排除测试文件搜索
grep -r --exclude="*test*" "class User" ./src/

# 只搜索 Python 文件
grep -r --include="*.py" "import requests" ./

配置文件处理

# 排除注释和空行查看配置
grep -v "^#" /etc/nginx/nginx.conf | grep -v "^$"

# 查找配置项
grep "server_name" /etc/nginx/sites-enabled/*

# 查找被注释掉的配置
grep "^\s*#" /etc/ssh/sshd_config

sed - 流编辑器

sed(Stream Editor)是一个强大的流编辑器,用于对文本进行过滤和转换。它逐行处理输入,可以执行替换、删除、插入等操作。

工作原理

sed 的工作流程如下:

  • 模式空间(Pattern Space):当前处理的行
  • 保持空间(Hold Space):临时存储区域,用于高级操作
  • 循环处理:每读取一行,执行所有命令,然后输出结果

基本语法

sed [选项] '命令' 文件
sed [选项] -f 脚本文件 文件

常用选项

# -n:静默模式,不自动输出
sed -n '5p' file.txt # 只输出第 5 行

# -e:指定多个命令
sed -e 's/foo/bar/' -e 's/baz/qux/' file.txt

# -f:从文件读取命令
sed -f script.sed file.txt

# -i:直接修改文件
sed -i 's/old/new/' file.txt

# -i.bak:修改前备份
sed -i.bak 's/old/new/' file.txt

# -E:使用扩展正则表达式
sed -E 's/[0-9]+/NUM/' file.txt

# -r:同 -E(兼容旧版本)
sed -r 's/[0-9]+/NUM/' file.txt

警告-i 选项会直接修改原文件,建议先测试命令正确性,或使用 -i.bak 创建备份。

地址定位

sed 命令可以指定作用的行范围:

# 无地址:作用于所有行
sed 's/old/new/' file.txt

# 数字地址:指定行号
sed '5s/old/new/' file.txt # 只处理第 5 行
sed '5,10s/old/new/' file.txt # 处理第 5 到 10 行
sed '$s/old/new/' file.txt # 处理最后一行

# 正则表达式地址
sed '/error/s/old/new/' file.txt # 只处理包含 error 的行
sed '/^#/d' file.txt # 删除以 # 开头的行

# 范围地址
sed '/start/,/end/s/old/new/' file.txt # 从 start 行到 end 行之间

# 步长地址
sed '1~2s/old/new/' file.txt # 处理第 1, 3, 5, 7... 行(奇数行)
sed '2~2s/old/new/' file.txt # 处理第 2, 4, 6, 8... 行(偶数行)

# 反向匹配
sed '/pattern/!s/old/new/' file.txt # 不匹配 pattern 的行才替换

替换命令(s)

替换命令是 sed 最常用的功能:

# 基本替换
sed 's/old/new/' file.txt # 每行替换第一个匹配

# 全局替换
sed 's/old/new/g' file.txt # 替换所有匹配

# 替换第 n 个匹配
sed 's/old/new/2' file.txt # 只替换每行的第 2 个匹配

# 忽略大小写
sed 's/old/new/gi' file.txt # i 标志忽略大小写

# 只打印修改的行
sed -n 's/old/new/p' file.txt # 只输出发生替换的行

# 写入文件
sed 's/old/new/gw output.txt' file.txt # 将修改写入文件

# 使用不同的分隔符
sed 's|/usr/local|/usr|g' file.txt # 当模式包含 / 时,使用其他分隔符
sed 's#old#new#g' file.txt

替换中的特殊字符

# & 代表匹配到的整个字符串
sed 's/[0-9]\+/[&]/g' file.txt # 将数字用方括号括起来
# 输入:hello 123 world
# 输出:hello [123] world

# \1, \2, ... 代表分组捕获的内容
sed -E 's/([a-z]+) ([a-z]+)/\2 \1/' file.txt # 交换两个单词
# 输入:hello world
# 输出:world hello

# \u 转大写,\l 转小写
sed 's/\b[a-z]/\u&/g' file.txt # 首字母大写

# \U 到 \E 之间转大写
sed 's/\b[a-z]\+/\U&\E/g' file.txt # 单词全部大写

删除命令(d)

# 删除特定行
sed '5d' file.txt # 删除第 5 行
sed '5,10d' file.txt # 删除第 5 到 10 行
sed '$d' file.txt # 删除最后一行

# 删除匹配行
sed '/^$/d' file.txt # 删除空行
sed '/^#/d' file.txt # 删除注释行
sed '/DEBUG/d' file.txt # 删除包含 DEBUG 的行

# 删除不匹配的行
sed '/important/!d' file.txt # 只保留包含 important 的行

# 删除到文件末尾
sed '/pattern/,$d' file.txt # 删除从匹配行到末尾

插入和追加命令(i/a)

# i 在行前插入
sed '5i\new line' file.txt # 在第 5 行前插入
sed '/pattern/i\new line' file.txt # 在匹配行前插入

# a 在行后追加
sed '5a\new line' file.txt # 在第 5 行后追加
sed '/pattern/a\new line' file.txt # 在匹配行后追加

# 在文件开头添加
sed '1i\header line' file.txt

# 在文件末尾添加
sed '$a\footer line' file.txt

# 插入多行
sed '5i\
first line\
second line\
third line' file.txt

修改命令(c)

# 替换整行
sed '5c\replacement line' file.txt # 用新行替换第 5 行

# 替换匹配行
sed '/old/c\new content' file.txt # 用新内容替换包含 old 的行

# 替换范围行
sed '5,10c\replaced multiple lines' file.txt # 用一行替换 5-10 行

打印命令(p)

# 打印特定行
sed -n '5p' file.txt # 只打印第 5 行
sed -n '5,10p' file.txt # 打印第 5 到 10 行

# 打印匹配行
sed -n '/pattern/p' file.txt # 打印匹配的行(类似 grep)

# 打印行号
sed -n '/pattern/=' file.txt # 只打印匹配行的行号

# 打印行号和内容
sed -n '/pattern/{=;p}' file.txt

高级操作

多行处理

# N:读取下一行到模式空间
sed 'N;s/\n/ /' file.txt # 合并相邻两行

# D:删除模式空间的第一行
sed '/^$/{N;/^\n$/D}' file.txt # 删除多余空行,保留一个

# P:打印模式空间的第一行
sed -n 'N;P' file.txt # 打印奇数行

保持空间操作

# h:复制模式空间到保持空间
# H:追加模式空间到保持空间
# g:复制保持空间到模式空间
# G:追加保持空间到模式空间
# x:交换模式空间和保持空间

# 反转文件行顺序
sed '1!G;h;$!d' file.txt

# 交换相邻行
sed '{N;s/\(.*\)\n\(.*\)/\2\n\1/}' file.txt

# 合并奇偶行
sed 'N;s/\n/ /' file.txt

实用案例

配置文件修改

# 修改配置项
sed -i 's/DEBUG=false/DEBUG=true/' config.ini

# 注释掉某行
sed -i 's/^option=/#option=/' config.ini

# 取消注释
sed -i 's/^#\(option=\)/\1/' config.ini

# 添加配置项(如果不存在)
grep -q "new_option" config.ini || sed -i '$a\new_option=value' config.ini

# 修改 XML 配置
sed -i 's|<old>|<new>|g' config.xml

数据清洗

# 删除 Windows 换行符
sed -i 's/\r$//' file.txt

# 删除行首空格
sed 's/^[ \t]*//' file.txt

# 删除行尾空格
sed 's/[ \t]*$//' file.txt

# 同时删除首尾空格
sed 's/^[ \t]*//;s/[ \t]*$//' file.txt

# 压缩连续空格
sed 's/ \+/ /g' file.txt

# 删除空行
sed '/^$/d' file.txt

# 删除连续空行(保留一个)
sed '/^$/N;/^\n$/D' file.txt

批量文件处理

# 批量替换多个文件
sed -i 's/old/new/g' *.txt

# 批量替换并备份
sed -i.bak 's/old/new/g' *.txt

# 只修改匹配的文件
grep -rl "pattern" . | xargs sed -i 's/old/new/g'

# 在特定目录递归替换
find . -name "*.py" -exec sed -i 's/old/new/g' {} +

awk - 数据处理

awk 是一个强大的文本处理工具,特别适合处理结构化数据(如 CSV、日志文件等)。它将每行分割成字段,支持条件判断、循环、函数等编程特性。

基本概念

awk 的核心概念:

  • 记录(Record):默认是一行文本
  • 字段(Field):记录中的列,默认以空白字符分隔
  • 模式(Pattern):匹配条件
  • 动作(Action):匹配成功后执行的操作

基本语法:

awk '模式 { 动作 }' 文件
awk -F 分隔符 '模式 { 动作 }' 文件

字段操作

# $0 代表整行
awk '{ print $0 }' file.txt # 打印整行

# $1, $2, ... 代表字段
awk '{ print $1 }' file.txt # 打印第 1 列
awk '{ print $1, $3 }' file.txt # 打印第 1 和第 3 列

# $NF 代表最后一个字段
awk '{ print $NF }' file.txt # 打印最后一列
awk '{ print $(NF-1) }' file.txt # 打印倒数第二列

# NF 代表字段数量
awk '{ print NF }' file.txt # 打印每行的字段数

# NR 代表行号
awk '{ print NR, $0 }' file.txt # 打印行号和内容

# 指定分隔符
awk -F: '{ print $1 }' /etc/passwd # 以冒号分隔
awk -F',' '{ print $1 }' data.csv # 处理 CSV 文件
awk -F'\t' '{ print $1 }' data.tsv # 处理 TSV 文件

# 使用正则表达式作为分隔符
awk -F'[,:]' '{ print $1, $3 }' file.txt

解释

  • awk 自动将每行分割成字段,无需手动分割
  • 字段索引从 1 开始,$0 表示整行
  • -F 选项设置字段分隔符,默认是空白字符

模式匹配

# 正则匹配
awk '/pattern/ { print }' file.txt # 打印匹配的行
awk '/error/ { print $0 }' file.txt # 打印包含 error 的行

# 字段匹配
awk '$1 ~ /pattern/ { print }' file.txt # 第 1 列匹配
awk '$1 !~ /pattern/ { print }' file.txt # 第 1 列不匹配

# 比较运算
awk '$3 > 100 { print }' file.txt # 第 3 列大于 100
awk '$1 == "root" { print }' file.txt # 第 1 列等于 root
awk '$3 >= 80 && $3 <= 100 { print }' file.txt # 范围匹配

# 行号匹配
awk 'NR == 5 { print }' file.txt # 打印第 5 行
awk 'NR >= 5 && NR <= 10 { print }' file.txt # 打印 5-10 行
awk 'NR % 2 == 0 { print }' file.txt # 打印偶数行

# 范围模式
awk '/start/,/end/ { print }' file.txt # 从 start 到 end 之间的行

内置变量

# NR:当前记录号(行号)
awk '{ print NR, $0 }' file.txt

# FNR:当前文件的记录号(处理多文件时)
awk '{ print FNR, $0 }' file1.txt file2.txt

# NF:当前记录的字段数
awk '{ print NF }' file.txt

# FS:字段分隔符(输入)
awk 'BEGIN { FS=":" } { print $1 }' /etc/passwd

# OFS:字段分隔符(输出)
awk 'BEGIN { OFS="," } { print $1, $2 }' file.txt

# RS:记录分隔符(输入)
awk 'BEGIN { RS="" } { print }' file.txt # 以空行分隔记录

# ORS:记录分隔符(输出)
awk 'BEGIN { ORS="\n\n" } { print }' file.txt # 行间加空行

# FILENAME:当前文件名
awk '{ print FILENAME, NR, $0 }' file.txt

# ARGV:命令行参数数组
awk 'BEGIN { for (i=0; i<ARGC; i++) print ARGV[i] }' file1.txt file2.txt

BEGIN 和 END

# BEGIN:处理前执行
awk 'BEGIN { print "Header" } { print }' file.txt

# END:处理后执行
awk '{ print } END { print "Total lines:", NR }' file.txt

# 初始化变量
awk 'BEGIN { FS=":"; OFS="\t" } { print $1, $3 }' /etc/passwd

# 计算统计
awk '{ sum += $1 } END { print "Sum:", sum }' numbers.txt

# 计算平均值
awk '{ sum += $1; count++ } END { print "Average:", sum/count }' numbers.txt

条件语句

# if-else
awk '{ if ($3 > 90) print $1, "Excellent"; else print $1, "Good" }' grades.txt

# if-else if-else
awk '{
if ($3 >= 90) grade = "A"
else if ($3 >= 80) grade = "B"
else if ($3 >= 70) grade = "C"
else grade = "F"
print $1, grade
}' grades.txt

# 三元运算符
awk '{ print $1, ($3 >= 60 ? "Pass" : "Fail") }' grades.txt

循环语句

# for 循环
awk '{ for (i=1; i<=NF; i++) print $i }' file.txt # 打印每个字段

# while 循环
awk '{ i=1; while (i<=NF) { print $i; i++ } }' file.txt

# do-while 循环
awk '{ i=1; do { print $i; i++ } while (i<=NF) }' file.txt

# 遍历数组
awk '{
for (i=1; i<=NF; i++) {
count[$i]++
}
} END {
for (word in count) {
print word, count[word]
}
}' file.txt

数组

# 关联数组
awk '{ count[$1]++ } END { for (item in count) print item, count[item] }' file.txt

# 统计单词出现次数
awk '{
for (i=1; i<=NF; i++) {
words[$i]++
}
} END {
for (word in words) {
print word, words[word]
}
}' file.txt

# 数组排序
awk '{ arr[NR] = $0 } END {
n = asort(arr)
for (i=1; i<=n; i++) print arr[i]
}' file.txt

# 检查数组元素
awk '$1 in arr { print "found" }' file.txt

# 删除数组元素
awk '{ delete arr[$1] }' file.txt

内置函数

字符串函数

# length:字符串长度
awk '{ print length($0) }' file.txt

# substr:子字符串
awk '{ print substr($0, 1, 10) }' file.txt # 前 10 个字符

# index:查找子串位置
awk '{ print index($0, "pattern") }' file.txt

# split:分割字符串
awk '{ split($0, arr, ":"); print arr[1] }' file.txt

# sub:替换第一个匹配
awk '{ sub(/old/, "new"); print }' file.txt

# gsub:替换所有匹配
awk '{ gsub(/old/, "new"); print }' file.txt

# tolower/toupper:大小写转换
awk '{ print tolower($0) }' file.txt
awk '{ print toupper($0) }' file.txt

# match:正则匹配
awk '{ if (match($0, /[0-9]+/)) print substr($0, RSTART, RLENGTH) }' file.txt

数学函数

# 基本数学函数
awk '{ print int($1) }' file.txt # 取整
awk '{ print sqrt($1) }' file.txt # 平方根
awk '{ print log($1) }' file.txt # 自然对数
awk '{ print exp($1) }' file.txt # e 的幂次
awk '{ print sin($1) }' file.txt # 正弦
awk '{ print cos($1) }' file.txt # 余弦

# rand:随机数(0-1)
awk 'BEGIN { srand(); print rand() }'

# 生成指定范围随机整数
awk 'BEGIN { srand(); print int(rand() * 100) }' # 0-99

格式化输出

# printf 格式化
awk '{ printf "%-10s %5d %10.2f\n", $1, $2, $3 }' file.txt

# 格式说明符
# %s - 字符串
# %d - 整数
# %f - 浮点数
# %x - 十六进制
# %o - 八进制
# %- 左对齐
# %10 宽度 10
# %.2 小数点后 2 位

# 打印表格
awk 'BEGIN {
printf "%-15s %10s %10s\n", "Name", "Score", "Grade"
printf "%s\n", "------------------------------------"
}
{
printf "%-15s %10d %10s\n", $1, $2, $3
}' grades.txt

自定义函数

# 定义函数
awk '
function max(a, b) {
return (a > b) ? a : b
}
{
print max($1, $2)
}' file.txt

# 递归函数
awk '
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n-1)
}
BEGIN {
print factorial(5) # 输出 120
}'

# 局部变量
awk '
function myfunc(x, y, z) { # y, z 是局部变量
y = x * 2
z = y + 1
return z
}
{
print myfunc($1)
}' file.txt

实用案例

日志分析

# 统计 IP 访问量
awk '{ ip[$1]++ } END { for (i in ip) print ip[i], i }' access.log | sort -rn | head -10

# 统计状态码分布
awk '{ status[$9]++ } END { for (s in status) print s, status[s] }' access.log

# 计算平均响应时间
awk '{ sum += $10; count++ } END { print "Avg:", sum/count, "ms" }' access.log

# 统计每小时的请求数
awk '{
split($4, time, ":")
hour = time[2]
count[hour]++
} END {
for (h in count) print h, count[h]
}' access.log | sort

# 提取慢请求(响应时间超过 1 秒)
awk '$10 > 1000 { print $0 }' access.log

数据统计

# 计算列的总和
awk '{ sum += $1 } END { print sum }' numbers.txt

# 计算平均值、最大值、最小值
awk '
BEGIN { max = -999999; min = 999999 }
{
sum += $1
count++
if ($1 > max) max = $1
if ($1 < min) min = $1
}
END {
print "Count:", count
print "Sum:", sum
print "Average:", sum/count
print "Max:", max
print "Min:", min
}' numbers.txt

# 按 key 分组求和
awk '{ sum[$1] += $2 } END { for (k in sum) print k, sum[k] }' data.txt

CSV 处理

# 提取特定列
awk -F',' '{ print $1, $3 }' data.csv

# 按条件过滤
awk -F',' '$3 > 100 { print }' data.csv

# 添加列标题
awk -F',' 'BEGIN { print "Name,Age,Score" } { print }' data.csv

# 转换格式
awk -F',' '{ printf "%-20s %5s %10s\n", $1, $2, $3 }' data.csv

# 合并多个 CSV 文件
awk -F',' 'FNR==1 && NR!=1 { next } { print }' *.csv > merged.csv

sort - 排序

sort 命令用于对文本行进行排序。

基本用法

# 基本排序(按字典序)
sort file.txt

# 反向排序
sort -r file.txt

# 数字排序
sort -n numbers.txt # 按数值排序
sort -rn numbers.txt # 按数值反向排序

# 按特定列排序
sort -k 2 data.txt # 按第 2 列排序
sort -k 2,2 -k 3,3 data.txt # 先按第 2 列,再按第 3 列

# 指定分隔符
sort -t: -k 3 -n /etc/passwd # 以冒号分隔,按第 3 列数字排序

# 去重排序
sort -u file.txt # 排序并去除重复行

# 检查是否已排序
sort -c file.txt # 如果未排序,输出第一个乱序的行

# 忽略前导空白
sort -b file.txt

# 忽略大小写
sort -f file.txt

# 月份排序
sort -M months.txt # JAN, FEB, MAR, ...

# 随机排序
sort -R file.txt

常用组合

# 按第 2 列数字降序排列
sort -k 2 -nr data.txt

# 排序后取前 10 名
sort -k 2 -nr data.txt | head -10

# 处理大文件(使用临时文件)
sort -S 1G -T /tmp bigfile.txt

# 合并已排序的文件
sort -m sorted1.txt sorted2.txt sorted3.txt

uniq - 去重

uniq 命令用于去除相邻的重复行。通常与 sort 配合使用。

基本用法

# 去除相邻重复行
uniq file.txt

# 统计重复次数
uniq -c file.txt

# 只显示重复行
uniq -d file.txt

# 只显示唯一行(不重复的)
uniq -u file.txt

# 忽略前 N 个字符
uniq -s 5 file.txt

# 只比较前 N 个字符
uniq -w 10 file.txt

# 忽略大小写
uniq -i file.txt

常用组合

# 统计每个值的出现次数并排序
sort file.txt | uniq -c | sort -rn

# 找出重复的行
sort file.txt | uniq -d

# 找出只出现一次的行
sort file.txt | uniq -u

# 找出出现次数最多的前 10 个
sort file.txt | uniq -c | sort -rn | head -10

cut - 列提取

cut 命令用于按列提取文本内容。

基本用法

# 按字符位置提取
cut -c 1-10 file.txt # 提取前 10 个字符
cut -c 5- file.txt # 从第 5 个字符到末尾
cut -c 1,5,10 file.txt # 提取第 1、5、10 个字符

# 按字段提取
cut -d: -f1 /etc/passwd # 以冒号分隔,提取第 1 字段
cut -d',' -f1,3 data.csv # 提取第 1 和第 3 字段
cut -d',' -f1-3 data.csv # 提取第 1 到第 3 字段

# 排除字段
cut -d',' --complement -f2 data.csv # 提取除第 2 字段外的所有字段

# 指定输出分隔符
cut -d: -f1,3 --output-delimiter=' ' /etc/passwd

tr - 字符转换

tr 命令用于字符转换、删除和压缩。

基本用法

# 字符替换
tr 'a-z' 'A-Z' < file.txt # 小写转大写
tr 'A-Z' 'a-z' < file.txt # 大写转小写

# 删除字符
tr -d '0-9' < file.txt # 删除所有数字
tr -d '\n' < file.txt # 删除换行符
tr -d '\r' < file.txt # 删除回车符

# 压缩连续字符
tr -s ' ' < file.txt # 压缩连续空格为一个
tr -s '\n' < file.txt # 压缩连续空行为一个

# 替换换行符
tr '\n' ' ' < file.txt # 换行转空格
tr ' ' '\n' < file.txt # 空格转换行

# 字符集
tr '[:lower:]' '[:upper:]' < file.txt # 小写转大写
tr -d '[:digit:]' < file.txt # 删除数字
tr -d '[:punct:]' < file.txt # 删除标点符号

wc - 统计

wc 命令用于统计行数、单词数和字节数。

基本用法

# 统计行数、单词数、字节数
wc file.txt # 输出:行数 单词数 字节数 文件名

# 只统计行数
wc -l file.txt

# 只统计单词数
wc -w file.txt

# 只统计字节数
wc -c file.txt

# 只统计字符数
wc -m file.txt

# 统计多个文件
wc *.txt

# 配合管道使用
cat file.txt | wc -l # 统计行数
grep "error" file.txt | wc -l # 统计匹配行数

组合应用

日志分析管道

# 统计访问量前 10 的 IP
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10

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

# 找出响应时间最慢的 10 个请求
awk '{ print $10, $0 }' access.log | sort -rn | head -10

# 统计每分钟的请求数
awk '{ print substr($4, 14, 16) }' access.log | sort | uniq -c

# 找出访问量异常的 IP(超过 1000 次)
awk '{ ip[$1]++ } END { for (i in ip) if (ip[i] > 1000) print ip[i], i }' access.log

数据处理管道

# CSV 数据处理:提取、过滤、排序
awk -F',' '$3 > 100 { print $1, $2, $3 }' data.csv | sort -k 3 -nr

# 提取所有邮箱地址
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt | sort -u

# 统计代码行数(排除空行和注释)
grep -v '^\s*$' code.py | grep -v '^\s*#' | wc -l

# 合并两个文件的特定列
paste <(cut -d',' -f1 file1.csv) <(cut -d',' -f2 file2.csv)

文本清洗管道

# 清洗数据:删除空白、去重、排序
cat data.txt | tr -s ' ' | sed 's/^[ \t]*//' | sort -u > clean.txt

# 格式化 JSON(需要 jq)
cat data.json | jq .

# 提取并格式化日期
grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' file.txt | sort | uniq -c

最佳实践

性能优化

  1. 处理大文件时

    • 使用 LC_ALL=C 加速排序:LC_ALL=C sort bigfile.txt
    • sort 使用 -S 指定内存:sort -S 1G bigfile.txt
    • awksed 在处理复杂逻辑时更高效
  2. 减少管道层级

    # 不推荐
    cat file.txt | grep "pattern" | wc -l
    # 推荐
    grep -c "pattern" file.txt
  3. 使用内置功能

    # 不推荐
    grep "pattern" file.txt | awk '{ print $1 }'
    # 推荐
    awk '/pattern/ { print $1 }' file.txt

安全注意

  1. 特殊字符处理

    # 使用引号保护变量
    grep "$pattern" file.txt

    # 处理包含特殊字符的文件名
    find . -name "*.txt" -print0 | xargs -0 grep "pattern"
  2. 避免命令注入

    # 不要直接在 awk 中执行用户输入
    # 危险:awk "{ system($1) }" file.txt
  3. 备份重要文件

    # 使用 -i.bak 创建备份
    sed -i.bak 's/old/new/g' important.conf

小结

本章我们学习了 Linux 文本处理的核心工具:

  • grep:强大的文本搜索工具,支持正则表达式
  • sed:流编辑器,用于文本替换和转换
  • awk:数据处理工具,支持复杂的数据分析和格式化
  • sort/uniq:排序和去重
  • cut/tr/wc:列提取、字符转换和统计

这些工具通过管道组合,可以构建强大的文本处理流水线,是系统管理和数据分析的必备技能。

练习

  1. 从日志文件中提取所有错误信息,统计每种错误的出现次数
  2. 使用 sed 批量修改配置文件中的 IP 地址
  3. 使用 awk 分析 CSV 文件,计算某列的总和和平均值
  4. 编写一个管道命令,找出系统中占用内存最多的 10 个进程
  5. 使用文本处理工具分析 Web 服务器访问日志,生成访问报告