Краткий способ печати всех строк вплоть до последней строки, соответствующей заданному шаблону
Я пытаюсь найти краткую однострочную оболочку, которая даст мне все строки в файле вплоть до некоторого шаблона.
Вариант использования - сбросить все строки в файле журнала, пока я не укажу маркер, указывающий, что сервер был перезапущен.
Вот глупый способ только для оболочки:
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