ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux文本处理命令实战:grep、sed、awk组合用法与日志分析技巧

Linux文本处理命令实战:grep、sed、awk组合用法与日志分析技巧 做技术这行Windows 和 Linux 之争聊了多少年真正落到干活上你会发现 Linux/Unix 系那套文本处理哲学依然是效率上限最高的那一档。哪怕现在有各种 JSON 格式化工具、日志平台、Kibana 看板但凡是你要在服务器上临时排查问题、批量处理数据、写个自动化脚本最顺手、最通用的依然是那群老伙计grep、sed、awk、sort、uniq、cut、xargs。这篇文章就把它们从头到尾串一遍不讲花架子全是在生产环境里真正能救命、能省时间的用法和套路。这篇文章适合谁刚接触 Linux 的运维和开发新人想系统性补齐文本处理基本功的或者已经在用但只会grep xxx和vim手动改文件的同学。我会从设计思想讲起把命令怎么组合、为什么这么组合讲清楚最后附上我踩过的坑和排查技巧。全程没有意识形态没有多余废话纯技术实战。1. 设计思路为什么文本处理是 Linux/Unix 的看家本领1.1 一切皆文件与文本流的哲学Linux/Unix 系统里有个核心抽象一切皆文件。普通文件是文件目录是文件设备是文件甚至进程的输入输出也是文件描述符。这意味着不管你面对的是什么数据源——日志文件、配置文件、命令输出、网络抓包结果——只要它能被读成字节流你就能用同一套文本处理工具去处理它。这套设计的能量在于它不强制你使用某个特定软件打开特定格式而是把所有数据都投影到“文本流”这一层。日志是文本配置文件是文本数据库导出的 CSV 也是文本。于是grep找到的东西可以喂给awk重新排版awk处理完的结果可以再交给sort排序整条流水线通过管道|串起来数据在内存里流动根本不需要产生中间文件。这就是管道哲学——每个命令只做好一件事通过组合完成复杂任务。我举一个特别直观的对比。假设你要从一个 2GB 的 Nginx 访问日志里找出状态码 500 的请求并按 IP 统计次数Windows 下你可能要写 Python 脚本读文件、正则匹配、字典计数、排序输出至少二十行代码。Linux 下呢grep 500 access.log | awk {print $1} | sort | uniq -c | sort -rn一行搞定。而且这行命令从执行到出结果通常只要几十秒因为它在管道里流式处理逐个读取日志行不需要一次性加载全部数据到内存。这就是基本功的价值。1.2 组合优于单点工具很多教程喜欢逐个命令讲讲完grep讲sed讲完sed讲awk仿佛它们是孤立的。但实际工作中单独用一个命令的场景很少更多是像管道一样串联起来。我习惯把命令分成几类角色过滤类grep、awk负责从数据流中筛选出符合条件的行或列。变换类sed、tr、cut负责修改行内容、替换字符、裁剪字段。统计类sort、uniq、wc负责排序、去重、计数。执行类xargs负责把前一个命令的输出变成后一个命令的参数或输入。打个比方就像工厂流水线。原材料是一大堆杂乱的日志文本grep是先质检员把符合特征的产品挑出来awk是拆分工人按空格把每个产品分解成零件编号sort和uniq是仓库管理员把零件按编号归类排序并清点数量最后到head这里只要出货量最大的前十个。理解这个角色划分你自然就知道什么场景该用什么命令了。1.3 为什么今天的开发者依然要学你可能会说现在有各种日志采集系统、数据库查询、可视化工具文本命令是不是过时了我自己的体验是恰恰相反。看一下这些场景Docker 容器日志不支持 SQL 查询你只能docker logs拉出来然后字符串处理Kubernetes 的kubectl describe输出是文本你要 grep 关键状态配置管理、CI/CD 脚本里到处都是对文本的判断和替换甚至你用 Ansible、SaltStack 写 playbook底层执行的还是命令。这些场合下文本处理命令就是最通用、最不会被版本迭代淘汰的接口。掌握了这套基本功你就拥有了在任何 Linux/Unix 环境里快速理解和操作数据的能力这比记住某个工具的具体操作界面更有价值。2. 查询与过滤grep 和它的黄金搭档2.1 grep 不是只会搜关键字grep全称是 Global Regular Expression Print全局正则匹配并打印。它最基本的用法确实是grep pattern file但实战中我很少用这么裸的形式多半会组合选项。下面这些是我日常出现频率最高的grep -E ERROR|FATAL app.log # 扩展正则匹配多个关键词 grep -w error app.log # 精确匹配整个单词避免匹配到 error_handler grep -i warning app.log # 忽略大小写 grep -v debug app.log # 反向匹配排除包含 debug 的行 grep -c 200 access.log # 返回匹配行数而不是内容 grep -l error *.log # 只列出包含匹配的文件名 grep -r timeout /etc/nginx/ # 递归搜索目录 grep -n listen nginx.conf # 显示行号改配置时特别有用 grep --include*.conf -r root /etc/ # 只搜索指定后缀文件这些选项单独看不复杂但组合起来能解决大问题。比如有一次我要排查线上所有虚拟主机配置里有没有重复的 server_name直接grep -r server_name /etc/nginx/conf.d/ | sort | uniq -d几秒钟就定位到了重复项。需要注意一个细节grep -E用的是扩展正则支持、?、|、()这些元字符基础正则grep里这些符号要加反斜杠转义才能用写作\、\|。语法习惯不同容易踩坑我自己的习惯是统一用grep -E省得记两套规则。还有一个隐藏技巧grep支持-P参数使用 Perl 正则比如需要\d、\w这种写法时非常方便但要注意-P在某些精简基础镜像比如 Alpine里不支持生产环境频繁切换容器要留意。另外grep的退出码也有讲究。匹配到结果时返回 0没匹配到返回 1命令本身出错返回 2。合理利用退出码可以写判断逻辑if grep -q healthy healthcheck.txt; then echo 服务健康 else echo 服务异常 fi-q是静默模式只要状态码不要输出。在 Shell 脚本里这样用比解析字符串靠谱得多。2.2 cut、sort、uniq 无痛统计grep负责“选行”cut负责“选列”。用cut的时候核心是搞清楚分隔符和字段位置cut -d -f1 /etc/passwd # 按空格切取第一列用户名 cut -d: -f1,3 /etc/passwd # 按冒号切取第一和第三列 cut -d: -f2- /etc/passwd # 第二列到行尾之前那个统计 IP 的例子里面awk {print $1}本质上做的也是选列的工作。cut和awk的区别在于cut更简单快速适合分隔符规整的场景awk则能处理更复杂的分隔符和条件逻辑。实话说如果只是按固定字符取列cut的执行速度略胜一筹在几 GB 大文件上差距能明显感受到。接着是sort。单独用sort只是按字典序排但加上选项才能发挥威力sort -n numbers.txt # 按数值大小排序而不是字典序 sort -k3 -t: /etc/passwd # 指定第3列排序分隔符是冒号 sort -rn -k2 result.txt # 按第2列数值降序 sort -u file.txt # 排序并去重等价 sort file.txt | uniq新手经常无视-n结果 10 排在 2 前面因为字典序里 1 小于 2。如果处理的是数字务必加上-n。uniq的逻辑有点反直觉它只能去除“连续相邻”的重复行不能全文去重。所以标准用法一定是和sort搭配sort access.log | uniq -c # 统计每行出现的次数 sort access.log | uniq -d # 只显示重复行 sort access.log | uniq -u # 只显示不重复行wc在统计场景也很常用wc -l数行数wc -c数字节数。结合grep就是写监控脚本经常用到的测试手段logs$(wc -l app.log) errors$(grep -c ERROR app.log) rate$(awk BEGIN {print ($errors / $logs) * 100}) echo 错误率: ${rate}%利用awk的BEGIN可以直接在命令行里做浮点运算不用另起计算器。3. 流式编辑器 sed 与 awk 的实用战场3.1 sed 的定位批量替换与正则手术sed全称 Stream Editor流式编辑器。它最大的价值在于你不需要打开文件就能自动完成修改特别适合批量操作配置文件、处理大数据量文本。最常用的就三个功能替换、删除、打印指定行。替换是 sed 的主场基本格式sed s/旧/新/标志sed s/old/new/g file.txt # 全行所有匹配替换g 表示全局 sed s/^#//g nginx.conf # 删除行首的 #即注释符号 sed s/\/usr\/local/\/opt/g path.txt # 路径中的斜杠需要转义斜杠转义很丑实际写的时候我更喜欢用其他字符做分隔符比如#或者|sed s#/usr/local#/opt#g path.txt sed s|http://|https://|g urls.txt删除行和打印行也是高频操作sed /^$/d file.txt # 删除所有空行 sed 10,20d file.txt # 删除 10 到 20 行 sed -n 5,10p file.txt # 只打印 5 到 10 行-n 关闭自动打印 sed -n $p file.txt # 打印最后一行$ 表示最后一行sed -i直接修改文件这是日常最危险也最常用的选项。危险在于一旦写错没有后悔药所以我强烈建议任何重要文件操作前先备份。带备份的写法更安全sed -i.bak s/foo/bar/g config.conf # 生成 config.conf.bak 再修改如果你想批量把某个服务的所有.conf文件里的端口从 8080 改为 9090一条命令扫完sed -i s/8080/9090/g /etc/myapp/*.conf在使用 sed 处理配置文件时有个容易忽略的细节如果你用sed -i修改了软链接指向的配置文件某些系统上会直接把软链接替换为普通文件。原因在于sed -i的实现方式一般是创建临时文件再重命名软链接关系就断了。我踩过一次这个坑修复方法是修改前先readlink -f确认真实路径。3.2 awk 的范式列提取与统计逻辑如果说 sed 是外科手术刀awk 就是瑞士军刀。它不仅做文本处理还能写分支条件、循环、数组统计甚至可以直接当一门迷你语言用。awk 的基本执行模型是逐行读取文件按分隔符拆成多个字段然后对每行执行{ }里的动作。$1表示第一列$0表示整行NF表示当前行的字段数NR表示当前处理到第几行。日常用得最多的几个场景我给拆开讲。第一个是字段提取。awk {print $1, $3}比cut直观而且还能插入自定义文本awk {print IP: $1 状态: $9} access.log | head -20第二个是条件过滤。awk 的过滤能力实际上比 grep 更强因为它可以按字段值过滤awk $9 500 {print $1, $7} access.log # 状态码字段等于 500 awk $9 ! 200 access.log # 状态码不是 200 awk $11 0.5 {print $1, $11} access.log # 请求耗时超过 0.5 秒的记录第三个是统计汇总。这是 awk 相对其他命令最强大的地方因为它自带关联数组awk {count[$1]} END {for (ip in count) print ip, count[ip]} access.log | sort -rn | head这段逻辑解释起来很简单每读一行以第一列 IP 为下标的计数器加一文件全部处理完后进入END块遍历所有 IP 并打印次数最后再接个sort -rn和head取出访问量最高的几个 IP。你可以用同样的办法统计 PV 数、统计各接口被调用的次数awk {count[$7]} END {for (url in count) print count[url], url} access.log | sort -rn | head -20如果你想计算所有请求的平均响应时间直接累加后除以行数awk {sum $NF} END {print 平均耗时:, sum / NR} app.log这里$NF是取最后一列假设你已经提前把耗时输出到日志最后一列了。awk 也支持浮点运算和格式化输出awk {sum $NF} END {printf 平均耗时: %.2fms\n, sum / NR} app.log还有一个生产环境经常用到的场景——按行号精准提取日志片段。比如一个服务崩溃前最后的 50 行可以先用grep -n找到关键错误在第几行然后用sed -n或者awk NR 1000 NR 1050精准拉取范围。字符串和行号结合起来比在 Vim 里翻页快得多。3.3 注意 awk 的字段分隔规则awk 默认按空白字符空格和 Tab分隔字段多个连续空格也算一个分隔符这点和cut -d 按单字符分隔不同。如果日志列间是多个空格用 awk 处理反而比 cut 更省心。但如果你想自定义分隔符用-Fawk -F: {print $1, $3} /etc/passwd # 以冒号分隔打印用户名和 UID awk -F, {print $2, $5} data.csv # 以逗号分隔处理 CSV顺便提一下awk 里的BEGIN块在读取文件前执行适合定义变量、打印表头END块在文件处理完后执行适合输出汇总结果。这三个块的结构让 awk 几乎成了一个小型的流式编程语言。我经常在写脚本时先本地造几个假数据行测试 awk 逻辑确认无误再跑真实大文件这一步能省下大量调试时间。4. 批量操作与文件对比的进阶套路4.1 xargs把输出变成参数xargs是一个典型的“桥接”命令它读取标准输入把它拆分成参数然后传给后面的命令执行。没有它某些命令无法直接从管道接收输入。比如find和grep、rm、cp的配合就很依赖它。最经典的组合是批量删除和批量打包find /data/logs -name *.log -mtime 30 | xargs rm -f find /data/logs -name *.log -mtime 30 | xargs tar -czf old_logs.tar.gz这里-mtime 30表示修改时间超过 30 天的文件。第一条命令直接删掉过期日志第二条把它们打包归档。注意管道左边是文件名列表右边是rm或tar中间的xargs相当于把它们一个个接上。处理文件名带空格的问题是新手最容易踩的坑。默认情况下xargs用空格和换行作为分隔符一个名为Game Log.txt的文件会被拆成Game和Log.txt两个参数。解决办法是让find用-print0输出以空字符分隔xargs对应加-0find . -name *.txt -print0 | xargs -0 rm -f这行代码里-print0对路径中的空格、换行、特殊字符一律免疫是打包删除、批量移动时最稳的写法。虽然-print0可能让你一时不习惯但养成习惯后真的能避免很多玄学故障。xargs还有几个好用的选项。-n1表示每条命令只用一个参数配合下载或逐个处理很常用cat urls.txt | xargs -n1 curl -O-P可以指定并发进程数实现并行处理cat urls.txt | xargs -P 8 -n1 curl -O-I可以自定义占位符实现更灵活的参数位置控制find . -name *.txt | xargs -I {} mv {} /data/backup/这里{}会被实际文件名替换然后传给mv。不过要注意一个隐藏问题管道中输出的行数特别多时xargs会把参数批量分组执行多次这是它的内部机制。像xargs rm -f即使有一万个文件也会分批执行不会因为参数列表过长报错。但如果你的命令本身需要一次性接收全部参数比如某些不支持多次调用的命令就要考虑用xargs -n控制分组。4.2 find 与 grep 的协作检索find常常被划入“查找文件”而不是“文本处理”但它在文本处理流水线里其实是个重要的起点它能快速圈定要处理的文件集合再交给后面的文本命令。除了按文件名我平时最常用的是按修改时间、权限、所属用户过滤find /var/log -name *.log -mmin -60 # 最近一小时内修改过的日志文件 find /etc -type f -perm -644 # 找出权限为 644 的普通文件 find /home -user zhangsan -name *.sh # 指定用户的脚本文件配合grep检索代码、配置文件里的内容也很顺手find /opt/app -name *.conf -exec grep -l password {} \; find /opt/app -name *.yml | xargs grep -E replica|master第一条里-exec ... {} \;是 find 自带执行机制{}是文件占位符第二条则用管道交给 xargs-grep。两种方式效果差不多-exec更安全不用担心里面有空格文件名的分割问题但性能上xargs并行效率更优。如果你追求简单可靠我通常建议-exec处理海量小文件时再换xargs -P提速。4.3 diff 和摘要式响应检测文件差异diff是行级对比工具常用于比较配置文件的差异、代码变更、两份导出数据。基础用法是diff file1 file2输出是二选一差异列表。不过默认格式可读性一般我更推荐统一格式diff -u old.conf new.conf # 带上下文和 - 符号的统一格式 diff -rq dir1/ dir2/ # 比较两个目录哪些文件不同 diff -u (cat remote.conf) local.conf # 进程替换直接比较远端文件和本地文件如果只需要知道“两个文件是否一致”用diff -q或者cmp更快cmp到第一个不同字节就会停下来不带多余输出。diff配合patch可以生成和应用补丁diff -u old.txt new.txt change.patch patch change.patch现在 Git 已经内置了类似功能但理解diff和patch的原理依然有助于了解版本控制的底层逻辑。这里有个容易踩的坑Linux 和 Unix 系统上的换行符通常是\nLFWindows 是\r\nCRLF。如果你在 Windows 上编辑了文件再传到 Linux用diff对比会出现“看起来完全一样却整行都被标记为差异”的情况。解决方法是先转换换行符再对比sed s/\r$// win_file.txt linux_file.txt diff -u linux_file.txt original_linux.txt或者直接安装dos2unix工具完成转换。类似的问题还有文件末尾是否有换行符、是否有 BOM 头。这些细节在排查“为什么配置完全一样但程序不识别”时经常会遇到提前知道能省不少功夫。4.4 tr 与字符集处理的隐藏价值tr用于字符集转换、删除、压缩。它不能处理单词和正则只能处理单个字符或字符类但正因为简单在处理批量小改动时效率极高。tr a-z A-Z file.txt # 小写转大写 tr \t , data.tsv data.csv # Tab 转逗号 tr -d \r file.txt file_clean.txt # 删除所有回车符处理 CRLF tr -s file.txt # 把连续多个空格压缩成一个tr -s在日志分析里特别实用。某些命令输出有大量空行或空格先用tr -s压缩再做列提取就不容易被空字段干扰。tr的输入输出都是标准文件流所以它和管道配合非常顺手。还有一类场景我们需要把命令输出的多行变成单行比如把公钥拼到一行写入 authorized_keys这种活一般用xargs或trcat id_rsa.pub | tr -d \n authorized_keys或者在处理文件列表时把多行文件名转成逗号分隔ls *.log | tr \n , | sed s/,$//5. 运维现场常见问题与排查技巧5.1 命令组合中的权限与缓冲陷阱组合命令时最常见的问题就是权限。用find或grep扫描/var/log时有些日志文件只允许 root 访问普通用户运行时你会看到一堆Permission denied错误刷屏。解决方法是把标准错误重定向掉grep ERROR /var/log/*.log 2/dev/null find / -name *.conf 2/dev/null | head另一个容易被忽视的坑是管道缓冲。默认情况下管道是缓冲的不是立即逐行传递的。当你执行tail -f app.log | grep ERROR时grep 的输出往往不会立刻显示要等缓冲区满或进程结束。这个特性在服务日志实时监控时非常烦人。解决办法是用grep --line-buffered强制行缓冲或者干脆用stdbuf -oL修改标准输出的缓冲方式tail -f app.log | grep --line-buffered ERROR tail -f app.log | stdbuf -oL awk {print $4, $1}这个细节我在刚接触 tail -f 管道时困惑了很久以为是命令没生效实际上只是输出被缓冲了。如果你在实时观察日志时发现结果半天不出来优先检查是不是缓冲问题。还有一个和字符编码相关的坑某些系统区域的 locale 不是 UTF-8grep处理含中文的日志时可能报错或匹配不到。排查时可以先看看当前 localelocale export LC_ALLC.UTF-8在脚本里混用grep -P时尤其要注意 locale 对正则库的影响必要的时候销毁现场带上LC_ALLC跑任务规避掉这层干扰。5.2 大日志分析的标准排查流程我处理线上经常出现“服务变慢、报错陡增”这类问题会把整套流程固化成几个基本步骤先看时间范围再做筛选再统计 TOP N最后关联上下文定位。具体到命令就是一组组合拳# 1. 先看总请求数与错误数 wc -l access.log grep -c 500 access.log # 2. 确认错误的时间段分布 grep 500 access.log | awk {print $4} | cut -d: -f2 | sort | uniq -c # 3. 按接口统计错误请求 TOP grep 500 access.log | awk {print $7} | sort | uniq -c | sort -rn | head -20 # 4. 提取一段时间的日志上下文 grep -E 2024-06-01T10:1[5-9] app.log | sed -n 1,200p这套流程几分钟内就能让你大致拼出故障图景某个特定接口在某一分钟开始大量 500错误消息是什么当时请求量有没有突增。我习惯把每一步的统计结果简化成表格或者数字而不是直接导出原始日志因为原始数据太容易淹没真正的问题了。5.3 文本处理命令易踩坑清单用过一段时间后你会发现这类工具真正的问题多半不是“不懂选项”而是“在错的格式上操作”。下面列几个高频坑我基本每个月都会在同事那边看到其中一两例。第一个坑是 grep 匹配到了文件本身或二进制文件。grep -r搜索目录时会扫描所有文件包括二进制文件和压缩文件输出乱码还可能卡住终端。稳妥的做法是加-I忽略二进制文件或者用--include*.log限定文件类型。第二个坑是sort -u与uniq的混用。很多人以为uniq可以不排序就完成全局去重实际上它只处理相邻行乱序文件里重复的行相距很远就不会被合并。所以规则很简单想全局去重就sort -u想统计次数就sort | uniq -c。第三个坑是sed -i修改文件时误伤不匹配行。s///不带g标志时只替换每行第一个匹配这是正常的但如果你以为它是全局替换而漏看选项结果会出乎意料。建议替换前先不加-i跑一遍看看输出sed s/127.0.0.1/0.0.0.0/g app.conf | diff app.conf - # 检查差异确认无误后正式用sed -i执行。第四个坑是管道命令的退出状态。管道中最后一个命令才决定整个管道的退出码。比如grep error file | wc -l即使 grep 没找到任何内容管道整体的退出码也是wc -l的 0因为 wc 成功执行了。如果你在脚本里用if 管道判断是否匹配会永远进入成功分支。正确做法是直接if grep -q error file或者用set -o pipefail让管道中任一命令失败都返回非零。5.4 踩坑后总结的两个个人习惯说实话文本处理命令本身不难难的是在你面对的海量日志和诡异配置中保持冷静。我这里有两个从实践中养成的习惯可能对你也实用。第一个习惯是“先看头尾再处理”。面对一个新日志或新配置文件先用head -5和tail -5看一眼确认列结构、分隔符、时间格式再做 awk 和 cut。跳过这步直接跑 awk经常因为列位置不对而输出整片空白。看 CSV 文件我甚至会先跑head -1确认表头字段数量有心算概念再写命令。第二个习惯是“命令全部先打印再执行”。凡是涉及修改、删除的操作我会先跑一遍不带写动作的版本把输出或匹配结果看清楚了再真改。删除日志用find ... | xargs rm前先跑find ...看列表批量替换前先跑 sed 不加-i。多花半分钟却可能挽回一整天的损失。这不能说是什么高明技巧只是踩了太多坑以后形成的肌肉记忆。希望你把这些坑都记下来少走我走过的弯路。
返回列表