
Я хочу сравнить два CSV-файла со следующим форматом. У них нет заголовков. Я хочу сравнить их по определенному столбцу (в данном случае, по второму).
Исходные CSV-файлы весят около 4–5 ГБ, поэтому загрузить их в память не получится.
Если в old.csv нет соответствующего столбца, то каждая новая строка записывается в out.csv.
Во втором столбце будет ссылка HTML, здесь для простоты только одно слово.
У меня вопрос: возможно ли добиться того же результата с помощью sed, awk, join или grep?
старый.csv
"person"|"john"|"smith"
"person"|"anne"|"frank"
"person"|"bob"|"macdonald"
"fruit"|"orange"|"banana"
"fruit"|"strawberry"|"fields"
"fruit"|"ringring"|"banana"
новый.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()
Который генерируетout.csv
"glider"|"person"|"airport"
"glider"|"person2"|"airport"
решение1
Очень просто с awk:
$ awk -F'|' 'NR == FNR {old[$2]; next} !($2 in old)' old.csv new.csv
"glider"|"person"|"airport"
"glider"|"person2"|"airport"
Это сохранит второе поле файла old.csv в массиве с именем «old», а затем для файла new.csv будут выведены записи, в которых второе поле отсутствует в массиве «old».
Правда, это не будет учитывать символы вертикальной черты внутри кавычек. Для этого мне нравится модуль csv в 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
'