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

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

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

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

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'

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


person Ævar Arnfjörð Bjarmason    schedule 22.01.2012    source источник
comment
В вашем заголовке указано, что все строки до последнего шаблона, но ваши два примера сценариев печатают все строки от последнего шаблона до конца. Я полагаю, это название вводит в заблуждение?   -  person John Zwinck    schedule 22.01.2012
comment
Если шаблон обычно присутствует и ближе к концу, вы можете рассмотреть возможность File::ReadBackwards (возврат в буфер до тех пор, пока не будет достигнут шаблон или начало файла).   -  person ikegami    schedule 23.01.2012


Ответы (7)


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

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

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

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

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

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

person Rob Davis    schedule 23.01.2012
comment
+1: Это очень причудливая обработка sed. Это одна строка и делает то, что хочет ОП. - person David W.; 23.01.2012

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

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

Как однострочный:

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

Альтернативно: tac "$file" | sed -n '/PATTERN/,$p' | tac

EDIT: если у вас нет tac, эмулируйте его, определив

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

Уродливый, но POSIX.

person Jo So    schedule 22.01.2012
comment
У меня нет двоичного файла tac. Учитывая, что в OP не указана операционная система, вероятно, лучше всего предлагать решения, которые будут работать повсеместно. - person ghoti; 23.01.2012
comment
Вы можете использовать tail -r вместо tac. Хотя это решение не совсем то (тело) вопроса. Для этого вам понадобится sed -n "1,/${pattern}/p". - person Rob Davis; 23.01.2012
comment
@ghoti: Похоже, вы не используете GNU/coreutils. Очевидно, tac не POSIX. Если вы настаиваете на POSIX, используйте cat -n | sort -nr | cut -f2 вместо tac (О, мы снова становимся уродливыми!) - person Jo So; 23.01.2012
comment
@RobDavis: tail -r тоже не POSIX и недоступен в моей системе Debian. Для второй части: Правда, заголовок не соответствует основному вопросу. Но, пожалуйста, дайте всю строку, которая будет tac | sed -n '1,/PATTERN/p' | tac (или tac замена) - person Jo So; 23.01.2012

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

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

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

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

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

person John Zwinck    schedule 22.01.2012

Команда Sed q сделает свое дело:

sed "/$pattern/q" $file

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

person David W.    schedule 23.01.2012
comment
Я думаю, это делает то, что предполагает заголовок и первая строка вопроса, но не то, что на самом деле хочет спрашивающий. Ему нужны все строки после, включая последнюю строку, которая соответствует заданному шаблону. - person Rob Davis; 23.01.2012
comment
@RobDavis - Ты прав. Я прочитал первый абзац и подумал Эй, это просто. Мне, вероятно, придется что-то придумать с Awk - person David W.; 23.01.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.

person ghoti    schedule 23.01.2012
comment
Вы можете использовать tail -r в OSX, чтобы получить функциональность tac. - person Mark Setchell; 10.02.2015
comment
Это правда, но это также не мультиплатформенность, так как опция -r не существует в Linux. Если я рекомендую против одного, было бы лицемерием с моей стороны не рекомендовать против другого. :) - person ghoti; 10.02.2015
comment
Я понимаю и полностью согласен - я просто указал, в основном для будущих читателей, что если они захотят использовать tac в OS X, они могут вместо этого использовать tail -r ... вместо того, чтобы оставить ваше заявление о том, что это не похоже существовать. - person Mark Setchell; 10.02.2015

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

Вы сказали:

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

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

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

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

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
person David W.    schedule 23.01.2012
comment
За исключением того, что ему нужны строки после последнего появления шаблона, я полагаю. - person Rob Davis; 23.01.2012
comment
@RobDavis - Ты прав. Ваше решение является лучшим. Это одна линия и независимая платформа. - person David W.; 23.01.2012