15

I have "test1.csv" and it contains

200,400,600,800
100,300,500,700
50,25,125,310

and test2.csv and it contains

100,4,2,1,7
200,400,600,800
21,22,23,24,25
50,25,125,310
50,25,700,5

now

diff test2.csv test1.csv > result.csv

is different than

diff test1.csv test2.csv > result.csv

I don't know which is the correct order but I want something else, both of the commands above will output something like

2 > 100,4,2,1,7
   3 2,3c3,5
   4 < 100,300,500,700
   5 < 50,25,125,310
   6 \ No newline at end of file
   7 ---
   8 > 21,22,23,24,25
   9 > 50,25,125,310

I want to output only the difference, thus results.csv should look like this

100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

I tried diff -q and diff -s but they didn't do the trick. Order doesn't matter, what matters is that I want to see only the difference, no > nor < nor blank space.

grep -FvF did the trick on smaller files not on big ones

first file contains more than 5 million lines, second file contains 1300.

so results.csv should result in ~4,998,700 lines

I also tried grep -F -x -v -f which didn't work.

7
  • 1
    meta.askubuntu.com/questions/14089/…
    – Tim
    Jun 23, 2015 at 21:33
  • 1
    @Tim i saw your link and i'm an old member so i know the rules but was careless, sorry :) was editing it, and i saw a popup that the post was edited so you did the work for me and I'm thankful Sir.
    – Lynob
    Jun 23, 2015 at 21:37
  • 50,25,125,310 is common to both file..you need to remove that from your desired output..
    – heemayl
    Jun 23, 2015 at 21:48
  • Should the order be preserved?
    – kos
    Jun 23, 2015 at 21:51
  • 1
    sort of depends on what you want to do with the information, diff, IMO, is for making a patch. At any rate, I am now sure of your best tool, diff, grep, awk, or perl .
    – Panther
    Jun 23, 2015 at 21:58

5 Answers 5

21

Sounds like a job for comm:

$ comm -3 <(sort test1.csv) <(sort test2.csv)
100,300,500,700
    100,4,2,1,7
    21,22,23,24,25
    50,25,700,5

As explained in man comm:

   -1     suppress column 1 (lines unique to FILE1)

   -2     suppress column 2 (lines unique to FILE2)

   -3     suppress column 3 (lines that appear in both files)

So, the -3 means that only lines that are unique to one of the files will be printed. However, those are indented according to which file they were found in. To remove the tab, use:

$ comm -3 <(sort test1.csv) <(sort test2.csv) | tr -d '\t'
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

In this case, you don't really even need to sort the files and you can simplify the above to:

comm -3 test1.csv test2.csv | tr -d '\t' > difference.csv
2
  • You haven't been fooled by the spaces after the 200,[...] line huh? :)
    – kos
    Jun 23, 2015 at 22:26
  • @kos no, I removed trailing spaces from the files first. I assumed the OP's files don't actually have them.
    – terdon
    Jun 23, 2015 at 22:26
6

Using grep with bash process substitution:

$ cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv)
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

To save the output as results.csv:

cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv) >results.csv
  • <() is the bash process substitution pattern

  • grep -vFf test2.csv test1.csv will find the lines unique to only test1.csv

  • grep -vFf test1.csv test2.csv will find the lines unique to only test2.csv

  • Finally we are summing up the results by cat

Or as Oli suggested, you can use command grouping also:

$ { grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv; }
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Or just run one after another, as they are both writing to STDOUT they will ultimately get added:

$ grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5
2
  • 1
    Why cat two redirected commands? Why not just run one then the other? grep ... ; grep ... or { grep ... ; grep ... ; } if you wanted to do something with the collective output.
    – Oli
    Jun 23, 2015 at 21:54
  • @Oli Thanks..thats a great idea..i did not think of that..
    – heemayl
    Jun 23, 2015 at 22:01
4

If the order of rows is not relevant, use awk or perl:

awk '{seen[$0]++} END {for (i in seen) {if (seen[i] == 1) {print i}}}' 1.csv 2.csv

Use grep to get the common lines and filter those out:

grep -hxvFf <(grep -Fxf 1.csv 2.csv) 1.csv 2.csv

The internal grep gets the common lines, then the external grep finds lines which don't match these common lines.

10
  • Your awk command just re-implements sort | uniq -u, which gives the wrong answer when one file contains duplicate lines. For grep, I'd say "inner"/"outer", not "internal"/"external". Jun 24, 2015 at 15:49
  • @PeterCordes yes, it does and who are you to say that's the wrong result?
    – muru
    Jun 24, 2015 at 18:07
  • Wrong in the sense that it's not exactly what the question asked for, in that corner case. It might be what someone wants, but you should point out the difference between what your awk will print and what the comm -3 and diff answers will print. Jun 24, 2015 at 18:13
  • @PeterCordes again, who are you to say that? Until the OP says that's what they want, I don't care if the output differs from that of comm -3. I don't see any reason why I should explain that. If you want to edit in a note, feel free.
    – muru
    Jun 24, 2015 at 18:15
  • The OP said he wants the difference. That's not always what your program produces. A program that produces the same output for one testcase, but doesn't satisfy the description as written for all cases, requires a heads up. I'm here to say that, and it's true regardless of who I am or who you are. I added a note. Jun 24, 2015 at 18:23
4

Use the --*-line-format=... options of diff

You can tell diff exactly what you need - explained below:

diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' f1.txt f2.txt

It is possible to specify the output of diff in a very detailed way, similar to a printf number format.

The lines from the first file, test1.csv are called "old" lines, and the lines from the second, test2.csv, are "new" lines. That makes sense when diff is used to see what changed in a file.

The options we need are the ones to set the format for "old" lines, "new" lines, and "unchanged" lines.
The formats we need are very simple:
For the changed lines, new and old, we want to output only the text of the lines. %L is the format symbol for the line text.
For the unchanged lines, we want to show nothing.

With this, we can write options like --old-line-format='%L', and put it all together, using your example data:

$ diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' test1.csv test2.csv
100,4,2,1,7
100,300,500,700
21,22,23,24,25
50,25,700,5


Notes on performance

Because the files have different size, try to exchange the input files if it does not matter, it could be that the inner workings of diff can handle one way better than the other. Better is either needing less memory, or less computation.

There is an optimisation option for using diff with large files: --speed-large-files. It uses assumptions about the file structure, so it's not clear whether it helps in your case, but worth trying it.

The format options are described in the man diff under --LTYPE-line-format=LFMT.

3

Since the order doesn't need to be preserved, simply:

sort test1.csv test2.csv | uniq -u
  • sort test1.csv test2.csv: merges and sorts test1.csv and test2.csv
  • uniq -u: prints only the lines which have no duplicate
1
  • That does not work if one file contains a line two times, that does not appear in the other file. Both occurences would be in a diff result. Jun 24, 2015 at 7:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .