如何使 grep 忽略不带尾随换行符的行

如何使 grep 忽略不带尾随换行符的行

我想在文件中查找字符串,但忽略不以尾随换行符结尾的行上的任何匹配项。换句话说,如果文件不以换行符结尾,我想忽略文件的最后一行。

做这个的最好方式是什么?

我在 python 脚本中遇到了这个问题,该脚本通过subprocess模块调用 grep 在处理之前过滤大型文本日志文件。文件的最后一行可能是写入中,在这种情况下我不想处理该行。

答案1

使用gawk(使用类似于 的 ERE grep -E):

gawk '/pattern/ && RT' file

RTin包含记录分隔gawk符匹配的内容。RS使用默认值RS( \n),\n除了非分隔的最后一条记录之外,该记录RT将为空。

使用perl(perl RE 与可用的情况类似grep -P):

perl -ne 'print if /pattern/ && /\n\z/'

gawk请注意,与or相反grepperl默认情况下适用于字节而不是字符。例如,它的.正则表达式运算符将匹配 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(或其他可能更快的正则表达式库)将允许使用正则表达式并检查是否有换行符。缺点:需要维护和调试新代码,重新实现部分内容的时间grepperl取决于表达式的复杂性或是否--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/'.

相关内容