Bagaimana cara menghapus garis yang muncul pada file B dari file A lainnya?

160

Saya punya file besar A (terdiri dari email), satu baris untuk setiap email. Saya juga punya file B lain yang berisi set surat lain.

Perintah mana yang akan saya gunakan untuk menghapus semua alamat yang muncul dalam file B dari file A.

Jadi, jika file A mengandung:

A
B
C

dan file B berisi:

B    
D
E

Maka file A harus dibiarkan dengan:

A
C

Sekarang saya tahu ini adalah pertanyaan yang mungkin lebih sering ditanyakan, tetapi saya hanya menemukan satu perintah online yang memberi saya kesalahan dengan pembatas yang buruk.

Bantuan apa pun akan sangat dihargai! Seseorang pasti akan datang dengan satu kalimat cerdas, tapi saya bukan ahli shell.

slhck
sumber
1
Sebagian besar jika jawabannya di sini adalah untuk file yang diurutkan, dan yang paling jelas hilang, yang tentu saja bukan kesalahan Anda, tetapi itu membuat yang lain lebih bermanfaat secara umum.
tripleee

Jawaban:

202

Jika file diurutkan (ada dalam contoh Anda):

comm -23 file1 file2

-23menekan garis yang ada di kedua file, atau hanya di file 2. Jika file tidak diurutkan, pipa mereka sortterlebih dahulu ...

Lihat halaman manual di sini

Pola Dasar Paul
sumber
8
comm -23 file1 file2 > file3akan menampilkan konten di file1 bukan di file2, ke file3. Dan mv file3 file1akhirnya akan menghapus konten yang berlebihan di file1.
Spektral
2
Atau, gunakan comm -23 file1 file2 | sponge file1. Tidak diperlukan pembersihan.
Socowi
Tautan halaman manual
Felix Rabe
@ Socowi Apa itu spons? Saya tidak memilikinya di sistem saya. (macos 10.13)
Felix Rabe
@ Felelixabe, yah, itu melelahkan. Diganti dengan tautan Anda. Terima kasih
The Archetypal Paul
84

grep -Fvxf <lines-to-remove> <all-lines>

  • bekerja pada file yang tidak diurutkan
  • mempertahankan pesanan
  • adalah POSIX

Contoh:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Keluaran:

b
a
01
b

Penjelasan:

  • -F: gunakan string literal alih-alih BRE default
  • -x: hanya pertimbangkan kecocokan yang cocok dengan seluruh baris
  • -v: cetak tidak cocok
  • -f file: ambil pola dari file yang diberikan

Metode ini lebih lambat pada file yang diurutkan sebelum metode lain, karena lebih umum. Jika kecepatan juga penting, lihat: Cara cepat menemukan baris dalam satu file yang tidak ada di file lain?

Berikut adalah otomatisasi bash cepat untuk operasi in-line:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub hulu .

pemakaian:

remove-lines lines-to-remove remove-from-this-file

Lihat juga: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
55

awk untuk menyelamatkan!

Solusi ini tidak memerlukan input yang diurutkan. Anda harus menyediakan fileB terlebih dahulu.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

kembali

A
C

Bagaimana cara kerjanya?

NR==FNR{a[$0];next} idiom adalah untuk menyimpan file pertama dalam array asosiatif sebagai kunci untuk tes "berisi" nanti.

NR==FNR sedang memeriksa apakah kami memindai file pertama, di mana penghitung garis global (NR) sama dengan penghitung baris file (FNR) saat ini.

a[$0] menambahkan baris saat ini ke array asosiatif sebagai kunci, perhatikan bahwa ini berperilaku seperti set, di mana tidak akan ada nilai duplikat (kunci)

!($0 in a)kita sekarang di file berikutnya, inadalah tes berisi, di sini memeriksa apakah baris saat ini di set kita huni di langkah pertama dari file pertama, !meniadakan kondisi. Yang hilang di sini adalah tindakan, yang secara default adalah {print}dan biasanya tidak ditulis secara eksplisit.

Perhatikan bahwa ini sekarang dapat digunakan untuk menghapus kata-kata daftar hitam.

$ awk '...' badwords allwords > goodwords

dengan sedikit perubahan dapat membersihkan banyak daftar dan membuat versi yang sudah dibersihkan.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
karakfa
sumber
tanda penuh pada ini. Untuk menggunakan ini pada baris perintah di GnuWin32 di Windows, ganti camilan tunggal dengan tanda kutip ganda. bekerja menyenangkan. terimakasih banyak.
twobob
Ini bekerja tetapi bagaimana saya bisa mengarahkan output ke fileA dalam bentuk A (Dengan baris baru) B
Anand Builders
Saya kira maksud Anda A\nC, menulis ke file temp terlebih dahulu dan menimpa file asli... > tmp && mv tmp fileA
karakfa
Nilai penuh dalam hal ini dari saya juga. Awk ini membutuhkan semua 1 detik untuk memproses file dengan 104.000 entri: +1:
MitchellK
Saat menggunakan ini dalam skrip, pastikan untuk terlebih dahulu memeriksa yang fileBtidak kosong (panjang 0 byte), karena jika demikian, Anda akan mendapatkan hasil kosong alih-alih konten yang diharapkan fileA. (Penyebab: FNR==NRakan berlaku untuk fileAsaat itu.)
Peter Nowee
18

Cara lain untuk melakukan hal yang sama (juga membutuhkan input yang diurutkan):

join -v 1 fileA fileB

Di Bash, jika file tidak disortir:

join -v 1 <(sort fileA) <(sort fileB)
Dijeda sampai pemberitahuan lebih lanjut.
sumber
7

Anda dapat melakukan ini kecuali file Anda diurutkan

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formatadalah untuk baris yang ada di file b tetapi tidak di --old-..is untuk baris yang ada di file a tetapi tidak di b --unchanged-..adalah untuk baris yang ada di keduanya. %Lmembuatnya jadi garis dicetak persis.

man diff

untuk lebih jelasnya

aec
sumber
1
Anda mengatakan ini akan berfungsi kecuali file diurutkan. Masalah apa yang terjadi jika mereka diurutkan? Bagaimana jika mereka diurutkan sebagian?
Carlos Macasaet
1
Itu sebagai respons terhadap solusi di atas yang menyarankan penggunaan commperintah. commmembutuhkan file untuk diurutkan, jadi jika mereka diurutkan Anda dapat menggunakan solusi itu juga. Anda dapat menggunakan solusi ini terlepas dari apakah file diurutkan atau tidak
aec
7

Penyempurnaan dari jawaban bagus @ karakfa ini mungkin terasa lebih cepat untuk file yang sangat besar. Seperti dengan jawaban itu, file tidak perlu diurutkan, tetapi kecepatan terjamin berdasarkan array asosiatif awk. Hanya file pencarian yang disimpan dalam memori.

Formulasi ini juga memungkinkan untuk kemungkinan bahwa hanya satu bidang tertentu ($ N) dalam file input yang akan digunakan dalam perbandingan.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Keuntungan lain dari pendekatan ini adalah mudah untuk memodifikasi kriteria perbandingan, misalnya untuk memangkas ruang putih terdepan dan tertinggal.)

puncak
sumber
Ini lebih sulit untuk digunakan dalam skenario cross-case cross platform daripada liner satu lainnya. Namun tutup untuk upaya kinerja
twobob
2

Anda dapat menggunakan Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'
Halo selamat tinggal
sumber
2

Kamu bisa memakai - diff fileA fileB | grep "^>" | cut -c3- > fileA

Ini akan berfungsi untuk file yang tidak diurutkan juga.

Darpan
sumber
-1

Untuk menghapus garis umum antara dua file, Anda dapat menggunakan perintah grep, comm atau join.

grep hanya berfungsi untuk file kecil. Gunakan -v bersama dengan -f.

grep -vf file2 file1 

Ini menampilkan baris dari file1 yang tidak cocok dengan baris apa pun di file2.

comm adalah perintah utilitas yang bekerja pada file yang diurutkan secara leksikal. Dibutuhkan dua file sebagai input dan menghasilkan tiga kolom teks sebagai output: hanya baris dalam file pertama; baris hanya di file kedua; dan baris di kedua file. Anda dapat menekan pencetakan kolom apa pun dengan menggunakan opsi -1, -2 atau -3.

comm -1 -3 file2 file1

Ini menampilkan baris dari file1 yang tidak cocok dengan baris apa pun di file2.

Akhirnya, ada gabungan, perintah utilitas yang melakukan gabungan kesetaraan pada file yang ditentukan. Opsi -v-nya juga memungkinkan untuk menghapus garis umum antara dua file.

join -v1 -v2 file1 file2
Aakarsh Gupta
sumber
Semua ini sudah diberikan dalam jawaban lain. Grep Anda membutuhkan -F, atau Anda akan mendapatkan hasil yang aneh ketika garis terlihat seperti regexps
The Archetypal Paul