Удаление строк из одного файла, находящихся в другом файле


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
Я просто попробовал это на 2 файлах по ~ 2 тыс. Строк каждый, и он был убит ОС (конечно, это не очень мощная виртуальная машина, но все же).
Trebor Rude

1
Мне нравится элегантность этого; Я предпочитаю скорость ответа Йоны Кристофера Санвала.
Alex Hall

1
@ arnaud576875: Вы уверены? Это зависит от реализации grep. Если он f2правильно выполняет предварительную обработку до начала поиска, поиск займет всего O (n) времени.
Hellogoodbye

57

Вместо этого попробуйте comm (при условии, что f1 и f2 "уже отсортированы")

comm -2 -3 f1 f2

5
Я не уверен comm, что решение, в котором вопрос не указывает, что строки f1отсортированы, что является необходимым условием для использованияcomm
Габузо

1
Это сработало для меня, поскольку мои файлы были отсортированы и содержали более 250 000 строк в одном из них и только 28 000 в другом. Спасибо!
Зима,

1
Когда это работает (входные файлы сортируются), это происходит очень быстро!
Майк Джарвис

Как и в решении arnaud576875, для меня, использующего cygwin, это устранило повторяющиеся строки во втором файле, которые, возможно, захотят сохранить.
Alex Hall

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) (размер exclude-these.txt) + O (n) (размер from-this.txt)


Почему вы говорите, что файлы не слишком большие? Страх здесь (я предполагаю), что awk запускает систему без системной памяти для создания хеша, или есть какие-то другие ограничения?
rogerdpack

для последователей есть еще один более агрессивный вариант «дезинфекции» строк (поскольку сравнение должно быть точным, чтобы использовать ассоциативный массив), например unix.stackexchange.com/a/145132/8337
rogerdpack

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

1
Это не удается (т.е. не производит никакого вывода), если exclude-these.txtоно пустое. Ответ @ jona-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 - длины файлов. В моем случае n было ~ 25 миллионов, а m ~ 14000. Решение awk было намного быстрее, чем сортировка, и я также предпочел сохранить исходный порядок.


Чем это отличается от ответа Денниса Уильямсона? Единственная разница в том, что он не выполняет присваивание хешу, что немного быстрее, чем это? Алгоритмическая сложность такая же, как у него?
rogerdpack

Разница в основном синтаксическая. Я считаю переменную fболее понятной NR == FNR, но это дело вкуса. Назначение в хэш должно быть настолько быстрым, чтобы между двумя версиями не было заметной разницы в скорости. Я думаю, что ошибался насчет сложности - если поиск постоянный, обновление тоже должно быть постоянным (в среднем). Не знаю, почему я думал, что обновление будет логарифмическим. Отредактирую свой ответ.
jcsahnwaldt Reinstate Monica

Я попробовал несколько этих ответов, и этот был УДИВИТЕЛЬНЫМ быстрым. У меня были файлы с сотнями тысяч строк. Работал как шарм!
Мистер Т.

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)

вот небольшой тест, любезно предоставленный user576875, но со 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.