最常用可靠方式是grep -o配合wc -l,加-w才按完整单词统计,否则匹配子串;含特殊字符需转义或单引号;中文等非ascii建议用awk;需区分统计字符串、单词或含词行数。

直接回答:用 grep -o 配合 wc -l 是最常用、最可靠的方式,但必须加 -w 才能按“完整单词”统计,否则会把子串也算进去(比如搜 log 会匹配 logging)。
grep -o 统计关键词出现次数(含子串)
这是默认行为,适合统计字符串而非单词。例如日志里查 timeout,不管它是不是独立单词都算:
-
grep -o "timeout" /var/log/messages | wc -l—— 每次匹配输出一行,wc -l数行数即总次数 - 加
-i可忽略大小写:grep -oi "error" app.log | wc -l - 如果关键词含特殊字符(如
.、*),要用单引号或转义,否则被 shell 解析:grep -o '\$HOME' config.sh | wc -l - 注意:不加
-w时,grep -o "cat"会匹配concat中的cat,这不是你想要的“单词”统计
grep -w 统计完整单词出现次数
-w 要求匹配边界(空格、换行、标点等),避免误匹配。但注意它只作用于整个 grep 行为,和 -o 一起用才真正实现“单词级计数”:
- 正确写法:
grep -ow "cat" animals.txt | wc -l—— 只计cat作为独立词出现的次数 - 错误写法:
grep -w "cat" animals.txt | wc -l—— 这是统计“含单词 cat 的行数”,不是“cat 出现多少次”,一行有 3 个cat也只算 1 行 -
-w对非 ASCII 字符(如中文、emoji)无效,它依赖 GNU libc 的单词边界定义,中文文本建议用awk或rg
awk gsub 实现高精度频次统计
当需要跨字段、多模式或规避 grep 边界限制时,awk 更可控。尤其适合处理带标点、混合格式的日志:
- 统计每行中
debug出现次数并累加:awk '{n += gsub(/debug/, "&")} END {print n+0}' app.log - 只在第 4 列等于
"500"的行里统计timeout:awk '$4 == "500" {n += gsub(/timeout/, "&")} END {print n+0}' access.log -
gsub返回替换次数,&表示原匹配内容,不会改变输入流;n+0确保空文件输出0而非空行 - 正则中写
/\bdebug\b/可模拟-w效果,但需注意\b在不同 locale 下行为可能不一致
多个文件批量统计与常见陷阱
别直接用 grep -o keyword *.log | wc -l —— 这会把所有文件输出混在一起计数,且丢失文件名信息:
- 想看每个文件各自次数:
grep -ow "fail" *.log | cut -d: -f1 | sort | uniq -c - 想汇总总数且避免路径干扰:
grep -ow "fail" *.log 2>/dev/null | wc -l(2>/dev/null屏蔽“无匹配文件”警告) - 文件名含空格?用
find . -name "*.log" -exec grep -ow "fail" {} \; | wc -l - 大文件慎用
grep -o:它会把每个匹配项输出一行,内存占用随次数线性增长;超百万次建议用awk流式处理
真正麻烦的不是命令怎么写,而是你没想清楚要统计的是“字符串出现次数”、“单词出现次数”,还是“含该词的行数”。三者结果可能差几倍,尤其在日志里一个 ERROR 行常带多个堆栈帧。动手前先用 head -20 file | grep -o "xxx" 看一眼实际匹配效果,比反复试错快得多。











