найдите (например, awk, grep, sed) строку, затем найдите X строк выше и другую строку ниже

Мне нужно иметь возможность искать строку (давайте использовать 4320101), печатать 20 строк над строкой и печатать после этого, пока не найдет строку

Например:

Random text I do not want or blank line
16 Apr 2013 00:14:15
id="4320101"
</eventUpdate>
Random text I do not want or blank line

Я просто хочу, чтобы в файл выводился следующий результат:

16 Apr 2013 00:14:15
id="4320101"
</eventUpdate>

В файле, который мне нужен, есть несколько примеров этих групп текста.

Я попытался использовать это ниже:

cat filename | grep "</eventUpdate>" -A 20 4320101 -B 100 > greptest.txt

Но он показывает только 20 строк по обе стороны от строки.

Примечания:
- номер строки, на которой находится текст, несовместим, поэтому я не могу отказаться от них, поэтому я использую -A 20.
- в идеале я бы предпочел, чтобы при поиске после строки, он останавливается, когда находит, а затем продолжает поиск.

Итог: найдите 4320101, выведите 20 строк выше 4320101 (или одну строку пробела), а затем выведите все строки ниже 4320101 до

</eventUpdate>

Проводя исследование, я не уверен, как заставить awk, nawk или sed работать в мою пользу, чтобы сделать это.


person Zippyduda    schedule 22.05.2013    source источник
comment
-A - это количество строк, которые grep будет печатать Aпосле совпадающей строки. Если вам не нужны строки после совпадающей строки, почему вы просите 20? Также я не понимаю вашего комментария о непоследовательности номеров строк, но если вы хотите 20 строк Before, используйте -B 20   -  person rici    schedule 22.05.2013
comment
Я понимаю. Это должно работать: cat имя файла | grep 4320101 -A 100 ‹/eventUpdate› -B 20 , но возвращает: ‹/eventUpdate›: Нет такого файла или каталога   -  person Zippyduda    schedule 22.05.2013
comment
Я понимаю, что -A и -B работают так, что ему нужно значение имени файла, поэтому он думает, что eventUpdate - это файл. Однако я хочу просто выполнить поиск до eventUpdate (предоставив ему 100 строк, чтобы найти его ниже 4320101)   -  person Zippyduda    schedule 22.05.2013
comment
Для этой работы нет стандартного инструмента. У меня есть 70-строчный (не минимальный) сценарий Perl, который делает строки до и после; его нужно было бы изменить, чтобы он обрабатывал «после и до альтернативного шаблона», но это намного проще, чем «от указанного шаблона до 4320101». У меня также есть версия сценария оболочки, которая использует edsed, и sort, и снова sed) в каждом файле - это было бы проще всего адаптировать. Свяжитесь со мной (см. мой профиль), если вы хотите это; они не являются полностью проработанными решениями вашей проблемы, но могут быть полезными ступеньками для ее решения.   -  person Jonathan Leffler    schedule 22.05.2013
comment
Что вы хотите, чтобы произошло, если 2 диапазона перекрываются, например. если 2 вхождения 4320101 лежат в пределах 20 строк друг от друга? Напечатать все строки один раз? Дважды вывести строки, лежащие в обоих диапазонах?   -  person Ed Morton    schedule 23.05.2013
comment
Он появляется только один раз в пределах 20 строк выше себя (на этот раз сам) и до eventUpdate, который никогда не может быть на 21 строку выше следующего вхождения.   -  person Zippyduda    schedule 24.05.2013


Ответы (6)


Это может сработать для вас (GNU sed):

sed ':a;s/\n/&/20;tb;$!{N;ba};:b;/4320102/!D;:c;n;/<\/eventUpdate>/!bc' file

РЕДАКТИРОВАТЬ:

  • :a;s/\n/&/20;tb;$!{N;ba}; это сохраняет окно из 20 строк в пространстве шаблона (PS)
  • :b;/4320102!D; это перемещает указанное выше окно по файлу до тех пор, пока не будет найден шаблон 4320102.
  • :c;n;/<\/eventUpdate>/!bc печатается 20-строчное окно и любая последующая строка, пока не будет найден шаблон <\/eventUpdate>.
person potong    schedule 22.05.2013
comment
Это сработало отлично. Просто изменил его, чтобы проверить 3 строки, прочитать пользовательский ввод из прочитанного идентификатора (в данном случае 4320102), а затем выполнить /'$ID'. Я должен спросить, не могли бы вы разбить, что именно все это делает? - person Zippyduda; 24.05.2013

Вот уродливое awk решение :)

awk 'BEGIN{last=1}
{if((length($0)==0) || (Random ~ $0))last=NR} 
/4320101/{flag=1;
if((NR-last)>20) last=NR-20;
cmd="sed -n \""last+1","NR-1"p \" input.txt";
system(cmd);
}
flag==1{print}
/eventUpdate/{flag=0}' <filename>

Таким образом, в основном он отслеживает последнюю пустую строку или строку, содержащую шаблон Random в переменной last. Теперь, если 4320101 был найден, он печатает из that line -20 or last в зависимости от того, что ближе, с помощью команды system sed. И устанавливает flag. flag заставляет печатать следующие строки до тех пор, пока не будет найдено eventUpdate. Не проверял, но должно работать

person abasu    schedule 22.05.2013

Оглядываться назад в sed/awk всегда сложно. Этот автономный скрипт awk в основном сохраняет последние 20 строк, когда он доходит до 4320101, он печатает эти сохраненные строки до момента, когда найдена пустая или нежелательная строка, а затем он останавливается. В этот момент он переключается в режим printall и печатает все строки до тех пор, пока не встретится eventUpdate, затем печатает его и завершает работу.

awk '
function store( line ) {
    for( i=0; i <= 20; i++ ) {
        last[i-1] = last[i]; i++;
    };
    last[20]=line;
};
function purge() {
    for( i=20; i >= 0; i-- ) {
        if( length(last[i])==0 || last[i] ~ "Random" ) {
            stop=i;
            break
        };
    };
    for( i=(stop+1); i <= 20; i++ ) {
        print last[i];
    };

};
{
store($0);
if( /4320101/ ) {
    purge();
    printall=1;
    next;
};
if( printall == 1) {
    print;
    if( /eventUpdate/ ) {
        exit 0;
    };
};
}' test
person qwwqwwq    schedule 22.05.2013

Давайте посмотрим, понимаю ли я ваши требования:

У вас есть две строки, которые я назову KEY и LIMIT. И вы хотите напечатать:

  1. Не более 20 строк перед строкой, содержащей KEY, но с остановкой, если есть пустая строка.

  2. Все строки между строкой, содержащей KEY, и следующей строкой, содержащей LIMIT. (Это игнорирует ваше требование, чтобы таких строк было не более 100; если это важно, добавить относительно просто.)

Самый простой способ выполнить (1) — сохранить циклический буфер из 20 строк и распечатать его, когда вы нажмете key. (2) является тривиальным как в sed, так и в awk, потому что вы можете использовать двухадресную форму для вывода диапазона.

Итак, давайте сделаем это в awk:

#file: extract.awk

# Initialize the circular buffer
BEGIN          { count = 0; }
# When we hit an empty line, clear the circular buffer
length() == 0  { count = 0; next; }
# When we hit `key`, print and clear the circular buffer
index($0, KEY) { for (i = count < 20 ? 0 : count - 20; i < count; ++i)
                   print buf[i % 20];
                 hi = 0;
               }
# While we're between key and limit, print the line
index($0, KEY),index($0, LIMIT)
               { print; next; }
# Otherwise, save the line
               { buf[count++ % 20] = $0; }

Чтобы это заработало, нам нужно установить значения KEY и LIMIT. Мы можем сделать это в командной строке:

awk -v "KEY=4320101" -v "LIMIT=</eventUpdate>" -f extract.awk $FILENAME

Примечания:

  1. Я использовал index($0, foo) вместо более обычного /foo/, потому что это позволяет избежать необходимости экранировать специальные символы регулярных выражений, а в требованиях нигде не указано, что регулярные выражения даже желательны. index(haystack, needle) возвращает индекс needle в haystack с индексами, начинающимися с 1, или 0, если needle не найден. Используется как значение true/false, истинно для needle.

  2. next завершает обработку текущей строки. Это может быть очень удобно, как показывает эта небольшая программа.

person rici    schedule 22.05.2013

Вы можете попробовать что-то вроде этого -

awk '{ 
    a[NR] = $0
}

/<\/eventUpdate>/ { 
    x = NR
}

END {
    for (i in a) {
        if (a[i]~/4320101/) {
            for (j=i-20;j<=x;j++) {
            print a[j]
            }
        }
    }
}' file
person jaypal singh    schedule 22.05.2013

Самый простой способ - использовать 2 прохода файла - первый для определения номеров строк в диапазоне, в котором находится ваше целевое регулярное выражение, второй для печати строк в выбранном диапазоне, например:

awk '
NR==FNR {
    if ($0 ~ /\<4320101\>/ {
        for (i=NR-20;i<NR;i++)
            range[i]
        inRange = 1
    }
    if (inRange) {
        range[NR]
    }
    if ($0 ~ /<\/eventUpdate>/) {
        inRange = 0
    }
    next
}
FNR in range
' file file
person Ed Morton    schedule 22.05.2013
comment
Используя это, я получил: awk: cmd. строка: 9: (FILENAME=test FNR=482) фатальный: попытка использовать скаляр `inRange' в качестве массива - person Zippyduda; 24.05.2013
comment
исправлено. awk указал вам на строку, где была ошибка, и сказал, в чем была ошибка. - person Ed Morton; 24.05.2013