6

Sorry guys I had to edit my example, because I didn't express my query properly. Let's say I have the .txt file:

Happy sad
Happy sad
Happy sad
Sad happy
Happy sad
Happy sad
Mad sad
Mad happy
Mad happy

And I want to delete any string that is unique. Leaving the file with:

Happy sad
Happy sad
Happy sad
Happy sad
Happy sad
Mad happy
Mad happy

I understand that sort is able to get rid of duplicates (sort file.txt | uniq), so is there anyway we can do the opposite in bash using a command? Or would I just need to figure out a while loop for it? BTW uniq -D file.txt > output.txt doesn't work.

Jerry
  • 161

4 Answers4

17

Using awk:

$ awk 'seen[$0]++; seen[$0] == 2' file
Happy sad
Happy sad
Happy sad
Happy sad
Happy sad
Mad happy
Mad happy

This uses the text of each line as the key into the associative array seen. The first seen[$0]++ will cause a line that has been seen before to be printed since the value associated with the line will be non-zero on the second and subsequent times the line is seen. The seen[$0] == 2 causes the line to be printed again if this is the second time the line has been seen (without this, you'll miss one occurrence of each duplicated line).

This is related to awk '!seen[$0]++' which is sometimes used to remove duplicates without sorting (see e.g. How does awk '!a[$0]++' work?).


To only get one copy of the duplicated lines:

awk 'seen[$0]++ == 1' file

or,

sort file | uniq -d
Kusalananda
  • 333,661
8

If the duplicates may not be contiguous and you need to preserve the order in the input, you could do it with awk and two passes, one to count the number of occurrences and one to print the lines that have been seen to occur more than once in the first pass:

awk 'second_pass {if (c[$0] > 1) print; next}
     {c[$0]++}' file.txt second_pass=1 file.txt
3

From man uniq:

-D print all duplicate lines

You can achieve your goal like so:

uniq -D file.txt
Panki
  • 6,664
  • 4
    Note that -D is a non-standard GNU extension, though now supported by a few other implementations including ast-open's and FreeBSD's – Stéphane Chazelas Nov 05 '20 at 10:50
  • 4
    Also note that it only reports duplicate lines that are contiguous. printf '%s\n' a b a | uniq -D will print nothing as the two a lines are not contiguous. It may very well be what the OP wants though. – Stéphane Chazelas Nov 05 '20 at 10:51
  • Note that some versions of uniq (e.g. on macOS) use a lowercase -d – jcaron Nov 08 '20 at 00:34
  • this appears to work in the OP's example, but if you delete the last Happy sad (6th line) in his input, it gives you the wrong answer. (I am assuming duplicates should be detected even if they are not contiguous, though OP did not actually say that and his example sufficiently illustrate what he wants in that edge case.) –  Nov 09 '20 at 03:30
0

this is probably a Linux-only solution, since it uses uniq's -u option. You could get around that by using uniq -c then filtering for ^ *1 etc. if you're running some other flavour.

sort < in | uniq --unique | grep --invert-match --line-regexp --fixed-strings --file - in

The first 2 stages will put out

Mad sad
Sad happy

and the next stage will remove lines that match exactly those lines. I picked the longer options for clarity; I myself rarely use them The short form would be sort < in | uniq -u | grep -v -x -F -f - in