
Quiero comparar dos archivos CSV con el siguiente formato. No tienen encabezados. Quiero compararlos por una columna específica (en este caso, la segunda).
Los archivos CSV de origen tienen entre 4 y 5 GB, por lo que cargarlos en la memoria no funcionará.
Si no hay ninguna columna coincidente en old.csv, cada nueva línea se escribe en out.csv.
Esta segunda columna será un enlace html, para simplificar, aquí solo una palabra.
Mi pregunta ¿es posible lograr el mismo resultado con sed, awk, join o grep?
viejo.csv
"person"|"john"|"smith"
"person"|"anne"|"frank"
"person"|"bob"|"macdonald"
"fruit"|"orange"|"banana"
"fruit"|"strawberry"|"fields"
"fruit"|"ringring"|"banana"
nuevo.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 esta generandoout.csv
"glider"|"person"|"airport"
"glider"|"person2"|"airport"
Respuesta1
Súper simple con awk:
$ awk -F'|' 'NR == FNR {old[$2]; next} !($2 in old)' old.csv new.csv
"glider"|"person"|"airport"
"glider"|"person2"|"airport"
Eso almacena el segundo campo del archivo old.csv en la matriz denominada "antiguo" y luego, para el archivo new.csv, imprimirá registros donde el segundo campo no esté en la matriz "antigua".
Es cierto que esto no respetará ningún carácter de barra vertical entre comillas. Para eso, me gusta el módulo csv de 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
'