grep -v: How to exclude only the first (or last) N lines that match?
You could use awk
to ignore the first n lines that match (e.g. assuming you wanted to remove only the 1st and 2nd match from the file):
n=2
awk -v c=$n '/PATTERN/ && i++ < c {next};1' infile
To ignore the last n lines that match:
awk -v c=${lasttoprint} '!(/PATTERN/ && NR > c)' infile
where ${lasttoprint}
is the line number of the n
th+1 to last match in your file. There are various ways to get that line no. (e.g. print only the line number for each match via tools like sed
/awk
, then tail | head
to extract it)... here's one way with gnu awk
:
n=2
lasttoprint=$(gawk -v c=$((n+1)) '/PATTERN/{x[NR]};
END{asorti(x,z,"@ind_num_desc");{print z[c]}}' infile)
sed
provides a simpler way:
... | sed '/some stuff/ {N; s/^.*\n//; :p; N; $q; bp}' | ...
This way you delete first occurrence.
If you want more:
sed '1 {h; s/.*/iiii/; x}; /some stuff/ {x; s/^i//; x; td; b; :d; d}'
, where count of i
is count of occurrences (one or more, not zero).
Multi-line Explanation
sed '1 {
# Save first line in hold buffer, put `i`s to main buffer, swap buffers
h
s/^.*$/iiii/
x
}
# For regexp what we finding
/some stuff/ {
# Remove one `i` from hold buffer
x
s/i//
x
# If successful, there was `i`. Jump to `:d`, delete line
td
# If not, process next line (print others).
b
:d
d
}'
In addition
Probably, this variant will work faster, 'cos it reads all rest lines and print them in one time
sed '1 {h; s/.*/ii/; x}; /a/ {x; s/i//; x; td; :print_all; N; $q; bprint_all; :d; d}'
As result
You can put this code into your .bashrc
(or config of your shell, if it is other):
dtrash() {
if [ $# -eq 0 ]
then
cat
elif [ $# -eq 1 ]
then
sed "/$1/ {N; s/^.*\n//; :p; N; \$q; bp}"
else
count=""
for i in $(seq $1)
do
count="${count}i"
done
sed "1 {h; s/.*/$count/; x}; /$2/ {x; s/i//; x; td; :print_all; N; \$q; bprint_all; :d; d}"
fi
}
And use it this way:
# Remove first occurrence
cat file | dtrash 'stuff'
# Remove four occurrences
cat file | dtrash 4 'stuff'
# Don't modify
cat file | dtrash