Краткий способ печати всех строк вплоть до последней строки, соответствующей заданному шаблону

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

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

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

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'

Но я не придумала способ напечатать все с последнего происшествия.

7 ответов

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: Если у вас нет tac подражать, определяя

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

Гадкий, но POSIX.

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

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

Как однострочник:

 perl -ne '@x=() if /$pattern/;push @x,$_;END{print @x}' input-file

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

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

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

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

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

СЕПГ q Команда сделает свое дело:

sed "/$pattern/q" $file

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

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

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

sed -ne '1,/PATTERN/p'

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

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

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

#!/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 вместо.

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

Вы сказали:

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

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

Но я не придумала способ напечатать все с последнего происшествия.

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

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
Другие вопросы по тегам