I'm trying to find a succinct shell one-liner that'll give me all the lines in a file up until some pattern.
The use case is dumping all the lines in a log file until I spot some marker indicating that the server has been restarted.
Here's a stupid shell-only way that:
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
}
A slightly more reliable Perl way that takes the file on stdin:
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];
}
'
And of course you could do that more efficiently be opening the file, seeking to the end and seeking back until you found a matching line.
It's easy to print everything as of the first occurrence, e.g.:
sed -n '/PATTERN/,$p'
But I haven't come up with a way to print everything as of the last occurance.
Here's a sed-only solution. To print every line in $file
starting with the last line that matches $pattern
:
sed -e "H;/${pattern}/h" -e '$g;$!d' $file
Note that like your examples, this only works properly if the file contains the pattern. Otherwise, it outputs the entire file.
Here's a breakdown of what it does, with sed commands in brackets:
Also note that it's likely to get slow with very large files, since any single-pass solution will need to keep a bunch of lines in memory.
Load the data into an array line by line, and throw the array away when you find a pattern match. Print out whatever is left at the end.
while (<>) {
@x=() if /$pattern/;
push @x, $_;
}
print @x;
As a one-liner:
perl -ne '@x=() if /$pattern/;push @x,$_;END{print @x}' input-file
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With