Краткий способ печати всех строк вплоть до последней строки, соответствующей заданному шаблону - PullRequest
7 голосов
/ 22 января 2012

Я пытаюсь найти краткую оболочку, которая даст мне все строки в файле до некоторый шаблон.

Вариант использования - сбросить все строки в файле журнала, пока я не укажу некоторые маркер, указывающий, что сервер был перезапущен.

Вот глупый способ только для оболочки:

tail_file_to_pattern() {
    pattern=$1
    file=$2

    tail -n$((1 + $(wc -l $file | cut -d' ' -f1) - $(grep -E -n "$pattern" $file | tail -n 1 | cut -d ':' -f1))) $file
}

Немного более надежный способ Perl, который принимает файл на стандартный ввод:

perl -we '
    push @lines => $_ while <STDIN>;
    my $pattern = $ARGV[0];
    END {
        my $last_match = 0;
        for (my $i = @lines; $i--;) {
            $last_match = $i and last if $lines[$i] =~ /$pattern/;
        }
        print @lines[$last_match..$#lines];
    }
'

И, конечно, вы могли бы сделать это более эффективно, открыв файл, ищите до конца и ищите назад, пока не найдете подходящую линию.

Легко напечатать все, начиная с первого экземпляра, например ::

sed -n '/PATTERN/,$p'

Но я не нашел способа напечатать все, начиная с last вхождение.

Ответы [ 7 ]

6 голосов
/ 23 января 2012

Вот решение только для sed.Чтобы напечатать каждую строку в $file , начиная с , последняя строка, которая соответствует $pattern:

sed -e "H;/${pattern}/h" -e '$g;$!d' $file

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

Вот разбивка того, что он делает, с командами sed в скобках:

  • [H] Добавлять каждую строку к «пространству удержания» sed, ноне передавайте его в стандартный вывод [d].
  • Когда мы сталкиваемся с шаблоном, [h] выбрасываем пробел и начинаем заново с совпадающей строки.
  • Когда мы доходим до концафайла, скопируйте пространство удержания в пространство шаблона [g], чтобы оно отображалось в stdout.

Также обратите внимание, что это может замедлиться при работе с очень большими файлами, поскольку любое однопроходное решениенужно будет хранить несколько строк в памяти.

4 голосов
/ 23 января 2012

Загрузка данных в массив строка за строкой, и выбросить массив, когда вы найдете совпадение с шаблоном.Распечатайте все, что осталось в конце.

 while (<>) {
     @x=() if /$pattern/;
     push @x, $_;
 }
 print @x;

В виде одной строки:

 perl -ne '@x=() if /$pattern/;push @x,$_;END{print @x}' input-file
4 голосов
/ 22 января 2012

В качестве альтернативы: tac "$file" | sed -n '/PATTERN/,$p' | tac

РЕДАКТИРОВАТЬ: если у вас нет tac эмулируйте его, определив

tac() {
    cat -n | sort -nr | cut -f2
}

Ужасно, но POSIX.

3 голосов
/ 23 января 2012
Команда

Sed q сделает свое дело:

sed "/$pattern/q" $file

Это напечатает все строки, пока не дойдет до строки с шаблоном. После этого sed напечатает последнюю строку и выйдет.

3 голосов
/ 22 января 2012

Я предлагаю упростить ваш сценарий оболочки:

tail -n +$(grep -En "$pattern" "$file" | tail -1 | cut -d: -f1) "$file"

Это существенно более кратко, потому что:

  • Использует опцию + tail для печати из заданной строки вконец, вместо того, чтобы вычислять расстояние оттуда до конца.
  • Использует более краткие способы выражения параметров командной строки.

И исправляет ошибку, цитируя $ file(поэтому он будет работать с файлами, имена которых содержат пробелы).

1 голос
/ 23 января 2012

Роб Дэвис указал мне, что вы сказали , что вы хотели, это не то, что вы на самом деле спросили:

Вы сказали:

Я пытаюсь найти краткую однострочную оболочку, которая выдаст мне все строки в файле до некоторый шаблон.

но потом в самом конце своего поста вы сказали:

Но я не нашел способа напечатать все с последнего вхождения.

Я уже дал вам ответ на ваш первый вопрос . Вот ответ на ваш второй вопрос в одну строку: Печать от регулярного выражения до конца файла:

awk '{ if ($0 ~ /'"$pattern"'/) { flag = 1 } if (flag == 1) { print $0 } }' $file

Подобный Perl однострочный:

export pattern="<regex>"
export file="<file>"
perl -ne '$flag=1 if /$ENV{pattern}/;print if $flag;' $file
1 голос
/ 23 января 2012

Название и описание этого вопроса не совпадают.

Для заголовка вопроса +1 для ответа @David W.Также:

sed -ne '1,/PATTERN/p'

По вопросу в теле у вас уже есть несколько решений.

Обратите внимание, что tac, вероятно, относится к Linux.Это, кажется, не существует в BSD или OSX.Если вам нужно решение, которое является мультиплатформенным, не полагайтесь на tac.

Конечно, практически любое решение потребует, чтобы ваши данные были либо помещены в буфер, либо переданы один раз для анализа ивторой раз для обработки.Например:

#!/usr/local/bin/bash

tmpfile="/tmp/`basename $0`,$$"
trap "rm $tmpfile" 0 1 2 5
cat > $tmpfile

n=`awk '/PATTERN/{n=NR}END{print NR-n+1}' $tmpfile`

tail -$n $tmpfile

Обратите внимание, что я использую tail для FreeBSD.Если вы используете Linux, вам, вероятно, понадобится tail -n $n $tmpfile.

...