Mesclando dois CSVs comparados apenas por uma coluna específica

2

Eu quero comparar dois arquivos CSV com o seguinte formato. Eles não têm cabeçalhos. Eu quero compará-los por uma coluna específica (neste caso, o segundo).

Os arquivos CSV de origem têm entre 4 e 5 GB, portanto, carregá-los na memória não funciona.

Se não houver colunas correspondentes em old.csv, todas as novas linhas serão gravadas em out.csv.

Esta segunda coluna será um link html, para a simplicidade, aqui apenas uma palavra.

Minha pergunta é possível obter o mesmo resultado com sed, awk, join ou grep?

old.csv

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

new.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()

Que está gerando out.csv

"glider"|"person"|"airport"
"glider"|"person2"|"airport"
    
por Lanti 04.11.2016 / 19:53

1 resposta

3

Super simples com o awk:

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

Isso armazena o segundo campo do arquivo old.csv na matriz chamada "old" e, em seguida, para o novo arquivo .csv, ele imprimirá os registros em que o segundo campo não está na matriz "antiga".

É verdade que isso não respeitará nenhum caractere de pipe entre aspas. Para isso, eu gosto do módulo csv do ruby:

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
'
    
por 04.11.2016 / 20:07