Perintah Unix untuk menemukan baris yang umum di dua file

179

Saya yakin saya pernah menemukan perintah unix yang dapat mencetak baris umum dari dua atau lebih file, apakah ada yang tahu namanya? Itu jauh lebih sederhana daripada diff.

terlalu banyak php
sumber
5
Jawaban atas pertanyaan ini tidak selalu apa yang diinginkan semua orang, karena commmemerlukan file input yang diurutkan. Jika Anda ingin hanya baris demi baris yang umum, itu bagus. Tetapi jika Anda menginginkan apa yang saya sebut "anti-diff", commtidak melakukan pekerjaan.
Robert P. Goldman
@ RobertP.Goldman adakah cara untuk mendapatkan kesamaan antara dua file saat file1 berisi pola parsial like pr-123-xy-45dan file2 berisi ec11_orop_pr-123-xy-45.gz. Saya perlu file3 mengandungec11_orop_pr-123-xy-45.gz
Chandan Choudhury
Lihat ini untuk menyortir file teks baris demi baris
y2k-shubham

Jawaban:

216

Perintah yang Anda cari adalah comm. misalnya:-

comm -12 1.sorted.txt 2.sorted.txt

Sini:

-1 : tekan kolom 1 (baris unik ke 1.sorted.txt)

-2 : tekan kolom 2 (baris unik ke 2.sorted.txt)

Jonathan Leffler
sumber
27
Penggunaan umum: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK
45
Sementara comm membutuhkan file yang diurutkan, Anda dapat menggunakan grep -f file1 file2 untuk mendapatkan baris umum dari kedua file tersebut.
Ferdy
2
@ferdy (Mengulangi komentar saya dari jawaban Anda, karena jawaban Anda pada dasarnya adalah jawaban berulang yang diposting sebagai komentar) grepmelakukan beberapa hal aneh yang mungkin tidak Anda harapkan. Secara khusus, semua yang ada di 1.txtdalamnya akan ditafsirkan sebagai ekspresi reguler dan bukan string biasa. Selain itu, setiap baris kosong di 1.txtakan cocok dengan semua baris di 2.txt. Jadi grephanya akan bekerja dalam situasi yang sangat spesifik. Anda setidaknya ingin menggunakan fgrep(atau grep -f) tetapi hal yang kosong mungkin akan mendatangkan malapetaka pada proses ini.
Christopher Schultz
11
Lihat jawaban ferdy di bawah ini, dan Christopher Schultz dan komentar saya tentang itu. TL; DR - gunakan . grep -F -x -f file1 file2
Jonathan Leffler
1
@bapors: Saya telah menyediakan Tanya Jawab otomatis sebagai Cara mendapatkan output dari commperintah menjadi 3 file terpisah? Jawabannya terlalu besar untuk cocok dengan nyaman di sini.
Jonathan Leffler
62

Untuk dengan mudah menerapkan perintah comm ke file yang tidak disortir , gunakan subtitusi proses Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Jadi file abc dan def memiliki satu baris yang sama, yang dengan "132". Menggunakan comm pada file yang tidak disortir:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

Baris terakhir tidak menghasilkan output, baris umum tidak ditemukan.

Sekarang gunakan comm pada file yang diurutkan, mengurutkan file dengan proses substitusi:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Sekarang kami dapat jalur 132!

Stephan Wehner
sumber
2
jadi ... sort abc > abc.sorted, sort dev > def.sortedlalu comm -12 abc.sorted def.sorted?
Nikana Reklawyks
1
@NikanaReklawyks Dan kemudian ingatlah untuk menghapus file sementara sesudahnya, dan mengatasi pembersihan jika ada kesalahan. Dalam banyak skenario, proses substitusi juga akan jauh lebih cepat karena Anda dapat menghindari disk I / O selama hasilnya sesuai dengan memori.
tripleee
29

Untuk melengkapi Perl one-liner, inilah awkpersamaannya:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Ini akan membaca semua baris dari file1dalam array arr[], dan kemudian memeriksa setiap baris file2jika sudah ada dalam array (yaitu file1). Garis-garis yang ditemukan akan dicetak sesuai urutan kemunculannya file2. Perhatikan bahwa perbandingan in arrmenggunakan seluruh baris dari file2sebagai indeks ke array, sehingga hanya akan melaporkan kecocokan tepat pada seluruh baris.

Tatjana Heuser
sumber
2
INI (!) Adalah jawaban yang benar. Tak satu pun dari yang lain dapat dibuat bekerja secara umum (saya belum mencoba perlyang lain, karena). Terima kasih satu juta, Nona
entonio
1
Mempertahankan pesanan saat menampilkan garis umum dapat sangat berguna dalam beberapa kasus yang akan mengecualikan comm karena itu.
tuxayo
1
Jika ada yang ingin melakukan hal yang sama berdasarkan kolom tertentu tetapi tidak tahu awk, ganti saja $ 0 dengan $ 5 misalnya untuk kolom 5 sehingga Anda mendapatkan baris yang dibagi dalam 2 file dengan kata-kata yang sama di kolom 5
FatihSarigol
24

Mungkin maksud Anda comm?

Bandingkan file yang diurutkan FILE1 dan FILE2 baris demi baris.

Tanpa opsi, hasilkan output tiga kolom. Kolom satu berisi baris unik untuk FILE1, kolom dua berisi baris unik untuk FILE2, dan kolom tiga berisi baris yang umum untuk kedua file.

Rahasia dalam menemukan informasi ini adalah halaman info. Untuk program GNU, mereka jauh lebih detail daripada halaman manualnya. Coba info coreutilsdan itu akan mencantumkan Anda semua utilitas kecil yang bermanfaat.

Johannes Schaub - litb
sumber
19

Sementara

grep -v -f 1.txt 2.txt > 3.txt

memberi Anda perbedaan dua file (apa yang ada di 2.txt dan bukan di 1.txt), Anda dapat dengan mudah melakukan a

grep -f 1.txt 2.txt > 3.txt

untuk mengumpulkan semua jalur umum, yang seharusnya memberikan solusi mudah untuk masalah Anda. Jika Anda memiliki file yang diurutkan, Anda harus mengambil comm. Salam!

Ferdy
sumber
2
grepmelakukan beberapa hal aneh yang mungkin tidak Anda harapkan. Secara khusus, semua yang ada di 1.txtdalamnya akan ditafsirkan sebagai ekspresi reguler dan bukan string biasa. Selain itu, setiap baris kosong di 1.txtakan cocok dengan semua baris di 2.txt. Jadi ini hanya akan bekerja dalam situasi yang sangat spesifik.
Christopher Schultz
13
@ChristopherSchultz: Dimungkinkan untuk memutakhirkan jawaban ini agar berfungsi lebih baik menggunakan grepnotasi POSIX , yang didukung oleh yang grepditemukan pada sebagian besar varian Unix modern. Tambahkan -F(atau gunakan fgrep) untuk menekan ekspresi reguler. Tambahkan -x(tepatnya) agar hanya cocok dengan seluruh baris.
Jonathan Leffler
Mengapa kami harus mengambil commfile yang diurutkan?
Ulysse BN
2
@UlysseBN commdapat bekerja dengan file besar yang sewenang-wenang selama mereka disortir karena hanya perlu menyimpan tiga baris dalam memori (Saya kira GNU commbahkan akan tahu untuk menyimpan awalan hanya jika garisnya benar-benar panjang). The grepsolusi perlu menjaga semua ekspresi pencarian di memori.
tripleee
9

Jika kedua file belum diurutkan, Anda dapat menggunakan:

comm -12 <(sort a.txt) <(sort b.txt)

dan itu akan berhasil, menghindari pesan kesalahan comm: file 2 is not in sorted order saat melakukan comm -12 a.txt b.txt.

Basj
sumber
Anda benar, tetapi ini pada dasarnya mengulangi jawaban lain , yang sebenarnya tidak memberikan manfaat apa pun. Jika Anda memutuskan untuk menjawab pertanyaan yang lebih lama yang sudah mapan dan jawaban yang benar, menambahkan jawaban baru di akhir hari mungkin tidak memberi Anda kredit apa pun. Jika Anda memiliki beberapa informasi baru yang khas, atau Anda yakin jawaban lainnya semuanya salah, tentu saja tambahkan jawaban baru, tetapi 'jawaban lain' memberikan informasi dasar yang sama lama setelah pertanyaan diajukan biasanya dimenangkan ' tidak memberi Anda banyak kredit.
Jonathan Leffler
Saya bahkan tidak melihat jawaban ini @JonathanLeffler karena bagian ini adalah bagian paling akhir dari jawaban, dicampur dengan unsur-unsur jawaban lain sebelumnya. Sementara jawaban yang lain lebih tepat, keuntungan saya, saya pikir adalah bagi seseorang yang menginginkan solusi cepat hanya akan memiliki 2 baris untuk dibaca. Terkadang kami mencari jawaban terinci dan terkadang kami tergesa-gesa dan jawaban siap-tempel yang cepat dibaca tidak masalah.
Basj
Juga saya tidak peduli dengan kredit / rep, saya tidak memposting untuk tujuan ini.
Basj
1
Perhatikan juga bahwa sintaksis substitusi proses <(command)tidak portabel untuk shell POSIX, meskipun bekerja di Bash dan beberapa lainnya.
tripleee
8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2
pengguna2592005
sumber
ini bekerja lebih baik daripada commperintah karena mencari setiap baris file1di file2mana commhanya akan membandingkan jika baris ndi file1yaitu sama dengan garis ndi file2.
teriiehina
1
@teriiehina: Tidak; commtidak hanya membandingkan baris N di file1 dengan baris N di file2. Ia dapat dengan baik mengelola serangkaian baris yang disisipkan dalam file mana pun (yang setara dengan menghapus serangkaian baris dari file lain, tentu saja). Itu hanya membutuhkan input untuk diurutkan.
Jonathan Leffler
Lebih baik daripada commjawaban jika seseorang ingin menjaga ketertiban. Lebih baik daripada awkmenjawab jika seseorang tidak ingin duplikat.
tuxayo
Penjelasannya ada di sini: stackoverflow.com/questions/17552789/…
Chris Koknat
5
awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2
RS John
sumber
3

Pada versi Linux yang terbatas (seperti QNAP (nas) yang saya kerjakan):

  • comm tidak ada
  • grep -f file1 file2dapat menyebabkan beberapa masalah seperti yang dikatakan oleh @ChristopherSchultz dan penggunaannya grep -F -f file1 file2sangat lambat (lebih dari 5 menit - belum selesai - lebih dari 2-3 detik dengan metode di bawah ini pada file di atas 20MB)

Jadi inilah yang saya lakukan:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Jika files.same.sortedharus dalam urutan yang sama dari yang asli, daripada tambahkan baris ini untuk urutan yang sama dari file1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

atau, untuk urutan yang sama dari file2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same
Master DJon
sumber
2

Hanya untuk referensi jika seseorang masih mencari cara melakukan ini untuk banyak file, lihat jawaban tertaut untuk Menemukan garis yang cocok di banyak file.


Menggabungkan dua jawaban ini ( ans1 dan ans2 ), saya pikir Anda bisa mendapatkan hasil yang Anda butuhkan tanpa mengurutkan file:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Cukup simpan, berikan hak eksekusi ( chmod +x compareFiles.sh), dan jalankan. Ini akan mengambil semua file yang ada di direktori kerja saat ini dan akan membuat perbandingan semua-vs-semua meninggalkan dalam file "matching_lines" hasilnya.

Hal-hal yang harus diperbaiki:

  • Lewati direktori
  • Hindari membandingkan semua file dua kali (file1 vs file2 dan file2 vs file1).
  • Mungkin menambahkan nomor baris di sebelah string yang cocok
akarpovsky
sumber
-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Ini harus dilakukan.

Alan Joseph
sumber
1
Anda mungkin harus menggunakan rm -f file3.txtjika Anda akan menghapus file; itu tidak akan melaporkan kesalahan jika file tidak ada. OTOH, itu tidak perlu jika skrip Anda hanya menggema ke output standar, membiarkan pengguna skrip memilih ke mana output harus pergi. Pada akhirnya, Anda mungkin ingin menggunakan $1dan $2(argumen baris perintah) alih-alih nama file yang tetap ( file1.outdan file2.out). Itu meninggalkan algoritme: itu akan lambat. Ini akan dibaca file2.outsatu kali untuk setiap baris file1.out. Ini akan lambat jika file besar (katakanlah beberapa kilobyte).
Jonathan Leffler
Meskipun ini secara nominal dapat bekerja jika Anda memiliki input yang tidak mengandung karakter meta shell (petunjuk: lihat peringatan apa yang Anda dapatkan dari shellcheck.net ), pendekatan naif ini sangat tidak efisien. Alat seperti grep -Fyang membaca satu file ke dalam memori dan kemudian melakukan satu melewati yang lain menghindari berulang kali berulang-ulang di kedua file input.
tripleee