Menghapus baris dari satu file yang ada di file lain

126

Saya punya file f1:

line1
line2
line3
line4
..
..

Saya ingin menghapus semua baris yang ada di file lain f2:

line2
line8
..
..

Saya mencoba sesuatu dengan catdan sed, yang bahkan tidak mendekati apa yang saya inginkan. Bagaimana saya bisa melakukan ini?

lalli
sumber
4
kemungkinan duplikat Hapus Garis dari Berkas yang muncul di Berkas lain
Sven Hohenstein
Jika Anda ingin menghapus baris dari file yang "bahkan berisi" string dari file lain (misalnya cocok sebagian) lihat unix.stackexchange.com/questions/145079/…
rogerdpack

Jawaban:

154

grep -v -x -f f2 f1 harus melakukan triknya.

Penjelasan:

  • -v untuk memilih garis yang tidak cocok
  • -x untuk mencocokkan hanya seluruh baris
  • -f f2 untuk mendapatkan pola dari f2

Sebagai gantinya, seseorang dapat menggunakan grep -Fatau fgrepuntuk mencocokkan string tetap dari f2alih-alih pola (jika Anda ingin menghapus garis dengan cara "apa yang Anda lihat jika apa yang Anda dapatkan" daripada memperlakukan garis f2sebagai pola regex).

gabuzo.dll
sumber
22
Ini memiliki kompleksitas O (n²) dan akan mulai membutuhkan waktu berjam-jam untuk menyelesaikannya setelah file berisi lebih dari beberapa K baris.
Arnaud Le Blanc
11
Mencari tahu teori mana yang disarankan SO yang memiliki kompleksitas O (n ^ 2) hanya memiliki kompleksitas O (n), tetapi masih membutuhkan waktu berjam-jam untuk bersaing.
HDave
2
Saya baru saja mencoba ini pada 2 file masing-masing ~ 2k baris, dan itu terbunuh oleh OS (memang, ini adalah VM yang tidak terlalu kuat, tapi tetap saja).
Trebor Rude
1
Saya suka keanggunan ini; Saya lebih suka kecepatan jawaban Jona Christopher Sahnwal.
Alex Hall
1
@ arnaud576875: Apakah Anda yakin? Itu tergantung pada implementasi grep. Jika preprocesses f2dengan benar sebelum mulai mencari pencarian hanya akan memakan waktu O (n) waktu.
HelloGoodbye
57

Coba gunakan komunikasi (dengan asumsi f1 dan f2 "sudah diurutkan")

comm -2 -3 f1 f2
Ignacio Vazquez-Abrams
sumber
5
Saya tidak yakin commapakah solusinya memiliki pertanyaan tidak menunjukkan bahwa baris f1diurutkan yang merupakan prasyarat untuk digunakancomm
gabuzo
1
Ini berhasil untuk saya, karena file saya diurutkan dan memiliki 250.000+ baris di salah satunya, hanya 28.000 di yang lain. Terima kasih!
Musim dingin
1
Saat ini berfungsi (file input diurutkan), ini sangat cepat!
Mike Jarvis
Seperti dalam solusi arnaud576875, bagi saya yang menggunakan cygwin, ini menghilangkan baris duplikat di file kedua yang mungkin ingin disimpan.
Alex Hall
9
Anda dapat menggunakan substitusi proses untuk mengurutkan file terlebih dahulu, tentu saja:comm -2 -3 <(sort f1) <(sort f2)
davemyron
14

Untuk mengecualikan file yang tidak terlalu besar, Anda dapat menggunakan array asosiatif AWK.

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

Outputnya akan berada dalam urutan yang sama dengan file "from-this.txt". The tolower()Fungsi membuatnya case-sensitive, jika Anda membutuhkan.

Kompleksitas algoritmik mungkin adalah O (n) (exclude-these.txt size) + O (n) (from-this.txt size)

Dijeda sampai pemberitahuan lebih lanjut.
sumber
Mengapa Anda mengatakan file yang tidak terlalu besar? Ketakutan di sini adalah (saya asumsikan) canggung menjalankan sistem dari memori sistem untuk membuat hash, atau adakah batasan lain?
rogerdpack
untuk pengikut, bahkan ada opsi lain yang lebih agresif untuk "membersihkan" garis (karena perbandingan harus tepat untuk menggunakan larik asosiatif), ex unix.stackexchange.com/a/145132/8337
rogerdpack
@rogerdpack: File pengecualian yang besar akan membutuhkan array hash yang besar (dan waktu pemrosesan yang lama). Sebuah "from-this.txt" yang besar hanya akan membutuhkan waktu pemrosesan yang lama.
Dijeda sampai pemberitahuan lebih lanjut.
1
Ini gagal (yaitu tidak menghasilkan output apa pun) jika exclude-these.txtkosong. Jawaban @ jona-christopher-sahnwaldt di bawah berfungsi dalam kasus ini. Anda juga dapat menentukan beberapa file misalnyaawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell
11

Mirip dengan jawaban Dennis Williamson (sebagian besar perubahan sintaksis, misalnya mengatur nomor file secara eksplisit daripada NR == FNRtriknya):

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

Mengakses r[$0]membuat entri untuk baris itu, tidak perlu menyetel nilai.

Dengan asumsi awk menggunakan tabel hash dengan pencarian konstan dan waktu pembaruan konstan (rata-rata), kompleksitas waktu ini adalah O (n + m), di mana n dan m adalah panjang file. Dalam kasus saya, n adalah ~ 25 juta dan m ~ 14000. Solusi awk jauh lebih cepat daripada menyortir, dan saya juga lebih suka menyimpan pesanan aslinya.

jcsahnwaldt Pasang kembali Monica
sumber
Apa bedanya dengan jawaban Dennis Williamson? Apakah satu-satunya perbedaan bahwa ia tidak melakukan tugas ke dalam hash, jadi sedikit lebih cepat dari ini? Kompleksitas algoritmik sama dengan miliknya?
rogerdpack
Perbedaannya sebagian besar adalah sintaksis. Saya menemukan variabel flebih jelas dari NR == FNR, tapi itu masalah selera. Penetapan ke dalam hash harus sangat cepat sehingga tidak ada perbedaan kecepatan yang dapat diukur antara kedua versi. Saya rasa saya salah tentang kompleksitas - jika pencarian konstan, pembaruan harus konstan juga (rata-rata). Saya tidak tahu mengapa saya pikir pembaruan akan menjadi logaritmik. Saya akan mengedit jawaban saya.
jcsahnwaldt Memulihkan Monica
Saya mencoba banyak jawaban ini, dan yang ini adalah AMAZEBALLS cepat. Saya memiliki file dengan ratusan ribu baris. Bekerja seperti pesona!
Tn. T
1
Ini adalah solusi pilihan saya. Ia bekerja dengan banyak file dan juga file exclude kosong misalnya 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. Sedangkan awksolusi lain gagal dengan file exclude kosong dan hanya dapat mengambil satu file.
Graham Russell
5

jika Anda memiliki 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

Yang memiliki kompleksitas O (N ^ 2). Jika Anda ingin peduli dengan kinerja, inilah versi lain

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

yang menggunakan hash untuk melakukan pengurangan, begitu juga kompleksitas O (n) (ukuran a) + O (n) (ukuran b)

berikut sedikit patokan, milik pengguna576875, tetapi dengan 100K baris, di atas:

$ 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 digunakan untuk menunjukkan tidak ada perbedaan antara 2 file yang dihasilkan.

kurumi
sumber
1
Ini memiliki kompleksitas O (n²) dan akan mulai membutuhkan waktu berjam-jam untuk menyelesaikannya setelah file berisi lebih dari beberapa K baris.
Arnaud Le Blanc
Saya tidak terlalu peduli pada saat ini, karena dia tidak menyebutkan file besar apa pun.
kurumi
3
Tidak perlu bersikap defensif, @ user576875 bukannya tidak menyukai jawaban Anda atau apa pun. :-)
John Parker
versi kedua yang sangat bagus, ruby ​​menang :)
Arnaud Le Blanc
4

Beberapa perbandingan waktu antara berbagai jawaban lainnya:

$ 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 bahkan bukan perbedaan simetris, karena menghapus garis yang muncul beberapa kali di salah satu file.

comm juga bisa digunakan dengan stdin dan di sini string:

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

Sepertinya pekerjaan yang cocok untuk shell 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
Benoit
sumber
1

Apakah Anda mencoba ini dengan 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
Ruan
sumber
0

Bukan jawaban 'pemrograman', tetapi inilah solusi cepat dan kotor: kunjungi saja http://www.listdiff.com/compare-2-lists-difference-tool .

Jelas tidak akan berfungsi untuk file besar tetapi itu berhasil untuk saya. Beberapa catatan:

  • Saya tidak berafiliasi dengan situs web ini dengan cara apa pun (jika Anda masih tidak mempercayai saya, Anda dapat mencari alat lain secara online; saya menggunakan istilah penelusuran "setel daftar perbedaan secara online")
  • Situs web yang ditautkan tampaknya melakukan panggilan jaringan pada setiap perbandingan daftar, jadi jangan memberinya data sensitif apa pun
youngrrrr
sumber