Limitar o contexto grep a N caracteres on-line

31

Eu tenho que grep através de alguns arquivos JSON nos quais os comprimentos de linha excedem alguns milhares de caracteres. Como posso limitar o grep para exibir o contexto de até N caracteres à esquerda e à direita da partida? Qualquer ferramenta que não seja grep também ficaria bem, desde que disponível em pacotes Linux comuns.

Isso seria exemplo de saída, para o switch grep imaginário Ф :

$ grep -r foo *
hello.txt: Once upon a time a big foo came out of the woods.

$ grep -Ф 10 -r foo *
hello.txt: ime a big foo came of t
dotancohen
fonte
3
stackoverflow.com/questions/8101701/…
Ciro Santilli escreveu:
11
Possível duplicata de Como exibir as linhas 2-4 após cada resultado grep?
Ciro Santilli escreveu
3
Não é uma duplicata. Trata-se de ± caracteres, mas sua alternativa sugerida é de ± linhas. (A sua referência para stackoverflow é bom, no entanto.)
roaima

Respostas:

22

Com o GNU grep:

N=10; grep -roP ".{0,$N}foo.{0,$N}" .

Explicação:

  • -o => Imprima apenas o que você combinou
  • -P => Use expressões regulares no estilo Perl
  • O regex diz corresponder 0 aos $Ncaracteres fooseguido por 0 aos $Ncaracteres.

Se você não possui GNU grep:

find . -type f -exec \
    perl -nle '
        BEGIN{$N=10}
        print if s/^.*?(.{0,$N}foo.{0,$N}).*?$/$ARGV:$1/
    ' {} \;

Explicação:

Como não podemos mais confiar em grepser o GNU grep, utilizamos findpara procurar arquivos recursivamente (a -ração do GNU grep). Para cada arquivo encontrado, executamos o snippet Perl.

Comutadores Perl:

  • -n Leia o arquivo linha por linha
  • -l Remova a nova linha no final de cada linha e coloque-a novamente ao imprimir
  • -e Trate a seguinte string como código

O snippet Perl está fazendo essencialmente a mesma coisa que grep. Começa definindo uma variável $Npara o número de caracteres de contexto que você deseja. Os BEGIN{}meios que isso é executado apenas uma vez no início da execução, não uma vez para cada linha em cada arquivo.

A instrução executada para cada linha é imprimir a linha se a substituição de regex funcionar.

A regex:

  • Faça a correspondência de qualquer coisa antiga preguiçosamente 1 no início da linha ( ^.*?) seguida por, .{0,$N}como no grepcaso, fooseguido de outra .{0,$N}e, finalmente, faça a correspondência de qualquer coisa antiga preguiçosamente até o final da linha ( .*?$).
  • Nós substituímos isso por $ARGV:$1. $ARGVé uma variável mágica que contém o nome do arquivo atual que está sendo lido. $1é o que os parênteses combinavam: o contexto neste caso.
  • As correspondências preguiçosas em cada extremidade são necessárias porque uma correspondência gananciosa consumiria todos os caracteres antes foosem deixar de corresponder (já que .{0,$N}é permitido corresponder zero vezes).

1 Ou seja, prefira não corresponder a nada, a menos que isso cause uma falha na correspondência geral. Em resumo, combine o mínimo de caracteres possível.

Joseph R.
fonte
Muito bom obrigado. Isso tem a desvantagem de destacar toda a saída, não apenas o texto pesquisado, mas que pode ser contornado anexando | grep fooao final (no entanto, perdendo o destaque do nome do arquivo no processo).
dotancohen
11
@dotancohen Eu acho que você não pode vencer todos eles :)
Joseph R.
Com GNU, grepvocê pode especificar cores / aplicativos correspondentes com base em sinalizadores aplicados por variáveis ​​de ambiente. então talvez você possa ganhar todos eles (sem promessas - nem mesmo certo se funcionaria nesse caso), mas eu pessoalmente não vejo a relevância aqui ... de qualquer maneira ... continue jogando.
mikeserv
Boa resposta. Apenas uma observação: usando zsheu sou incapaz de fazê-lo passar N = 10, como no exemplo. No entanto, ele funciona se eu export N=10antes de executar o comando. Alguma idéia de como ajustar o exemplo para trabalhar com o zsh?
Gabe Kopley
Ouperl -lne 'print "$ARGV: $_" for /.{0,10}foo.{0,10}/g'
Stéphane Chazelas 19/04/19
20

Tente usar este:

grep -r -E -o ".{0,10}wantedText.{0,10}" *

-E diz que você deseja usar regex estendido

-o informa que você deseja imprimir apenas a correspondência

-r grep está procurando resultado recursivamente na pasta

REGEX:

{0,10} informa quantos caracteres arbitrários você deseja imprimir

. representa um personagem arbitrário (um personagem em si não era importante aqui, apenas seu número)

Edit: Ah, entendo, que Joseph recomenda quase a mesma solução que eu: D

Eenoku
fonte
Obrigado. Embora seja essencialmente a mesma solução, é inspirador que este seja o melhor método quando duas pessoas o recomendam independentemente.
dotancohen
Você é bem-vindo, comunidade Unix simplesmente deve cooperar, isso é o que estamos :-)
Eenoku
2
Embora sejam parecidas, a resposta aceita não funcionou para mim (ainda produzia longas linhas), mas uma que funcionou. O truque com N = 10 não funciona com um shell bash.
meesern
no cygwin -E é significativamente mais rápido que -P.
Bob Stein
2

Retirado de: http://www.topbug.net/blog/2016/08/18/truncate-long-matching-lines-of-grep-a-solution-that-preserves-color/ e https: // stackoverflow. com / a / 39029954/1150462

A abordagem sugerida ".{0,10}<original pattern>.{0,10}"é perfeitamente boa, exceto pelo fato de que a cor de destaque é muitas vezes confusa. Eu criei um script com uma saída semelhante, mas a cor também é preservada:

#!/bin/bash

# Usage:
#   grepl PATTERN [FILE]

# how many characters around the searching keyword should be shown?
context_length=10

# What is the length of the control character for the color before and after the matching string?
# This is mostly determined by the environmental variable GREP_COLORS.
control_length_before=$(($(echo a | grep --color=always a | cut -d a -f '1' | wc -c)-1))
control_length_after=$(($(echo a | grep --color=always a | cut -d a -f '2' | wc -c)-1))

grep -E --color=always "$1" $2 | grep --color=none -oE ".{0,$(($control_length_before + $context_length))}$1.{0,$(($control_length_after + $context_length))}"

Supondo que o script seja salvo como grepl, grepl pattern file_with_long_linesdeve exibir as linhas correspondentes, mas com apenas 10 caracteres ao redor da sequência correspondente.

xuhdev
fonte
0

Tubulação stdout para cutcom a -bbandeira; você pode instruir a saída do grep apenas para bytes 1 a 400 por linha.

grep "foobar" * | cut -b 1-400
Eric Leschinski
fonte