Có cách nào để 'uniq' theo cột không?


195

Tôi có một tệp .csv như thế này:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Tôi phải xóa các email trùng lặp (toàn bộ dòng) khỏi tệp (tức là một trong những dòng có overflow@example.comtrong ví dụ trên). Làm cách nào để tôi chỉ sử dụng uniqtrên trường 1 (cách nhau bằng dấu phẩy)? Theo man, uniqkhông có tùy chọn cho các cột.

Tôi đã thử một cái gì đó với sort | uniqnhưng nó không hoạt động.

Câu trả lời:


325
sort -u -t, -k1,1 file
  • -u cho độc đáo
  • -t, vì vậy dấu phẩy là dấu phân cách
  • -k1,1 cho trường khóa 1

Kết quả kiểm tra:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
điều này không hoạt động nếu cột chứa dấu phẩy (có trích dẫn)
user775187

13
Tại sao bạn cần, 1 trong -k1,1? Tại sao không chỉ -k1?
xin chào_there_andy

18
@hello_there_andy: Điều này được giải thích trong hướng dẫn ( man sort). Nó là viết tắt của vị trí bắt đầu và dừng lại.
Serrano

3
@CarlSmotricz: Tôi đã thử nghiệm nó và nó đã xác nhận sorttrang của bạn nói gì: " -u, --unique với -c, kiểm tra thứ tự nghiêm ngặt; không có -c, chỉ xuất ra lần đầu tiên của một lần chạy bằng nhau ." Vì vậy, nó thực sự là "sự xuất hiện đầu tiên của bản sao trước khi sắp xếp."
Lão máu

2
Điều này cũng thay đổi thứ tự của các dòng, phải không?
rkachach

102
awk -F"," '!_[$1]++' file
  • -F đặt dấu phân cách trường.
  • $1 là lĩnh vực đầu tiên.
  • _[val]tra cứu valtrong hàm băm _(một biến thông thường).
  • ++ gia tăng, và trả lại giá trị cũ.
  • ! Trả về logic không.
  • có một bản in ngầm ở cuối.

4
Cách tiếp cận này nhanh hơn hai lần so với sắp xếp
cắn

9
Điều này cũng có lợi ích bổ sung của việc giữ các dòng theo thứ tự ban đầu!
AffluentOwl

8
Nếu bạn cần uniq cuối cùng thay vì đầu tiên thì tập lệnh awk này sẽ giúp:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima

3
@eshwar chỉ cần thêm nhiều trường vào chỉ mục từ điển! Chẳng hạn, !_[$1][$2]++có thể được sử dụng để sắp xếp theo hai trường đầu tiên. awkMặc dù vậy, -fu của tôi không đủ mạnh để có thể duy nhất trên một loạt các lĩnh vực. :(
Soham Chowdhury

1
Xuất sắc! tùy chọn này tốt hơn câu trả lời vì nó giữ thứ tự các dòng
rkachach 20/03/19

16

Để xem xét nhiều cột.

Sắp xếp và đưa ra danh sách duy nhất dựa trên cột 1 và cột 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : dấu hai chấm là dấu phân cách
  • -k 1,1 -k 3,3 dựa trên cột 1 và cột 3

8

hoặc nếu bạn muốn sử dụng uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

cho:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Tôi muốn chỉ ra một sự đơn giản hóa có thể có: Bạn có thể đổ cat! Thay vì đường ống vào tr, chỉ cần để tr đọc tệp bằng cách sử dụng <. Đường ống thông qua catlà một biến chứng không cần thiết phổ biến được sử dụng bởi người mới. Đối với số lượng lớn dữ liệu, cần phải có hiệu ứng hiệu suất.
Carl Smotricz

4
Tốt để biết. Cám ơn! (Tất nhiên điều này có ý nghĩa, nghĩ về "con mèo" và "sự lười biếng";))
Carsten C.

Việc đảo ngược các trường có thể được đơn giản hóa với rev.
Hielke Walinga

5

Nếu bạn muốn giữ lại một trong những bản sao cuối cùng bạn có thể sử dụng

 tac a.csv | sort -u -t, -r -k1,1 |tac

Đó là yêu cầu của tôi

đây

tac sẽ đảo ngược dòng tập tin theo dòng


1

Đây là một cách rất tiện lợi.

Đầu tiên định dạng nội dung sao cho cột được so sánh về tính duy nhất là chiều rộng cố định. Một cách để làm điều này là sử dụng awk printf với một công cụ xác định độ rộng trường / cột ("% 15s").

Bây giờ, các tùy chọn -f và -w của uniq có thể được sử dụng để bỏ qua các trường / cột trước đó và để chỉ định chiều rộng so sánh (chiều rộng cột).

Dưới đây là ba ví dụ.

Trong ví dụ đầu tiên ...

1) Tạm thời làm cho cột quan tâm có chiều rộng cố định lớn hơn hoặc bằng chiều rộng tối đa của trường.

2) Sử dụng tùy chọn -f uniq để bỏ qua các cột trước đó và sử dụng tùy chọn -w uniq để giới hạn chiều rộng cho tmp_fixed_ference.

3) Xóa các khoảng trắng ở cuối cột để "khôi phục" chiều rộng của nó (giả sử không có khoảng trắng ở trước).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

Trong ví dụ thứ hai ...

Tạo cột uniq mới 1. Sau đó xóa nó sau khi bộ lọc uniq được áp dụng.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Ví dụ thứ ba giống như ví dụ thứ hai, nhưng đối với nhiều cột.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

tốt, đơn giản hơn so với cách ly cột với awk, nếu bạn cần xóa mọi thứ với một giá trị nhất định cho một tệp đã cho, tại sao không làm grep -v:

ví dụ: xóa mọi thứ với giá trị "col2" trong dòng vị trí thứ hai: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Nếu điều này không đủ tốt, bởi vì một số dòng có thể bị tước không đúng cách có thể có giá trị phù hợp hiển thị trong một cột khác, bạn có thể làm một cái gì đó như thế này:

awk để cô lập cột vi phạm: vd

awk -F, '{print $2 "|" $line}'

-F đặt trường được phân định thành ",", $ 2 có nghĩa là cột 2, theo sau là một số dấu phân cách tùy chỉnh và sau đó là toàn bộ dòng. Sau đó, bạn có thể lọc bằng cách xóa các dòng bắt đầu bằng giá trị vi phạm:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

và sau đó loại bỏ các công cụ trước dấu phân cách:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(lưu ý - lệnh sed là cẩu thả vì nó không bao gồm các giá trị thoát. Ngoài ra, mẫu sed thực sự phải là một cái gì đó như "[^ |] +" (tức là bất cứ thứ gì không phải là dấu phân cách). Nhưng hy vọng điều này là đủ rõ ràng.


3
Anh ta không muốn thanh lọc các dòng, anh ta muốn giữ lại một bản sao của một dòng với một chuỗi cụ thể. Uniq là trường hợp sử dụng đúng.
ingyhere

-3

Bằng cách sắp xếp tệp với sortđầu tiên, sau đó bạn có thể áp dụng uniq.

Có vẻ như sắp xếp các tập tin tốt:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Bạn cũng có thể làm một số phép thuật AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Điều này không phải là duy nhất theo cột như được yêu cầu trong câu hỏi. Điều này chỉ là duy nhất cho toàn bộ dòng. Ngoài ra, bạn không phải thực hiện một loại để làm một uniq. Hai loại trừ lẫn nhau.
Javid Jamae

1
Vâng, bạn đúng. Ví dụ cuối cùng làm những gì câu hỏi yêu cầu mặc dù, mặc dù câu trả lời được chấp nhận là sạch sẽ hơn rất nhiều. Về sortsau uniq, sortcần phải được thực hiện trước khi thực hiện uniqnếu không nó không hoạt động (nhưng bạn có thể bỏ qua lệnh thứ hai và chỉ sử dụng sort -u). Từ uniq(1): "Lọc các dòng khớp liền kề từ INPUT (hoặc đầu vào tiêu chuẩn), ghi vào OUTPUT (hoặc đầu ra tiêu chuẩn)."
Mikael S

Ah, bạn đúng về việc sắp xếp trước uniq. Tôi không bao giờ nhận ra rằng uniq chỉ hoạt động trên các dòng liền kề. Tôi đoán tôi luôn luôn sử dụng sort -u.
Javid Jamae
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.