2011-01-17 36 views
16

Làm việc trong môi linux/vỏ, làm thế nào tôi có thể thực hiện như sau:rút những giá trị độc đáo giữa 2 bộ/file

file văn bản 1 chứa:

1 
2 
3 
4 
5 

file văn bản 2 chứa:

6 
7 
1 
2 
3 
4 

Tôi cần trích xuất các mục trong tệp 2 không có trong tệp 1. Vì vậy, '6' và '7' trong ví dụ này.

Làm cách nào để thực hiện điều này từ dòng lệnh?

cảm ơn rất nhiều!

+0

Bài tập về nhà phải không? Nếu tích cực, vui lòng gắn thẻ như vậy. – karlphillip

+0

dấu phân tách giá trị là gì? – Ass3mbler

+0

bắt tốt! mỗi giá trị nằm trên một dòng riêng; vì vậy dòng mới là sep. – mark

Trả lời

34
$ awk 'FNR==NR {a[$0]++; next} !a[$0]' file1 file2 
6 
7 

Giải thích về cách mã hoạt động:

  • Nếu chúng ta đang làm việc trên file1, theo dõi từng dòng văn bản chúng ta thấy.
  • Nếu chúng tôi đang làm việc trên tệp 2 và chưa thấy văn bản dòng, hãy in văn bản đó.

Giải thích về chi tiết:

  • FNR là tập tin hiện tại của con số kỷ lục
  • NR là con số kỷ lục tổng thể hiện nay từ tất cả các đầu vào tập tin
  • FNR==NR là đúng chỉ khi chúng ta đang đọc file1
  • $0 là dòng văn bản hiện tại
  • a[$0] là một hash với bộ chìa khóa dẫn đến dòng hiện tại của văn bản
  • a[$0]++ bài hát mà chúng ta đã nhìn thấy những dòng hiện tại của văn bản
  • !a[$0] là đúng chỉ khi chúng ta chưa thấy văn bản dòng
  • In dòng văn bản nếu các mô hình trên trả về true, đây là hành vi awk mặc định khi không có hành động rõ ràng được đưa ra
+0

@David cảm ơn, tôi chỉ tối ưu hóa nó ngắn gọn hơn nhiều – SiegeX

+0

ngọt ngào! điều này làm việc tuyệt vời nhưng nếu các giá trị là mỗi trên một dòng riêng biệt, không cách nhau bởi không gian như trong ví dụ của tôi (tôi thực sự đã có chúng trên một dòng mới nhưng SO định dạng chúng trên cùng một dòng)? – mark

+0

@ đánh dấu mã của tôi sẽ hoạt động cho cả hai trường hợp, nhưng nếu mỗi số nằm trên một dòng riêng biệt, bạn hoàn toàn có thể xóa 'RS =" [\ n] "' để làm cho mã ngắn hơn. Ngoài ra, chào mừng bạn đến với SO. – SiegeX

0

Nếu bạn là thực sự bộ về việc này từ dòng lệnh, this site (tìm kiếm cho "không có bản sao tìm thấy ") cóVí dụtìm kiếm các bản sao. Nó có thể là một điểm khởi đầu tốt để xem xét điều đó.

Tuy nhiên, tôi khuyến khích bạn sử dụng Perl hoặc Python cho việc này. Về cơ bản, dòng chảy của chương trình sẽ là:

findUniqueValues(file1, file2){ 
    contents1 = array of values from file1 
    contents2 = array of values from file2 
    foreach(value2 in contents2){ 
     found=false 
     foreach(value1 in contents1){ 
      if (value2 == value1) found=true 
     } 
     if(!found) print value2 
    } 
} 

Đây không phải là cách thanh lịch nhất để làm điều này, vì nó có một O (n^2) thời gian phức tạp, nhưng nó sẽ thực hiện công việc.

+0

cảm ơn rất nhiều David, tôi sẽ có một cái nhìn! – mark

11

Sử dụng một số tiện ích ít được biết đến:

sort file1 > file1.sorted 
sort file2 > file2.sorted 
comm -1 -3 file1.sorted file2.sorted 

chí này bản sao đầu ra, vì vậy nếu có 1 3 trong file1, nhưng 2 trong file2, điều này sẽ vẫn ra 1 3. Nếu đây không phải là những gì bạn muốn, ống đầu ra từ sort qua uniq trước khi viết nó vào một tập tin:

sort file1 | uniq > file1.sorted 
sort file2 | uniq > file2.sorted 
comm -1 -3 file1.sorted file2.sorted 

Có rất nhiều tiện ích trong gói coreutils GNU cho phép cho tất cả các loại thao tác văn bản.

+5

Cuộc gọi tốt trên các tiện ích này. Bạn có thể kết hợp điều này với một form đơn giản hơn nhiều và loại bỏ sự cần thiết cho các file tạm thời: 'comm -13 <(sort file1) <(sort file2)' Tôi vẫn thích 'awk' chỉ vì nó chạy một tiến trình thay vì 3 không yêu cầu tệp được sắp xếp. Điều này có thể tạo ra sự khác biệt lớn trên các tệp lớn. – SiegeX

+3

'join' cũng có thể được sử dụng cho việc này. –

+1

@SiegeX - Cá nhân tôi thích phiên bản có 3 lệnh - theo cách đó nếu tôi cần chỉnh sửa các lệnh (hoặc ví dụ, nhận tập tin cập nhật 1), tôi không cần phải chạy lại điều WHOLE; có thể là một lợi ích cho các tệp thực sự lớn. Ngoài ra, cú pháp bạn cung cấp giống như bash, nó có thể không hoạt động trên các vỏ khác (/ bin/sh hoặc csh dẫn xuất) – DVK

4

với grep:

grep -F -x -v -f file_1 file_2 
+1

Điều này dẫn đến kết quả sai, như có thể được hiển thị nếu a. (dấu chấm) được thêm vào tệp_1. 'grep -F -x -v -f file_1 file_2' thực sự là đúng. – xebeche

+0

@xebeche: Thx! Đã sửa dòng mã theo đề xuất của bạn. –

2

đây là một giải pháp awk

$ awk 'FNR==NR{a[$0]++;next}(!($0 in a))' file1 file2 
6 
7 
+0

Các quy tắc mà bạn có thể sử dụng '()' thay cho '{}' là gì? Tôi cho rằng đây không phải là một con diều hâu bởi vì bạn có xu hướng sử dụng 'gawk' khi đó là trường hợp. – SiegeX

+0

như bạn đã biết, cú pháp awk bao gồm '/ pattern/{action}'. '((! $ 0 trong a))' là phần "mẫu". '{action}' được in theo mặc định. Giống như bạn có thể làm 'NR == 1' (ví dụ). – ghostdog74

+0

Tôi đoán tôi tò mò hơn về bộ đôi parens, tại sao không phải là '(! $ 0 in a)' đủ? Btw, nếu bạn tiền tố bình luận của bạn với '@ username' thì tên người dùng thực sự nhận được thông báo rằng có một bình luận cho họ đang chờ xử lý, nếu không thì họ sẽ không. Tiền tố '@ username' không cần thiết chỉ khi bạn là người viết câu hỏi và/hoặc trả lời mọi người đang bình luận. Vì vậy, về mặt kỹ thuật, tôi không cần phải làm điều đó cho nhận xét này cho bạn. – SiegeX

8

tôi đã tự hỏi đó trong những giải pháp sau đây là "nhanh nhất" cho "lớn hơn" tập tin:

awk 'FNR==NR{a[$0]++}FNR!=NR && !a[$0]{print}' file1 file2 # awk1 by SiegeX 
awk 'FNR==NR{a[$0]++;next}!($0 in a)' file1 file2   # awk2 by ghostdog74 
comm -13 <(sort file1) <(sort file2) 
join -v 2 <(sort file1) <(sort file2) 
grep -v -F -x -f file1 file2 

Kết quả điểm chuẩn của tôi trong ngắn hạn:

  • Không sử dụng grep -Fxf, chậm hơn nhiều (2-4 lần trong các thử nghiệm của tôi).
  • comm hơi nhanh hơn join.
  • Nếu tệp1 và tệp2 đã được sắp xếp, commjoin nhanh hơn awk1 + awk2 nhanh hơn nhiều. (Tất nhiên, chúng không giả định các tệp đã sắp xếp.)
  • awk1 + awk2, được cho là, sử dụng nhiều RAM hơn và ít CPU hơn. Thời gian chạy thực tế thấp hơn cho comm có thể do thực tế là nó sử dụng nhiều chủ đề hơn. Thời gian CPU thấp hơn cho awk1 + awk2.

Vì lợi ích ngắn gọn, tôi bỏ qua chi tiết đầy đủ. Tuy nhiên, tôi cho rằng bất cứ ai quan tâm có thể liên hệ với tôi hoặc chỉ cần lặp lại các bài kiểm tra. Xấp xỉ, thiết lập là

# Debian Squeeze, Bash 4.1.5, LC_ALL=C, slow 4 core CPU 
$ wc file1 file2 
    321599 321599 8098710 file1 
    321603 321603 8098794 file2 

kết quả tiêu biểu của chạy nhanh nhất

awk2: real 0m1.145s user 0m1.088s sys 0m0.056s user+sys 1.144 
awk1: real 0m1.369s user 0m1.324s sys 0m0.044s user+sys 1.368 
comm: real 0m0.980s user 0m1.608s sys 0m0.184s user+sys 1.792 
join: real 0m1.080s user 0m1.756s sys 0m0.140s user+sys 1.896 
grep: real 0m4.005s user 0m3.844s sys 0m0.160s user+sys 4.004 

BTW, cho awkies: Dường như a[$0]=1 nhanh hơn a[$0]++, và (!($0 in a)) nhanh hơn (!a[$0]). Vì vậy, đối với một giải pháp awk Tôi đề nghị:

awk 'FNR==NR{a[$0]=1;next}!($0 in a)' file1 file2 
+0

Đánh giá, kết quả và tối ưu hóa tuyệt vời. Cảm ơn bạn! – joelparkerhenderson

3

Làm thế nào về:

diff file_1 file_2 | grep '^>' | cut -c 3- 

này sẽ in ra các mục trong file_2 mà không phải là trong file_1. Đối với kết quả ngược lại, chỉ cần thay thế '>' bằng '<'. 'cut' loại bỏ hai ký tự đầu tiên được thêm bởi 'diff', không phải là một phần của nội dung gốc.

Các tệp thậm chí không cần phải được sắp xếp.

+0

không hoạt động đối với tôi – muon

Các vấn đề liên quan