1. 从单行匹配到全盘检索:grep命令解决的是哪几类问题
很多人第一次接触Linux,学完cd、ls、mkdir之后,下一个绕不开的命令就是grep。如果说ls是让你看清楚当前目录有什么,那么grep就是让你在一堆文本里快速找到“你想要的线索”。我这些年排查问题、写脚本、处理日志,grep的使用频率基本排在我个人命令榜前三,仅次于cd和vim。它之所以这么重要,是因为Linux世界里几乎所有东西最终都以“文本”形式存在——配置文件是文本、程序输出是文本、日志文件是文本、进程列表也是文本。只要你能把信息变成文本,grep就能帮你把它翻个底朝天。
先看一个最基础的使用场景。你想查一下nginx配置里到底监听在哪个端口,配置文件可能有几百行,你不可能从头翻到尾:
bash复制grep listen /etc/nginx/nginx.conf
这条命令的意思很简单:把/etc/nginx/nginx.conf文件里包含listen字符串的行打印出来。grep这个名字来源于global regular expression print,全局正则表达式打印,它最早是为Unix下的文本处理设计的,已经存在了几十年,却依然是今天Linux运维、开发、数据分析中最常用的工具之一。
归纳起来,grep在实际工作中解决的是四种典型问题。
第一种是“确认存在性”。某个服务是否启动成功?某个配置项是否已写入?某个报错是否出现?这类问题的答案通常只需要一个“有还是没有”。比如:
bash复制grep "error" /var/log/messages
只要输出结果非空,就说明日志里有error字样。配合退出码使用效果更好:grep找到了匹配行会返回0,找不到返回1,文件不存在或参数错误返回2。这就意味着你可以在脚本里直接用if判断,不需要再去解析文本输出。
第二种是“过滤与提取”。从一个混杂着大量噪声的输出中,只挑出你关心的部分。比如ps aux会打印所有进程,几十行甚至上百行,你只想知道某个Java进程的PID和启动参数,那就可以:
bash复制ps aux | grep java
把ps的输出通过管道|传给grep,grep过滤出包含java的行。类似的还有ss -lntp | grep 8080、dmesg | grep usb、tail -f app.log | grep ERROR,本质都是一样的:把上一个命令的输出流当成grep的输入,只留下匹配的行。
第三种是“按上下文排查”。日志中出现了异常,光看异常那一行往往不够,你还得知道异常前后的日志是什么。grep提供了-A、-B和-C三个参数,分别表示输出匹配行之后的行、之前的行、以及前后各多少行。这在实际排障中极其有用,我在后面专门讲日志场景时会细说。
第四种是“统计与批量处理”。配合-c可以统计匹配行数,配合-o可以只输出匹配到的部分而不是整行,配合-l可以只列文件名不显示内容,配合-r可以递归搜索整个目录。这些组合让grep不再只是“看一眼”,而变成了一个能在脚本中精确控制逻辑的零件。
这么多用途,但核心原理只有一个:grep逐行读取输入,判断该行是否匹配给定的模式,如果匹配则按你指定的方式输出。理解了这个逐行模型,后面所有参数和组合都不难理解。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 常用参数选型逻辑:为什么精确匹配要加-w,反向过滤要防误伤
grep的参数很多,但真正高频使用的基本上就是-i、-v、-w、-n、-r、-l、-c、-o这几个。参数本身不难记,难的是搞清楚每个参数在什么场景下该用、用了会有什么副作用。这一节我按“选型逻辑”来讲,而不是简单罗列参数表。
2.1 -i与-w:匹配时的两个“半自动辅助”
默认情况下,grep是区分大小写的。你要搜error,它不会匹配Error或ERROR。但在大部分日志场景里,你其实是想“不管大小写,只要这个词出现都给我找出来”,这时候加-i:
bash复制grep -i error app.log
-i的意思就是忽略大小写。不过要注意,加-i会降低匹配精度,搜索范围变大了,结果噪声也可能变多。比如error可能会匹配到ErrorRate、errorCode这类并不是报错的词。所以要不要加-i,取决于你是在做“网罗式搜索”还是“精确确认”。
再说-w。假设你想查配置里有没有user这个配置项:
bash复制grep user nginx.conf
这条命令会把user nobody;这一行列出来,但同时也会把username、user_group、users这些包含“user”子串的行统统列出来。如果你的意图是精确匹配“user”这个完整单词,就要加-w:
bash复制grep -w user nginx.conf
-w全称--word-regexp,它要求匹配的字符串两侧必须是单词边界(也就是非字母、非数字、非下划线的位置)。这跟\b正则元字符是一个逻辑。我在处理配置文件、写部署脚本时几乎总是用-w或带边界的正则,因为配置项之间经常有相似的前缀,不加边界很容易误伤。
2.2 -v反向过滤:用排除法缩小范围
grep -v是热搜词里出现频率非常高的一个变体,它的作用正好相反:输出所有不包含匹配字符串的行。
bash复制grep -v "^#" /etc/nginx/nginx.conf
这条命令会输出所有不以#开头的行,也就是过滤掉注释。它的价值在于:很多场景下你要的不是“包含什么”,而是“不包含什么”。比如查看日志时想排除掉每天大量刷屏的健康检查请求:
bash复制grep -v "healthcheck" access.log
再比如我们经常配合管道使用,过滤掉grep命令自身产生的进程:
bash复制ps aux | grep java | grep -v grep
为什么要加grep -v grep?因为ps aux | grep java这条命令中的grep进程本身也会作为一个进程出现在ps的输出里,它包含了“java”这个字符串,所以会被自己匹配出来。这个多余的结果几乎每次都会出现,堪称新手最容易困惑的grep现象之一。我后面会专门用一节来讲这个。
但反向过滤有一个副作用:它会把整个匹配逻辑反过来,一旦你写错匹配条件,丢掉的不是你想要的噪声,而是真正有用的数据。比如你想排除DEBUG日志:
bash复制grep -v "DEBUG" app.log
如果日志中有DEBUG_MODE、DEBUGGER之类字样,它们也会被一起过滤掉。如果其中某个恰恰是你想保留的关键信息,那你就在无意中把线索弄丢了。所以用-v之前应该先不加-v跑一遍,看看匹配结果包含了哪些意料之外的行。
2.3 -n与-o:让结果不只可读还可算
-n是最实用参数之一,输出时在每行前加上行号:
bash复制grep -n "listen" /etc/nginx/nginx.conf
加行号的意义在于,你找到问题位置后可以直接去vim里按:行号跳转。对于日志分析来说,行号还能辅助说明“相邻报错之间的距离”,两个错误之间只隔几行往往意味着同根因,隔了几百行则大概率无关联。
-o则不一样,它只输出匹配到的部分,而不输出整行:
bash复制grep -o 'error[0-9]*' app.log
如果某一行是2025-01-01 12:00:00 ERROR error123 timeout,普通grep会输出整行,-o只输出error123。这在你需要从文本里“抽取”固定字段时特别有用。比如从日志中提取所有的IP地址、时间戳、订单号,配合sort和uniq可以做简单统计:
bash复制grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' access.log | sort | uniq -c | sort -rn
这条命令的输出是每个IP在access.log里出现的次数,从高到低排列,可以直接用来分析哪些IP的请求量最大。grep在这里已经不是过滤工具,而是数据提取工具了。
2.4 -r、-l、-c:从单文件到多文件的策略切换
-r递归搜索当前目录下的所有文件:
bash复制grep -rn "timeout" /etc/
递归搜索配置目录时,你会得到很多行结果,但如果你只想知道“哪些文件里有timeout配置”,-l更合适:
bash复制grep -rl "timeout" /etc/
只输出包含匹配的文件名,不输出具体行。这在批量排查配置散落位置时非常高效。-c则是统计每个文件中匹配的行数:
bash复制grep -rc "timeout" /etc/
多文件时输出格式为“文件名:匹配行数”。你可能会有疑问:grep -c统计的到底是“匹配的行数”还是“匹配的次数”?答案是行数。如果一行里出现了3次timeout,grep -c计为1,grep -o | wc -l计为3。这两个在数字上有很大差异,按需选择。
3. 管道组合中的分析思维:ps、tail、ss这些高频搭档的正确用法
grep单独使用能解决简单问题,但大部分真实场景里,它都是跟其他Linux命令组合在一起使用的。热搜词里那些典型的组合——ps aux | grep、sudo ss -lntp | grep 8080、tail -n 50 grep——不是随机凑出来的,而是一套固定的“信息流处理”思路。理解这个思路,比背下来几个组合命令更重要。
3.1 ps aux | grep 脚本名,PID为什么一直变
很多人在排查脚本是否在跑时会执行:
bash复制ps aux | grep test.sh
但有时会发现,同一个脚本PID为什么每次执行结果都不一样?再仔细一看,PID变的是grep进程本身,不是脚本。这背后的原因就是2.2节提到的grep -v grep问题:ps aux | grep test.sh会把正在执行这条命令的grep进程也匹配出来,而grep进程每次运行的PID都不同,所以看起来PID一直在变。解决办法就是过滤掉grep自身:
bash复制ps aux | grep test.sh | grep -v grep
如果你用的是pgrep或pidof,那就更优雅了,可以直接绕开这个自匹配问题:
bash复制pgrep -f test.sh
这里-f表示匹配完整命令行,而不是只匹配进程名。因为ps aux输出里包含完整的启动参数,如果你只按进程名匹配,很可能漏掉那些名字包含但参数不同的进程;反过来,按完整命令行匹配又可能误伤。所以我个人建议:pgrep -f用于启动命令足够独特的情况,否则还是老老实实用ps aux | grep再加一层过滤。
3.2 sudo ss -lntp | grep 8080:端口排查的固定配方
网络排查场景中,最经典的组合是查“某个端口到底被谁占用”。热搜词里的sudo ss -lntp | grep 8080是个非常标准的写法。拆开来看:
ss是socket statistics命令,用来显示网络套接字信息。-l表示只显示监听状态的端口。-n表示不做域名反解析,直接用IP和端口号显示,这样可以避免DNS解析导致的等待,速度更快。-t表示只看TCP连接。-p表示显示占用套接字的进程信息。
sudo则是必须的——因为-p要读取其他进程的信息,普通用户往往没有权限看到所有进程名,不加sudo你可能会看到进程名称显示为-或权限错误。
整条命令连起来就是:列出所有处于监听状态的TCP连接及其进程信息,再筛选出包含8080的行。结果大致长这样:
code复制LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=12345,fd=62))
看到这条输出,你就能确定8080端口被PID 12345的java进程占用。如果这个端口的状态是TIME_WAIT或CLOSE_WAIT,那处理方式完全不同,ss -lntp | grep 8080这种写法可能根本看不到它们,因为TIME_WAIT不是监听状态。排查这类问题时要区分:报错是“端口被占用”还是“端口连接不上”,前者看LISTEN状态,后者要看所有状态,把-l去掉:
bash复制sudo ss -tnp | grep 8080
3.3 tail -n 50 与 grep 搭配:从最新日志里找线索
tail -n 50表示查看文件最后50行,它是tail -n的简写,完整的写法是tail -n 50 file。日志文件通常按时间追加,最新内容在文件末尾,所以tail -n 50天然适合看“最近发生了什么”。跟grep组合:
bash复制tail -n 50 app.log | grep ERROR
这表示只取最后50行,再在里面筛ERROR。但这样做有个边界问题:如果错误发生在第51行,你根本看不到。所以实际排障时我习惯先看文件总行数,再决定tail范围:
bash复制wc -l app.log
tail -n 500 app.log | grep ERROR
如果是实时日志,我更喜欢用tail -f配合grep:
bash复制tail -f app.log | grep ERROR
-f表示follow,文件有新内容写入时立即输出。跟grep组合后你就能实时看到不断滚动的新错误。但注意grep的输出有缓冲,在管道里可能不会立即显示,这时可以加--line-buffered:
bash复制tail -f app.log | grep --line-buffered ERROR
--line-buffered强制grep每匹配到一行就刷新输出,而不是攒一批再一起输出。否则你会觉得命令没生效,其实数据还在缓冲区里。
3.4 多级管道:把grep当作信息流处理中的一个环节
管道最强大的地方是可以串联多个grep,等价于做了多次过滤。比如:
bash复制cat access.log | grep "2025-06-01" | grep "POST" | grep -v "/health"
这条命令先找到6月1日的日志,再从中筛出POST请求,再剔除健康检查路径。每一级grep都在缩小范围。但这事也不一定要用多个grep,很多时候用单个grep加正则也可以,例如:
bash复制grep "2025-06-01.*POST" access.log | grep -v "/health"
第一个grep用正则.*把时间戳和POST放在同一个模式里。如果行内时间戳和请求方法之间还有其他字符,.*能覆盖任意中间内容。两种写法都有各自优势:多个grep更容易读、更容易加-v排除;单个正则执行效率略高,因为grep进程少了一个。实际使用中我优先考虑可读性,可读性差的多级管道很容易在三个月后自己都看不懂。
除了grep串联,还可以把grep的结果传给其他命令进行二次处理。前面提到的grep -o ... | sort | uniq -c | sort -rn就是一个完整的统计流水线;grep ERROR app.log | wc -l则直接统计错误行数;grep -n "exception" app.log | head -20只取前20条结果,避免报错太多刷屏。正是这种“文本流处理”的能力,让grep在Linux命令行体系中处于绝对核心的位置。
4. 正则表达式的三个坑:转义、贪婪匹配与字符集陷阱
grep真正强大的地方在于它支持正则表达式。基础字符串匹配解决的是“有没有这个字”,正则解决的是“符不符合某种模式”。但正则也是新手最容易栽跟头的地方。我总结了三个最常见的坑,每一个都在实际排障中浪费过我的时间。
4.1 转义问题:括号和加号为什么经常不生效
用grep搜IP地址,新手通常会写:
bash复制grep "192.168.1.1" access.log
这里.在正则里表示“任意一个字符”,所以192.168.1.1实际上能匹配192x168x1x1这种字符串。虽然大多数情况下文本里就是IP,你不会感觉到问题,但如果你写的是grep "\.(com|cn)",没有转义括号和点号,结果就会完全跑偏。
另一个高频坑是加号+。在基础正则(BRE)模式下,+不作为量词,它就是一个普通字符;只有在扩展正则(ERE)模式下,+才表示“前面的字符出现一次或多次”。grep默认使用BRE模式,所以:
bash复制grep "error[0-9]+" app.log
在默认模式下+匹配的是字面加号,[0-9]+根本不会匹配error123中的123。解决办法是给grep加-E参数,或者使用egrep(虽然在现代Linux里egrep已经不建议使用,但它等价于grep -E):
bash复制grep -E "error[0-9]+" app.log
同理,?、|、(、)这些字符在BRE模式下都有转义语义:要使用它们的分组或或运算能力,要么加-E,要么用反斜杠转义。我的习惯是:凡是要写稍微复杂一点的正则,一律加-E,省得再去记那些反斜杠。
4.2 贪婪匹配:.*为什么会吞掉不该吞的内容
在正则里,.*表示“任意长度的任意字符”。问题在于.*是贪婪的,它会尽可能多地匹配字符。举例,日志行:
code复制2025-06-01 10:00:00 INFO message a=1, b=2, c=3
如果你想提取a=后面的值:
bash复制grep -o "a=.*" app.log
它会匹配到a=1, b=2, c=3,因为你没指定在哪个字符处停止。如果想要a=1,正则应写成:
bash复制grep -o "a=[0-9]*" app.log
这就是所谓“贪婪”的本质:.*会延伸到行尾。很多人在日志中只想提取某个字段,结果把整行都输出出来了,就是因为忽略了贪婪匹配。grep本身没有非贪婪模式(不像Perl正则里的*?),所以要通过更精确的字符集来限定范围。
4.3 字符集陷阱:[^...]的含义和-的位置
字符集[...]内部有个特殊规则:如果[后面紧跟^,表示“不包含该字符集”;如果-出现在字符集首尾,它表示字面量减号;出现在中间则是一个范围符。举例:
bash复制grep -E "a[0-9]{5}" app.log # 匹配 a + 5位数字
grep -E "[^a]" app.log # 匹配不含字母a的行
两则含义完全不同。第一个{5}表示前一个字符出现5次,这是扩展正则的量词;第二个[^a]表示“任何不是a的字符”,注意[^a]并不能匹配“不含a的行”——如果行里既有b又有a,这一行仍然会匹配,因为[^a]只需要在某个位置上找到一个非a字符即可。很多人误以为[^a]等价于“整行没有a”,这是个常见误解。要做到“整行不含a”,应该用grep -v a。
字符集里的-也容易搞混。[a-z]是小写字母范围,[a-]则是字母a和减号。如果你要搜索的字符串里包含减号,最好把它放在字符集的最前面或最后面,避免被当成范围符。
4.4 正则优先级与分组使用
写复杂正则时优先级也很重要。正则的优先级从高到低大致是:字符组[]、量词* + ? {}、连接(先后顺序)、锚点^ $、或运算|。因为|优先级较低,abc|def等价于(abc)|(def),如果想让ab后面跟c或d,得写成ab(c|d)。用grep -E时:
bash复制grep -E "^ERROR|FATAL" app.log
这条命令匹配的是“以ERROR开头的行”或者“包含FATAL的行”,而不是“以ERROR或FATAL开头的行”。后者应该写成:
bash复制grep -E "^(ERROR|FATAL)" app.log
这两种写法结果差异巨大。我在实际开发中写出过不少这种优先级错误的表达式,排查半天才发现是|的作用范围超出了预期。建议大家在写正则时,把每个|都配上括号,明确划分边界,不要依赖默认优先级。
5. 经典排障场景拆解:进程、端口、日志排查的一线实战
讲了一堆参数和正则,这一节我把它落到具体排障场景里。每一个场景都是我实际处理过或反复看到过的类型,按照“发现问题→验证手段→缩小范围→定位根因”的流程来拆解。
5.1 场景一:服务没起来,到底是不是在运行?
小王部署了一个Java服务,启动脚本执行后没有任何报错,但访问页面超时。第一件事就是确认进程是否存在:
bash复制ps aux | grep app.jar | grep -v grep
如果输出为空,说明进程没起来;如果输出了进程,再判断进程状态。注意ps aux输出里的STAT列,R表示运行,S表示睡眠,T表示停止,Z表示僵尸。如果看到一个Z,说明进程已经退出但父进程没有回收它,端口虽然可能释放了,但进程表里还占着一个PID。遇到僵尸进程,光杀僵尸本身没用,得处理它的父进程。
如果没有输出,最好的方式不是反复执行启动脚本,而是先确认启动日志:
bash复制tail -n 100 /path/to/app/logs/error.log | grep -E "ERROR|Exception"
几乎每次都能在启动日志里找到原因。常见的是端口被占用、配置文件解析失败、依赖的数据库连不上。一个容易忽略的点:某些服务启动失败后会立刻退出,日志还没刷新到文件里,所以用tail看可能什么都没有。这时应该看原本输出到控制台的启动信息,或者用nohup.log之类的输出重定向文件。
5.2 场景二:8080端口被占用,到底是谁占的?
这个场景在热搜词里出现过,也是面试题常客。完整排查流程应该是:
bash复制sudo ss -lntp | grep 8080
看到LISTEN行后,确认PID。如果占用者不是本服务,你可能需要进一步看它是什么进程:
bash复制ps -fp 12345
-f显示完整信息,-p指定PID,这样能看到进程的启动命令、时间、父进程。如果ss输出里进程名是-,多半是因为权限不足,记得加sudo;如果是在容器或某些受限环境里,ss可能不显示进程信息,这时可以改用:
bash复制sudo lsof -i:8080
lsof列出打开文件的进程,-i:8080表示筛选使用8080端口的进程。两条命令各有优劣,ss适合快速查看端口状态,lsof适合查看进程与文件的关联。但lsof不是所有Linux系统都预装,ss是iproute2包的一部分,基本都会带。
5.3 场景三:日志疯狂刷错误,需要定位首次报错时间
日志文件很大,比如几个G,直接tail -n 50只能看到最后50行,可能都是同一类错误刷屏。你想知道这类错误是从什么时间开始出现的。处理思路是分两步:
先用-c统计大致出现次数:
bash复制grep -c "OutOfMemoryError" app.log
再用head从文件开头搜索第一次出现的位置:
bash复制grep -n "OutOfMemoryError" app.log | head -1
-n输出行号,head -1取第一行,这样能找到错误首次出现的行号,再结合该行的时间戳就能确定开始时间。如果你需要看这个时间点前后的日志,就用行号配合sed:
bash复制sed -n '1000,1060p' app.log
sed的1000,1060p表示打印第1000到1060行。grep定位行号,sed精确截取,这个组合在处理大日志时非常高效,比直接打开文件翻找快得多。
5.4 场景四:过滤日志中的“成功日志”,看真正失败的业务
有时候日志里99%都是成功请求,只有少数失败请求,grep -v反而比grep更有用。比如你只想看到业务失败的记录:
bash复制grep -v "response_code.*200" app.log
但这里有个坑:如果日志里既有response_code:200也有response_code:2000,grep -v "200"会把2000那段也排除掉,因为它们都包含子串200。这时必须用带边界的匹配:
bash复制grep -vE "response_code[=:]200([^0-9]|$)" app.log
([^0-9]|$)表示200后面不能是数字,要么是其他字符要么是行尾。这种带边界的写法在过滤端口、状态码、版本号时特别重要,因为数字子串很容易误伤。
5.5 场景五:多个文件中找出配置差异
排查多台服务器配置不一致时,grep -r派上大用场。比如想确认所有服务器上nginx的worker_processes配置:
bash复制grep -rn "worker_processes" /etc/nginx/
如果某些服务器用的不是nginx默认路径,可能搜不到。一个更稳妥的做法是明确指定所有可能路径:
bash复制grep -rn "worker_processes" /etc/nginx/ /usr/local/nginx/conf/ 2>/dev/null
2>/dev/null把“目录不存在”的错误信息丢弃,避免刷屏。-n输出行号,你就能直接对比不同文件之间的配置差异。如果文件数量太多,还可以加-l只列文件名,快速确定“哪些服务器需要改”。
6. 五条老手的实用建议与grep的边界意识
最后分享几条我平时使用grep的实践心得,不算多高深,但每一条都是踩过坑之后总结出来的。
建议一:频繁使用的长命令,给grep配置个别名。 我常用的两个别名是:
bash复制alias grep='grep --color=auto'
alias grep-e='grep -E --color=auto'
--color=auto能让匹配部分高亮显示,在终端里一眼就能看到命中位置。--color=auto在管道里会自动关闭颜色,不会影响重定向到文件或传给其他命令。
建议二:用grep之前先想想有没有更合适的工具。 grep不是万能的。文件内容不需要过滤而只是查看时,用tail、cat、less更合适;要在日志里按时间范围筛选时,grep缺乏时间语义,用awk按字段比较更精准;要处理JSON格式的日志时,jq远比正则好用。grep擅长的是“行级文本过滤”,一旦日志格式变成多层嵌套JSON,正则写起来又长又脆弱,jq可以几行解决问题。我见过有人用grep解析nginx日志里的JSON字段,写出来的正则维护成本极高,换用awk或jq会清爽很多。
建议三:grep的退出码是脚本编写者的好朋友。 大量运维脚本里都有这种模式:
bash复制if grep -q "success" /var/log/xxx.log; then
echo "deploy success"
else
echo "deploy failed"
fi
-q参数表示quiet模式,不输出任何内容,只返回退出码。grep一旦匹配到就返回0,脚本就可以据此做条件判断。这样写比解析输出文本判断“有没有success字符串”要可靠得多。
建议四:注意二进制文件和乱码。 默认情况下grep会把二进制文件当作文本处理,但输出可能包含乱码。加上-I参数可以让grep直接跳过二进制文件,避免终端被控制字符搞乱。检查某个目录里有哪些文本文件包含关键字时,这个参数尤其有用。
建议五:先小范围测试,再全库执行。 写复杂正则时,先在一个小文件或head -100的输出上测试,确认匹配结果符合预期,再对整个目录递归执行。原因很简单:递归搜索一旦正则写错,你会得到大量无关结果或漏掉真正需要的结果,排查起来更费时间。
bash复制head -100 app.log | grep -E "^(ERROR|FATAL)"
# 确认无误后再
grep -rE "^(ERROR|FATAL)" /var/log/
边界意识。 grep处理的是文本流,它没有内核对“文件类型”的感知,也不理解“时间戳”“端口号”“IP地址”这些业务语义。一切看似“智能”的过滤,本质上都是模式匹配的结果。因此在使用grep前,你应该先问自己:我想要的到底是什么?是包含某字符串的行,还是满足某种时间范围,还是某个字段值符合条件?如果你发现用grep实现某个需求非常别扭,那多半是选错了工具。在这些时候,sed、awk、perl乃至专门的日志分析工具才是更合适的选择。
但无论如何,grep依然是我在Linux上最依赖的命令之一。它简单、快速、无处不在,几十行的配置文件和几个G的日志在它面前都是一条条待检查的文本行。把它的参数、正则表达式和管道组合吃透,你处理Linux问题的效率会提升一个明显的档次。现在,找一台服务器,把这一节里的命令逐个试一遍,让手指记住它们,远比你收藏这篇文章有用得多。
