Ограничить контекст grep до N символов в строке

31

Я должен пролистать некоторые JSON-файлы, в которых длина строк превышает несколько тысяч символов. Как я могу ограничить grep для отображения контекста до N символов слева и справа от совпадения? Подойдет любой инструмент, кроме grep, если он доступен в обычных пакетах Linux.

Это будет пример вывода для воображаемого переключателя grep Ф :

$ 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
источник
3
stackoverflow.com/questions/8101701/…
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件
1
Возможный дубликат Как отображать строки 2-4 после каждого результата grep?
Чиро Сантилли 事件 改造 中心 法轮功 六四 事件
3
Не дубликат Это около ± символов, но предлагаемая вами альтернатива около ± строк. (Ваша ссылка на stackoverflow хороша, хотя.)
roaima

Ответы:

22

С GNU grep:

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

Объяснение:

  • -o => Печатайте только то, что вам подходит
  • -P => Используйте регулярные выражения в стиле Perl
  • Регулярное выражение говорит, что соответствует 0 $Nсимволам, затем fooследует 0 $Nсимволам.

Если у вас нет GNU grep:

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

Объяснение:

Поскольку мы больше не можем полагаться на grepто grep, чтобы быть GNU , мы используем findрекурсивный поиск файлов ( -rдействие GNU grep). Для каждого найденного файла мы выполняем фрагмент Perl.

Perl переключатели:

  • -n Читать файл построчно
  • -l Удалите новую строку в конце каждой строки и вставьте ее обратно при печати
  • -e Рассматривайте следующую строку как код

Фрагмент Perl делает практически то же самое, что и grep. Он начинается с установки переменной $Nна количество символов контекста, которые вы хотите. Это BEGIN{}означает, что это выполняется только один раз в начале выполнения, а не один раз для каждой строки в каждом файле.

Оператор, выполняемый для каждой строки, должен напечатать строку, если подстановка регулярного выражения работает.

Регулярное выражение:

  • Сопоставьте любую старую вещь лениво 1 в начале строки ( ^.*?), затем, .{0,$N}как в grepслучае, за которой fooследует другая, .{0,$N}и, наконец, сопоставьте любую старую вещь лениво до конца строки ( .*?$).
  • Мы заменим это на $ARGV:$1. $ARGVявляется магической переменной, которая содержит имя текущего файла для чтения. $1вот что совпало с паренсом: контекст в данном случае.
  • Ленивые совпадения на обоих концах требуются, потому что жадное совпадение сожрало бы все символы раньше fooбез совпадения (так .{0,$N}как разрешено совпадать ноль раз).

1 То есть, предпочитайте не сопоставлять что-либо, если только это не приведет к сбою в общем совпадении. Короче говоря, подберите как можно меньше символов.

Джозеф Р.
источник
Очень хорошо, спасибо. Этот недостаток заключается в выделении всего вывода, а не только искомого текста, но это можно обойти, добавив | grep fooв конец (однако при этом теряется выделение имени файла в процессе).
Dotancohen
1
@dotancohen Я думаю, вы не можете победить их всех :)
Джозеф Р.
С GNU grepвы можете указать соответствие цветов / приложений на основе флагов, применяемых через переменные среды. так что, возможно, даже вы могли бы выиграть их всех (без обещаний - даже не уверен, что это сработает в этом случае), но я лично не вижу здесь уместности ... в любом случае ... продолжайте играть
mikeserv
Хороший ответ. Просто примечание: zshя не могу заставить его работать, передавая N = 10, как в примере. Однако это работает, если я export N=10до запуска команды. Есть идеи, как настроить пример для работы с Zsh?
Гейб Коплей
Илиperl -lne 'print "$ARGV: $_" for /.{0,10}foo.{0,10}/g'
Стефан Шазелас
20

Попробуйте использовать это:

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

-E говорит, что вы хотите использовать расширенное регулярное выражение

говорит, что вы хотите напечатать только матч

-r grep рекурсивно ищет результат в папке

REGEX:

{0,10} говорит, сколько произвольных символов вы хотите напечатать

, представляет произвольный символ (сам символ здесь не важен, только их количество)

Редактировать: О, я вижу, что Джозеф рекомендует почти то же решение, что и я: D

Eenoku
источник
Спасибо. Хотя это по сути одно и то же решение, вселяет уверенность, что это лучший метод, когда два человека независимо рекомендуют его.
Dotancohen
Не за что, сообщество Unix просто должно сотрудничать, вот чем мы являемся :-)
Eenoku
2
Хотя они похожи, принятый ответ не сработал для меня (по-прежнему создавал длинные строки), но один это сработало. Трюк с N = 10 не работает с оболочкой bash.
Meesern
в cygwin -E значительно быстрее чем -P.
Боб Стейн
2

Взято из: http://www.topbug.net/blog/2016/08/18/truncate-long-matching-lines-of-grep-a-solution-that-preserved-color/ и https: // stackoverflow. ком / а / 39029954/1150462

Предлагаемый подход ".{0,10}<original pattern>.{0,10}"очень хорош, за исключением того, что цвет подсветки часто путается. Я создал скрипт с похожим выводом, но цвет также сохранился:

#!/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))}"

Предполагая, что скрипт сохранен как grepl, тогда grepl pattern file_with_long_linesдолжны отображаться соответствующие строки, но только с 10 символами вокруг соответствующей строки.

xuhdev
источник
0

Трубопровод cutс -bфлагом; вы можете настроить вывод grep только на байты от 1 до 400 на строку.

grep "foobar" * | cut -b 1-400
Эрик Лещинский
источник