Como fazer o grep ignorar linhas sem arrastar o caractere de nova linha

3

Gostaria de fazer um arquivo grep para uma string, mas ignorar as correspondências nas linhas que não terminam com um caractere de nova linha à direita. Em outras palavras, se o arquivo não terminar com um caractere de nova linha, eu gostaria de ignorar a última linha do arquivo.

Qual é a melhor maneira de fazer isso?

Encontrei esse problema em um script python que chama o grep por meio do módulo subprocess para filtrar um arquivo de log de texto grande antes de processá-lo. A última linha do arquivo pode estar no meio da gravação, caso em que não quero processar essa linha.

    
por dshin 13.06.2018 / 19:17

4 respostas

1

grep é explicitamente definido para ignorar novas linhas, então você não pode realmente Use isso. sed sabe internamente se a linha atual (fragmento) termina em uma nova linha ou não, mas não consigo ver como ela pode ser coagida para revelar essa informação. awk separa os registros por novas linhas ( RS ), mas realmente não se importa se havia um, a ação padrão de print é imprimir uma nova linha ( ORS ) no final em qualquer caso.

Portanto, as ferramentas usuais não parecem muito úteis aqui.

No entanto, sed sabe quando está trabalhando na última linha, por isso, se você não se importar em perder a última linha intacta nos casos em que uma parcial não é vista, você pode ter apenas sed delete pensa que é o último. Por exemplo,

sed -n -e '$d' -e '/pattern/p'  < somefile                   # or
< somefile sed '$d' | grep ...

Se isso não for uma opção, então sempre haverá Perl. Isso deve imprimir apenas as linhas que correspondem a /pattern/ e ter uma nova linha no final:

perl -ne 'print if /pattern/ && /\n$/'
    
por 13.06.2018 / 20:19
4

Com gawk (usando EREs semelhantes a grep -E ):

gawk '/pattern/ && RT' file

RT in gawk contém o que corresponde a RS do separador de registro. Com o valor padrão de RS ( \n ) que seria \n , exceto para um último registro não delimitado, em que RT estaria vazio.

Com perl (perl REs semelhante a grep -P , quando disponível):

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

Observe que, ao contrário de gawk ou grep , perl , por padrão, funciona em bytes e não em caracteres. Por exemplo, o operador . regexp corresponderia a cada um dos dois bytes de um £ codificado em UTF-8. Para trabalhar com caracteres de acordo com a definição de caracteres do local como awk / grep , você usaria:

perl -Mopen=locale -ne 'print if /pattern/ && /\n\z/'
    
por 13.06.2018 / 20:29
1

Algo como isso poderia fazer o trabalho:

#!/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

Contamos com a seguinte característica de substituição de comando descrito em man bash :

Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.

    
por 13.06.2018 / 19:58
1

Se você precisar de velocidade, usar o PCRE (ou alguma outra biblioteca de regex possivelmente mais rápida) de C permitiria o uso de uma expressão regular e uma verificação se existe uma nova linha. Desvantagens: novo código para manter e depurar, tempo para reimplementar partes de grep ou perl dependendo da complexidade da expressão ou se recursos como --only-matching forem usados.

#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);
}

Isso é cerca de 49% mais rápido que perl -ne 'print if /.../ && /\n\z/' .

    
por 14.06.2018 / 20:10

Tags