Zusammenführen von zwei CSV-Dateien, die nur anhand einer bestimmten Spalte verglichen werden

Zusammenführen von zwei CSV-Dateien, die nur anhand einer bestimmten Spalte verglichen werden

Ich möchte zwei CSV-Dateien mit folgendem Format vergleichen. Sie haben keine Kopfzeilen. Ich möchte sie anhand einer bestimmten Spalte vergleichen (in diesem Fall der zweiten).

Die Quell-CSV-Dateien sind etwa 4–5 GB groß, daher funktioniert das Laden in den Speicher nicht.

Wenn es in old.csv keine passende Spalte gibt, wird jede neue Zeile in out.csv geschrieben.

Diese 2. Spalte wird ein HTML-Link sein, der Einfachheit halber nur ein Wort.

Meine Frage ist, ob es möglich ist, mit sed, awk, join oder grep dasselbe Ergebnis zu erzielen?

alt.csv

"person"|"john"|"smith"
"person"|"anne"|"frank"
"person"|"bob"|"macdonald"
"fruit"|"orange"|"banana"
"fruit"|"strawberry"|"fields"
"fruit"|"ringring"|"banana"

neu.csv

"person"|"john"|"smith"
"person"|"anne"|"frank"
"person"|"bob"|"macdonald"
"fruit"|"orange"|"banana"
"fruit"|"strawberry"|"fields"
"glider"|"person"|"airport"
"fruit"|"ringring"|"banana"
"glider"|"person2"|"airport"

diff.py

#!/usr/bin/env python3

"""
Source: https://gist.github.com/davidrleonard/4dbeebf749248a956e44
Usage: $ ./csv-difference.py -d new.csv -s old.csv -o out.csv -c 1
"""

import sys
import argparse
import csv

def main():
    parser = argparse.ArgumentParser(description='Output difference in CSVs.')
    parser.add_argument('-d', '--dataset', help='A CSV file of the full dataset', required=True)
    parser.add_argument('-s', '--subset', help='A CSV file that is a subset of the full dataset', required=True)
    parser.add_argument('-o', '--output', help='The CSV file we should write to (will be overwritten if it exists', required=True)
    parser.add_argument('-c', '--column', help='A number of the column to be compared (0 is column 1, 1 is column 2, etc.)', required=True, type=int)

    args = parser.parse_args()
    dataset_file = args.dataset
    subset_file = args.subset
    output_file = args.output
    column_num = args.column

    with open(dataset_file, 'r') as datafile, open(subset_file, 'r') as subsetfile, open(output_file, 'w') as outputfile:
        data = {row[column_num]: row for row in csv.reader(datafile, delimiter='|', quotechar='"')}
        subset = {row[column_num]: row for row in csv.reader(subsetfile, delimiter='|', quotechar='"')}

        data_keys = set(data.keys())
        subset_keys = set(subset.keys())
        output_keys = data_keys - subset_keys

        output = [data[key] for key in output_keys]
        output_csv = csv.writer(outputfile, delimiter='|', quotechar='"', quoting=csv.QUOTE_ALL)
        for row in output:
            output_csv.writerow(row)

if __name__ == '__main__':
    main()

sys.stdout.flush()

Das erzeugtout.csv

"glider"|"person"|"airport"
"glider"|"person2"|"airport"

Antwort1

Ganz einfach mit awk:

$ awk -F'|' 'NR == FNR {old[$2]; next} !($2 in old)' old.csv new.csv
"glider"|"person"|"airport"
"glider"|"person2"|"airport"

Dadurch wird das zweite Feld der Datei „old.csv“ im Array mit dem Namen „old“ gespeichert. Anschließend werden für die Datei „new.csv“ Datensätze gedruckt, bei denen sich das zweite Feld nicht im Array „old“ befindet.

Es stimmt, dass dabei keine Pipe-Zeichen in Anführungszeichen berücksichtigt werden. Dafür gefällt mir Rubys CSV-Modul:

ruby -rcsv  -e '
  old_col2 = []
  old_data = CSV.foreach("./old.csv", :col_sep => "|") do |row|
    old_col2 << row[1]
  end

  CSV.foreach("./new.csv", :col_sep => "|") do |row|
    if not old_col2.include?(row[1])
      puts CSV.generate_line(row, :col_sep => "|", :force_quotes => true)
    end
  end
'

verwandte Informationen