grep -c 同一行多次匹配只计一次,统计结果偏小
GNU grep 的 -c 统计匹配行数。用 -o 分离非空匹配后计数,并区分无匹配、读取失败,以及完整单词、重叠和空匹配的不同要求。
一行日志里有三个 ERROR,grep -c 'ERROR' app.log 却只增加 1。把整个文件逐处核对后,统计结果明显偏小,但命令没有报错。这通常是计数单位选错了:-c 统计匹配行数,同一行出现多少次都只算一行。
下面针对 GNU grep,示例按 GNU grep 3.11 的选项行为说明。输入是编码与当前 locale 相容的普通文本,搜索内容是不含换行的非空固定字符串。二进制记录、跨行模式、空匹配和重叠匹配需要另外确定规则,不能把下面的数字泛称为所有可能的匹配数。
先把匹配项拆开看
假设文件内容是:
ERROR ERROR
OK
ERROR
以下查询得到的匹配行数预期为 2:
grep -c -F -e 'ERROR' -- app.log
要按每一次非重叠出现计数,先用 -o 只输出匹配部分:
grep -o -F -e 'ERROR' -- app.log
预期输出三行 ERROR。于是可以把这三行交给 wc -l:
grep -o -F -e 'ERROR' -- app.log | wc -l
预期结果为 3。-F 表示固定字符串;如果要查 a.b 这样的字面内容,句点不会被当作正则通配符。-e 显式给出搜索内容,-- 后面是文件路径。替换关键词时应保留引用,不要把含空格的内容拆成多个参数。
wc -l 数的是输入中的换行。这里每个非空匹配都由 grep -o 单独输出成一行,因而能够用于计数。它不是直接统计原日志的行数。
接入脚本时,不把读取失败当成零次
短管道适合交互检查,但默认管道状态可能只反映最后的 wc。输入文件不存在时,grep 报错,wc 仍可能打印 0;这个 0 不能作为“没有出现”的业务结果。
对大小受控、只统计一个短固定字符串的文本,可以先接住 grep 的结果并判断状态,再计数。下面是完整 Bash 脚本片段,失败时退出当前脚本,不要直接粘贴到需要保留的交互会话:
input='./app.log'
needle='ERROR'
if matches=$(grep -o -F -e "$needle" -- "$input"); then
printf '%s\n' "$matches" | wc -l
else
status=$?
if [ "$status" -eq 1 ]; then
printf '0\n'
else
printf '%s\n' 'Search failed; do not use a partial count.' >&2
exit "$status"
fi
fi
GNU grep 的状态 0 表示选中了行,1 表示没有选中行,2 表示发生错误。示例只把 1 转换为零次,其他失败保留为失败;grep 自己的诊断仍写到标准错误。不要加 -q,它会抑制匹配输出,也有找到匹配后提前结束的行为。
这个片段要求 needle 非空且不含换行,输入不含 NUL。命令替换会删掉输出末尾换行,随后 printf '%s\n' 补一个换行;这里保存的是已经拆成行的固定字符串匹配列表,不是尝试逐字节保存原文件。匹配量很大时不应全部塞进变量,应改成有独立错误检查的流式处理或受控中间文件流程。
明确是否需要单词、重叠或空匹配
查固定字符串 ERROR 时,ERROR_CODE 中的那一段也会计入。如果要求完整单词,可以评估加 -w,但 GNU grep 的单词字符包含字母、数字和下划线,并不等于所有业务语言的分词规则。修改这个条件会改变统计含义,应先用业务样例验证。
普通 -o 计数不适合要求重叠位置的任务。例如在 ababa 中寻找 aba,是否把起点 1 和起点 3 都算进去,必须先确定。若需要两个重叠起点,应使用明确支持这种遍历方式的程序,不能假定上面的管道已经覆盖。
空匹配也不同。正则 ^ 能选中非空文件的每一行,但 -o 只打印非空的匹配部分,用它统计行首位置会得到错误的计数方式。不要把 -F 随意换成正则选项后继续沿用原来的验收结果。
出现二进制匹配提示或编码诊断时,先解决输入格式判断。不能一边让 grep 省略匹配文本,一边仍把输出行数当作完整结果,也不要为了得到数字直接丢弃错误输出。
用同一行多次出现的样例验收
至少检查一行没有匹配、一行只有一次、一行有多次,以及整个文件没有匹配这几种情况。再故意使用不存在的测试路径,确认脚本报失败,而非返回可供下游使用的 0。正式核对时使用稳定的文件副本,避免日志写入或轮转让两次查询面对不同内容。
这些查询不会改写输入。保存原统计脚本与一组预期结果即可回退;若后续报表原本使用的是“发生错误的行数”,不要直接把它替换为“错误标记出现次数”,两者可能都是有效指标,但不能共用同一个含义不明的字段。


