Видалення рядків з одного файлу, який знаходиться в іншому файлі


126

У мене є файл f1:

line1
line2
line3
line4
..
..

Я хочу видалити всі рядки, що знаходяться в іншому файлі f2:

line2
line8
..
..

Я спробував щось із, catі sedце було навіть близько до того, що я мав намір. Як я можу це зробити?



Якщо ви хочете видалити рядки з файлу, що містять навіть рядки з іншого файлу (наприклад, часткові збіги), див. Unix.stackexchange.com/questions/145079/…
rogerdpack

Відповіді:


154

grep -v -x -f f2 f1 повинен зробити трюк.

Пояснення:

  • -v щоб вибрати невідповідні лінії
  • -x щоб відповідати лише цілим рядкам
  • -f f2 щоб отримати зразки f2

Замість цього можна використовувати grep -Fабо fgrepдля відповідності фіксованих рядків з , f2а не моделей (в разі , якщо ви хочете видалити рядки в «то , що ви бачите , якщо то , що ви отримуєте» спосіб , а не обробляти рядки в f2якості регулярних виразів шаблонів).


22
Ця складність має O (n²), а завершення розпочне забирати години, як тільки файли містять більше кількох K рядків.
Арно Ле Блан

11
З'ясування, які запропоновані SO алгоритми мають складність O (n ^ 2), має лише O (n) складність, але все ще може зайняти години, щоб конкурувати.
HDave

2
Я щойно спробував це у двох файлах по ~ 2k рядків кожен, і він загинув ОС (надано, це не дуже потужний VM, але все ж).
Trebor Rude

1
Я люблю витонченість цього; Я віддаю перевагу швидкості відповіді Йони Крістофер Санваль.
Алекс Холл

1
@ arnaud576875: Ви впевнені? Це залежить від реалізації grep. Якщо вона буде попередньо оброблена f2належним чином перед початком пошуку, пошук займе лише O (n) час.
HelloGoodbye

57

Спробуйте кому замість цього (припустимо, що f1 і f2 "вже відсортовані")

comm -2 -3 f1 f2

5
Я не впевнений comm, що рішення має питання, чи не вказує на те, що рядки в f1сортованих, що є необхідною умовою для використанняcomm
gabuzo

1
Це працювало для мене, оскільки мої файли були відсортовані та мали в одній з них 250 000+ рядків, в іншій лише 28 000. Дякую!
Зима

1
Коли це працює (вхідні файли сортуються), це надзвичайно швидко!
Майк Джарвіс

Як і у рішенні arnaud576875, для мене, використовуючи cygwin, це усунуло повторювані рядки у другому файлі, які, можливо, захочеться зберегти.
Алекс Холл

9
Ви можете використовувати процедуру заміни, щоб спершу сортувати файли, звичайно:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

Для виключення не надто великих файлів, ви можете використовувати асоціативні масиви AWK.

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

Вихід буде в тому ж порядку, що і файл "from-this.txt". Ця tolower()функція робить її нечутливою до регістру, якщо вам це потрібно.

Алгоритмічна складність, ймовірно, буде O (n) (виключати-розмір цих.txt) + O (n) (від-this.txt розмір)


Чому ви кажете файли, які не надто великі? Тут страх полягає в (я припускаю) дивним запуском системи із системної пам'яті для створення хешу, чи є якесь інше обмеження?
rogerdpack

для послідовників, є навіть інший агресивніший варіант "санітизувати" рядки (оскільки порівняння має бути точним, щоб використовувати асоціативний масив), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: великий файл виключення потребує великого хеш-масиву (і тривалий час обробки). Великий "from-this.txt" вимагатиме лише тривалого часу на обробку.
Призупинено до подальшого повідомлення.

1
Це не вдається (тобто не дає жодного результату), якщо exclude-these.txtвін порожній. У цьому випадку відповідь @nana-christopher-sahnwaldt нижче. Ви також можете вказати кілька файлів, наприкладawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Грем Рассел

11

Подібно до відповіді Денніса Вільямсона (переважно синтаксичні зміни, наприклад, встановлення номера файлу явно замість NR == FNRхитрості):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

Доступ r[$0]створює запис для цього рядка, не потрібно встановлювати значення.

Якщо припустити, що awk використовує хеш-таблицю з постійним пошуком і (в середньому) постійним часом оновлення, часова складність цього буде O (n + m), де n і m - довжини файлів. У моєму випадку п було ~ 25 мільйонів, а m ~ 14000. Рішення awk було набагато швидшим, ніж сортування, і я також вважав за краще дотримуватися початкового замовлення.


Чим це відрізняється від відповіді Денніса Вільямсона? Єдина відмінність у тому, що він не виконує призначення в хеші, так що трохи швидше, ніж це? Алгоритмічна складність така ж, як і його?
rogerdpack

Різниця здебільшого синтаксична. Я вважаю цю змінну fчіткішою NR == FNR, але це питання смаку. Призначення в хеш має бути настільки швидким, щоб між двома версіями не було вимірної різниці швидкостей. Я думаю, що я помилявся щодо складності - якщо пошук постійний, оновлення має бути постійним (в середньому). Я не знаю, чому я вважав, що оновлення буде логарифмічним. Я відредагую свою відповідь.
jcsahnwaldt Відновити Моніку

Я спробував купу цих відповідей, і ця швидка була AMAZEBALLS. У мене були файли із сотнями тисяч рядків. Працював як шарм!
Містер T

1
Це моє бажане рішення. Він працює з декількома файлами, а також порожні файли виключення, наприклад awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out. Тоді як інше awkрішення не працює з порожнім виключенням файлу і може приймати лише один.
Грем Рассел

5

якщо у вас є Ruby (1.9+)

#!/usr/bin/env ruby 
b=File.read("file2").split
open("file1").each do |x|
  x.chomp!
  puts x if !b.include?(x)
end

Який має складність O (N ^ 2). Якщо ви хочете дбати про продуктивність, ось ще одна версія

b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}

який використовує хеш для здійснення віднімання, так це складність O (n) (розмір a) + O (n) (розмір b)

ось невеликий орієнтир, люб’язно надаючи користувач576875, але із 100K рядками, із зазначеного вище:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time ruby test.rb > ruby.test

real    0m0.639s
user    0m0.554s
sys     0m0.021s

$time sort file1 file2|uniq -u  > sort.test

real    0m2.311s
user    0m1.959s
sys     0m0.040s

$ diff <(sort -n ruby.test) <(sort -n sort.test)
$

diff було використано, щоб показати, що немає відмінностей між двома створеними файлами.


1
Ця складність має O (n²), а завершення розпочне забирати години, як тільки файли містять більше кількох K рядків.
Арно Ле Блан

Мені це зовсім не цікаво на цьому етапі, тому що він не згадав жодних великих файлів.
kurumi

3
Не потрібно бути настільки оборонним, це не так, якби @ user576875 відмовився від вашої відповіді чи чогось іншого. :-)
Джон Паркер

дуже приємна друга версія, перемагає рубін :)
Арно Ле Блан

4

Деякі порівняння часу між різними іншими відповідями:

$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null

real    0m0.019s
user    0m0.023s
sys     0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null

real    0m0.026s
user    0m0.018s
sys     0m0.007s
$ time grep -xvf f2 f1 > /dev/null

real    0m43.197s
user    0m43.155s
sys     0m0.040s

sort f1 f2 | uniq -u Це навіть не симетрична різниця, оскільки вона видаляє рядки, що з’являються кілька разів у будь-якому файлі.

comm також можна використовувати з stdin та тут рядками:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

Здається, робота, яка підходить для оболонки SQLite:

create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify  .separator ××any_improbable_string×× 
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q

1

Ви пробували це з sed?

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh

sed -i 's#$#%%g'"'"' f1#g' f2.sh

sed -i '1i#!/bin/bash' f2.sh

sh f2.sh

0

Відповідь не "програмування", але ось швидке та брудне рішення: просто перейдіть на сторінку http://www.listdiff.com/compare-2-lists-difference-tool .

Очевидно, що не працюватимуть для величезних файлів, але це зробило трюк для мене. Кілька приміток:

  • Я жодним чином не пов'язаний з веб-сайтом (якщо ви все ще не вірите мені, ви можете просто шукати інший інструмент в Інтернеті; я використовував пошуковий термін "встановити список різниць в Інтернеті")
  • Зв'язаний веб-сайт, схоже, здійснює мережеві дзвінки за кожним порівнянням списку, тому не вводьте в нього ніяких конфіденційних даних
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.