Не объединять контекст смежных совпадений с grep

#grep #matching

#grep #сопоставление

Вопрос:

Если я выполню grep -C 1 match следующий файл:

 a
b
match1
c
d
e
match2
f
match3
g
  

Я получаю следующий вывод:

 b
match1
c
--
e
match2
f
match3
g
  

Как вы можете видеть, поскольку контекст вокруг смежных совпадений «match2» и «match3» перекрывается, они объединяются. Однако я бы предпочел получать одно описание контекста для каждого совпадения, возможно, дублируя строки из входных данных в отчете о контексте. В этом случае я хотел бы, чтобы:

 b
match1
c
--
e
match2
f
--
f
match3
g
  

Какой был бы наилучший способ добиться этого? Я бы предпочел решения, которые являются достаточно общими, чтобы их можно было легко адаптировать к другим grep параметрам (разные значения для -A , -B -C или совершенно разные флаги). В идеале, я надеялся, что есть умный способ сделать это только с grep ….

Ответ №1:

Я не думаю, что это возможно сделать, используя обычный grep.

приведенная ниже конструкция sed работает в некоторой степени, теперь мне нужно только выяснить, как добавить разделитель «—«

 $ sed -n -e '/match/{x;1!p;g;$!N;p;D;}' -e h log
b
match1
c
e
match2
f
f
match3
g
  

Комментарии:

1. Это результат, который он уже получает; он хочет чего-то другого. Пожалуйста, прочитайте вопрос, прежде чем отвечать.

2. Это не идеально, потому что вам пришлось бы изменить выражение sed неочевидным способом, если бы вы хотели иметь разные значения -C, или -A, или -B … не говоря уже об использовании других grep опций.

3. @a3_nm это разумное требование для решения, но оно не указано в исходном вопросе. Я бы посоветовал вам отредактировать ваш вопрос, добавив его.

4. Все, что вам действительно нужно, это декоратор для вывода. Вы знаете токен, который ищете — этой информации достаточно, чтобы дублировать строки, где токен повторяется, вставляя —. Я думаю, что это sed решение приведет вас в правильном направлении. Вы могли бы составить сценарий оболочки, который просто вызывает grep с его идеей, передавая произвольные аргументы и выполняя ваш вопрос.

Ответ №2:

Я не думаю, что это возможно с использованием обычного grep.

Вы когда-нибудь использовали Python? На мой взгляд, это идеальный язык для таких задач (этот фрагмент кода будет работать как для Python 2.7, так и для 3.x):

 with open("your_file_name") as f:
   lines = [line.rstrip() for line in f.readlines()]
   for num, line in enumerate(lines):
      if "match" in line:
         if num > 0:
            print(lines[num - 1])

         print(line)

         if num < len(lines) - 1:
            print(lines[num   1])
            if num < len(lines) - 2:
               print("--")
  

Это дает мне:

 b
match1
c
--
e
match2
f
--
f
match3
g
  

Комментарии:

1. Это необходимо было бы адаптировать для более сложного использования grep . Кажется неудовлетворительным переопределять сопоставление упрощенным способом ( grep делает это очень умным способом) только из-за этой отсутствующей опции. В любом случае, спасибо за предложение!

Ответ №3:

Я бы предложил исправить grep вместо того, чтобы обходить его. В GNU grep 2.9 в src/main.cpp:

 933       /* We print the SEP_STR_GROUP separator only if our output is
934          discontiguous from the last output in the file. */
935       if ((out_before || out_after) amp;amp; used amp;amp; p != lastout amp;amp; group_separator)
936         {
937           PR_SGR_START_IF(sep_color);
938           fputs (group_separator, stdout);
939           PR_SGR_END_IF(sep_color);
940           fputc('n', stdout);
941         }
942 
  

Здесь было бы достаточно простого дополнительного флага.

Редактировать: Ну, черт возьми, это, конечно, не так просто, поскольку grep не будет воспроизводить контекст, просто добавьте еще несколько разделителей. Из-за линейности grep весь патч, вероятно, не так прост. Тем не менее, если у вас есть веские аргументы в пользу исправления, оно того стоит.

Ответ №4:

Это не представляется возможным с grep или GNU grep. Однако это возможно с помощью стандартных инструментов POSIX и хорошей оболочки, такой как bash, в качестве рычага для получения желаемого результата.
Примечание: ни python, ни perl не должны быть необходимы для решения. В худшем случае используйте awk или sed.

Одно из решений, которое я быстро прототипировал, выглядит примерно так (оно действительно связано с накладными расходами на повторное чтение файла, и это решение зависит от того, приемлемы ли эти накладные расходы, а ответом является использование в исходном вопросе значения -1 в качестве фиксированного количества строк контекста, что позволяет просто использовать head amp; tail) :

 $ OIFS="$IFS"; lines=`grep -n match greptext.txt | /bin/cut -f1 -d:`; 
for l in $lines; 
do IFS=""; match=`/bin/tail -n  $(($l-1)) greptext.txt | /bin/head -3`; 
echo $match; echo "---"; 
done; IFS="$OIFS"
  

С этим может быть связан некоторый угловой регистр, и это сбрасывает IFS, когда, возможно, в этом нет необходимости, хотя это подсказка для попытки использовать возможности POSIX shell amp; tools, а не высокоуровневый интерпретатор для получения желаемого результата.

Мнение: Все хорошие операционные системы имеют: grep, awk, sed, tr, cut, head, tail, more, less, vi в качестве встроенных модулей. В лучших операционных системах они находятся в /bin.