How can we delete duplicate lines only seen in lines X to Y and change the file in-place?
For instance, if I just want to delete duplicate lines from line 10 to 20.
How can we delete duplicate lines only seen in lines X to Y and change the file in-place?
For instance, if I just want to delete duplicate lines from line 10 to 20.
With GNU awk
(4.1.0 or above for the inplace
extension¹):
gawk -i /usr/share/awk/inplace.awk '
NR >= 10 && NR <= 20 {
if ($0 in seen) next
seen[$0]
}
{print}' ./file
Or with perl
:
perl -ni -e 'print if $. < 10 or $. > 20 or !$seen{$_}++' ./file
To process several files:
gawk -i /usr/share/awk/inplace.awk '
BEGINFILE{delete seen}
FNR >= 10 && FNR <= 20 {
if ($0 in seen) next
seen[$0]
}
{print}' ./*.txt
Or with perl
:
perl -ni -e '
print if $. < 10 or $. > 20 or !$seen{$_}++;
if (eof) {close ARGV; undef %seen}' ./*.txt
¹ do not use -i inplace
as gawk
tries to load the inplace
extension (as inplace
or inplace.awk
) from the current working directory first, where someone could have planted malware. The path of the inplace
extension supplied with gawk
may vary with the system, see the output of gawk 'BEGIN{print ENVIRON["AWKPATH"]}'
awk
is your friend
awk '{
if(NR>=10 && NR<=20)
{
if($0 in record){
next
}else{
print;
record[$0];
}
}
else{
print
}
}' file > temp && mv temp file
{if(
and )
are not needed on the first line (the NR>=10 && NR<=20
"pattern" is still required). awk
scripts consist of a series of pattern {action}; statements, either of which are optional ("pattern" defaults to true ("condition" would probably be a better name for it than "pattern"), and "action" defaults to print`).
If OP need to remove lines which duplicates just within 10-20 lines:
sed -i '
:a; 10,19!b; N; s/\(^\|\n\)\([^\n]*\)\n\(\(.\+\n\|\)\2$\)/\1\3/; ba
' file1 file2 ...
printf '%s\n' {1..10} T TEST
for instance. It also assumes GNU sed
(and that POSIXLY_CORRECT
be not in the environment)..
Commented
Aug 8, 2016 at 11:39
The very same tricks applied in the Perl-based answers can also be used to shorten the Awk code, and it ends up smaller and cleaner:
awk 'NR < 10 || NR > 20 || !seen[$0]++'
^ ^ ^ ^
| | | |
| \__________\___________\______ no sigil noise
|
\_ no options here to remember
(unless we want that Gawk inplace semantics)
The counters will not overflow because the range is restricted to ten lines—and GNU Awk has bignum integers anyway.