A simple way to only keep the last matching line is to print the input in reverse, select the first matching line only and then print the output in reverse, in a pipeline. Assuming tac
from GNU Coreutils is available to you:
tac input_file | awk '!/test$/ || !seen++' | tac >output_file
In-place editing (as you requested in a comment) is generally obtained by wrapping commands in a script or a function that takes care of overwriting the file(s) given as argument(s) with the processed output.
tmpdir=$(mktemp -d)
cp input_file "$tmpdir/file"
tac "$tmpdir/file" | awk '!/test$/ || !seen++' | tac >input_file
rm -r "$tmpdir"
If your shell supports the pipefail
option (I could successfully test bash, ksh93, mksh, zsh using setopt PIPE_FAIL
, busybox ash, yash), this can be made safer with set -e
and set -o pipefail
: errors (including those occurring anywhere in the pipeline) will make the execution stop before the temporary file is removed.
On platforms that support it, assuming you don't care losing your data in case something goes wrong, you may also use:
{ rm file; tac | awk '!/test$/ || !seen++' | tac >file; } <file
Note that this will change the inode of file
(as would do the in-place editing option provided by many common tools).
If, instead, you want to remove the first n matching lines, assuming here n = 2:
awk '!/test$/ || ++seen > 2' input_file >output_file
In this case, in-place editing is conveniently allowed by GNU awk
's ability to import additional libraries (and, specifically, the "inplace" one shipped with gawk
itself):
awk -i inplace '...' file
More on this can be found in this other answer on U&L.