我想在文件中查找字符串,但忽略不以尾随换行符结尾的行上的任何匹配项。换句话说,如果文件不以换行符结尾,我想忽略文件的最后一行。
做这个的最好方式是什么?
我在 python 脚本中遇到了这个问题,该脚本通过subprocess
模块调用 grep 在处理之前过滤大型文本日志文件。文件的最后一行可能是写入中,在这种情况下我不想处理该行。
答案1
使用gawk
(使用类似于 的 ERE grep -E
):
gawk '/pattern/ && RT' file
RT
in包含记录分隔gawk
符匹配的内容。RS
使用默认值RS
( \n
),\n
除了非分隔的最后一条记录之外,该记录RT
将为空。
使用perl
(perl RE 与可用的情况类似grep -P
):
perl -ne 'print if /pattern/ && /\n\z/'
gawk
请注意,与or相反grep
,perl
默认情况下适用于字节而不是字符。例如,它的.
正则表达式运算符将匹配 UTF-8 编码的两个字节中的每一个£
。为了让它按照区域设置的字符定义(例如awk
/ )处理字符grep
,您可以使用:
perl -Mopen=locale -ne 'print if /pattern/ && /\n\z/'
答案2
grep
明确地定义的忽略换行符,所以你不能真正使用它。sed
在内部知道当前行(片段)是否以换行符结尾,但我看不出如何强制它透露该信息。awk
用换行符 ( RS
) 分隔记录,但并不真正关心是否有换行符,默认操作是在任何情况下都在末尾print
打印换行符 ( )。ORS
所以常用的工具在这里似乎没有太大帮助。
但是,sed
它确实知道它何时在最后一行上工作,因此如果您不介意在看不到部分行的情况下丢失最后一行完整的行,则可以sed
删除它认为是最后一行的内容。例如
sed -n -e '$d' -e '/pattern/p' < somefile # or
< somefile sed '$d' | grep ...
如果这不是一个选择,那么总是有 Perl。这应该只打印匹配的行/pattern/
,并在末尾有一个换行符:
perl -ne 'print if /pattern/ && /\n$/'
答案3
像这样的东西可以完成这项工作:
#!/usr/bin/env sh
if [ "$(tail -c 1 FILE)" = "" ]
then
printf "Trailing newline found\n"
# grep whole file
# grep ....
else
printf "No trailing newline found\n"
# ignore last line
# head -n -1 FILE | grep ...
fi
我们依赖于以下描述的命令替换特征man bash
:
Bash 通过执行命令并将命令替换替换为命令的标准输出来执行扩展,与任何 删除尾随换行符。
答案4
如果您需要速度,那么使用 C 中的 PCRE(或其他可能更快的正则表达式库)将允许使用正则表达式并检查是否有换行符。缺点:需要维护和调试新代码,重新实现部分内容的时间grep
或perl
取决于表达式的复杂性或是否--only-matching
使用诸如此类的功能。
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pcre.h>
#define MAX_OFFSET 3
int main(int argc, char *argv[])
{
// getline
char *line = NULL;
size_t linebuflen = 0;
ssize_t numchars;
// PCRE
const char *error;
int erroffset, rc;
int offsets[MAX_OFFSET];
pcre *re;
if (argc < 2) errx(1, "need regex");
argv++;
if ((re = pcre_compile(*argv, 0, &error, &erroffset, NULL)) == NULL)
err(1, "pcre_compile failed at offset %d: %s", erroffset, error);
while ((numchars = getline(&line, &linebuflen, stdin)) > 0) {
if (line[numchars-1] != '\n') break;
rc = pcre_exec(re, NULL, line, numchars, 0, 0, offsets, MAX_OFFSET);
if (rc > 0) fwrite(line, numchars, 1, stdout);
}
exit(EXIT_SUCCESS);
}
这比perl -ne 'print if /.../ && /\n\z/'
.