Chỉ giữ lại các dòng chứa số lượng dấu phân cách chính xác


9

Tôi có một tệp csv khổng lồ với 10 trường được phân tách bằng dấu phẩy. Thật không may, một số dòng không đúng định dạng và không chứa chính xác 10 dấu phẩy (điều này gây ra một số vấn đề khi tôi muốn đọc tệp vào R). Làm cách nào tôi chỉ có thể lọc ra các dòng chứa chính xác 10 dấu phẩy?


1
câu hỏi của bạn và câu hỏi liên kết không giống nhau. bạn hỏi làm thế nào để xử lý các dòng không nhiều hơn hoặc ít hơn một số lượng trận đấu nhất định, trong khi câu hỏi đó chỉ yêu cầu số lượng trận đấu tối thiểu. thực tế là câu hỏi được trả lời dễ dàng hơn - nó không yêu cầu quét một dòng đầy đủ, hoặc (ít nhất, như sedở đây) chỉ bằng một trận đấu nhiều hơn so với tìm kiếm, mặc dù câu hỏi này có. Bạn không nên đóng cái này.
mikeerv

1
Thật ra, nhìn gần hơn, người hỏi ở đó không muốn nhiều hơn hoặc ít hơn các trận đấu. câu hỏi đó cần một tiêu đề mới. nhưng grepcâu trả lời không có câu trả lời chấp nhận được cho một trong hai câu hỏi ...
mikeerv

Câu trả lời:


21

Một POSIX khác:

awk -F , 'NF == 11' <file

Nếu dòng có 10 dấu phẩy thì sẽ có 11 trường trong dòng này. Vì vậy, chúng tôi chỉ đơn giản là awksử dụng ,như là dấu phân cách trường. Nếu số lượng trường là 11, điều kiện NF == 11là đúng, awksau đó thực hiện hành động mặc định print $0.


5
Đó thực sự là điều đầu tiên tôi nghĩ đến câu hỏi này. Tôi nghĩ rằng nó là quá mức cần thiết, nhưng nhìn vào mã ... nó chắc chắn là rõ ràng hơn. Vì lợi ích của người khác: -Fđặt dấu phân cách trường và NFtham chiếu số lượng trường trong một dòng nhất định. Vì không có khối mã nào {statement}được gắn vào điều kiện NF == 11, nên hành động mặc định là in dòng. (@cuonglm, vui lòng kết hợp lời giải thích này nếu bạn thích.)
Wildcard

4
+1: Giải pháp rất thanh lịch và dễ đọc mà cũng rất chung chung. Ví dụ, tôi có thể tìm thấy tất cả các dòng không đúng vớiawk -F , 'NF != 11' <file
Miroslav Sabo

@gardenhead: Thật dễ dàng để có được nó, như bạn thấy OP đã nói trong bình luận của mình. Thỉnh thoảng tôi trả lời từ điện thoại di động của mình, vì vậy thật khó để thêm lời giải thích chi tiết.
cuonglm

1
@mikeerv: Không, xin lỗi nếu tôi làm bạn bối rối, đó chỉ là tiếng Anh của tôi. Bạn không thể có 11 trường với 1-9 dấu phẩy.
cuonglm

1
@OlivierDulac: Nó bảo vệ bạn chống lại tập tin bắt đầu bằng -hoặc được đặt tên -.
cuonglm

8

Sử dụng egrep(hoặc grep -Etrong POSIX):

egrep "^([^,]*,){10}[^,]*$" file.csv

Điều này lọc ra bất cứ thứ gì không chứa 10 dấu phẩy: nó khớp với các dòng đầy đủ ( ^ở đầu và $cuối), chứa chính xác mười lần lặp lại ( {10}) của chuỗi "bất kỳ số lượng ký tự nào ngoại trừ ',', theo sau là một ','" ( ([^,]*,)), tiếp theo là bất kỳ số lượng ký tự nào ngoại trừ ',' ( [^,]*).

Bạn cũng có thể sử dụng -xtham số để thả neo:

grep -xE "([^,]*,){10}[^,]*" file.csv

Điều này ít hiệu quả hơn giải pháp của cuonglmawk ; cái sau thường nhanh hơn sáu lần trên hệ thống của tôi đối với các dòng có khoảng 10 dấu phẩy. Dòng dài hơn sẽ gây ra sự chậm lại lớn.


5

grepMã đơn giản nhất sẽ hoạt động:

grep -xE '([^,]*,){10}[^,]*'

Giải trình:

-xđảm bảo rằng mẫu phải phù hợp với toàn bộ dòng, thay vì chỉ là một phần của nó. Điều này rất quan trọng để bạn không khớp các dòng có hơn 10 dấu phẩy.

-E có nghĩa là "regex mở rộng", giúp thoát khỏi dấu gạch chéo ngược trong regex của bạn.

Dấu ngoặc đơn được sử dụng để nhóm và {10}sau đó có nghĩa là phải có chính xác mười kết quả khớp trong một hàng của mẫu trong các dấu ngoặc.

[^,]là một lớp nhân vật, ví dụ, [c-f]sẽ khớp với bất kỳ ký tự đơn nào là a c, a d, an ehoặc an f[^A-Z]sẽ khớp với bất kỳ ký tự đơn nào KHÔNG phải là chữ cái viết hoa. Vì vậy, [^,]phù hợp với bất kỳ nhân vật duy nhất ngoại trừ một dấu phẩy.

Lớp *sau ký tự có nghĩa là "không hoặc nhiều trong số này."

Vì vậy, phần regex ([^,]*,)có nghĩa là "Bất kỳ ký tự nào ngoại trừ dấu phẩy bất kỳ số lần (bao gồm 0 lần), theo sau là dấu phẩy" và {10}chỉ định 10 trong số này. Sau đó, [^,]*để phù hợp với phần còn lại của các ký tự không dấu phẩy cho đến cuối dòng.


5
sed -ne's/,//11;t' -e's/,/&/10p' <in >out

Đầu tiên, phân nhánh bất kỳ dòng nào có từ 11 dấu phẩy trở lên, sau đó in ra những gì còn lại chỉ phù hợp với 10 dấu phẩy.

Rõ ràng tôi đã trả lời điều này trước đây ... Đây là một vụ đạo văn từ một câu hỏi tìm kiếm chính xác 4 lần xuất hiện của một số mô hình:

Bạn có thể nhắm mục tiêu [num]sự xuất hiện của một mẫu bằng s///lệnh sed ubstlation bằng cách chỉ cần thêm [num]lệnh. Khi bạn tthay thế thành công và không chỉ định :nhãn đích , test sẽ tách ra khỏi tập lệnh. Điều này có nghĩa là tất cả những gì bạn phải làm là kiểm tra s///5hoặc thêm dấu phẩy, sau đó in những gì còn lại.

Hoặc, ít nhất, xử lý các dòng vượt quá mức tối đa của bạn là 4. Rõ ràng bạn cũng có một yêu cầu tối thiểu. May mắn thay, điều đó chỉ đơn giản như sau:

sed -ne 's|,||5;t' -e 's||,|4p'

... chỉ cần thay thế lần xuất hiện thứ 4 của ,một dòng bằng chính nó và giải quyết vấn đề của bạn pvới các s///cờ ubstlation. Bởi vì bất kỳ dòng nào khớp từ ,5 lần trở lên đã được cắt tỉa, các dòng chứa 4 ,kết quả chỉ chứa 4.


1
@cuonglm - đó là những gì tôi đã thực sự, lúc đầu, nhưng mọi người luôn nói với tôi rằng tôi nên viết mã dễ đọc hơn. vì tôi có thể đọc những thứ mà người khác tranh chấp là không thể đọc được nên tôi không chắc nên giữ gì và bỏ cái gì ...? vì vậy tôi đặt dấu phẩy thứ hai.
mikeerv

@cuonglm - bạn có thể chế giễu tôi - nó sẽ không làm tổn thương cảm xúc của tôi. tôi có thể nói đùa nếu bạn đang chế giễu tôi thì thật là buồn cười. Không sao đâu - tôi chỉ không chắc chắn và muốn biết. theo tôi, mọi người nên có thể tự cười mình. Dù sao, tôi vẫn không nhận được nó!
mikeerv

Haha, đúng rồi, đó là một suy nghĩ rất tích cực. Dù sao, thật vui khi trò chuyện với bạn và đôi khi, bạn làm tôi căng thẳng .
cuonglm

Thật thú vị khi trong câu trả lời này , nếu tôi thay thế s/hello/world/2với s//world/2, GNU sed làm việc tốt. Với hai sedtừ gia truyền, /usr/5bin/posix/sednâng cao segfault, /usr/5bin/sedđi vào vòng lặp nguyên bản.
cuonglm

@mikeerv, tham khảo cuộc thảo luận trước đó của chúng tôi về sedawk (trong các bình luận), tôi thích câu trả lời này và nâng cao nó, nhưng chú ý bản dịch của awkcâu trả lời được chấp nhận là: "In dòng với 11 trường" và bản dịch của sedcâu trả lời này là: " Cố gắng xóa dấu phẩy thứ 11, bỏ qua dòng tiếp theo nếu bạn thất bại. Cố gắng thay thế dấu phẩy thứ 10 bằng chính dấu phẩy; dòng in nếu bạn thành công. " Câu awktrả lời đưa ra hướng dẫn cho máy tính giống như cách bạn diễn đạt chúng bằng tiếng Anh. ( awktốt cho dữ liệu dựa trên trường.)
Wildcard

4

Ném một số ngắn python:

#!/usr/bin/env python2
with open('file.csv') as f:
    print '\n'.join(line for line in f if line.count(',') == 10)

Điều này sẽ đọc từng dòng và kiểm tra xem số dấu phẩy trong dòng có bằng 10 không line.count(',') == 10, nếu có thì nó sẽ in dòng đó.


2

Và đây là một cách Perl:

perl -F, -ane 'print if $#F==10'

Các -nnguyên nhân perlđể đọc dòng tệp đầu vào của nó theo dòng và thực thi tập lệnh được cung cấp bởi -etrên mỗi dòng. Các -alượt bật tự động tách: mỗi dòng đầu vào sẽ được phân chia trên giá trị được cho bởi -F(ở đây, dấu phẩy) và được lưu dưới dạng mảng @F.

Các $#F(hay tổng quát hơn $#array), là chỉ số cao nhất của mảng @F. Kể từ mảng bắt đầu từ 0, một dòng với 11 lĩnh vực sẽ có @Fcủa 10. Tập lệnh, do đó, in dòng nếu nó có chính xác 11 trường.


Bạn cũng có thể làm print if @F==11như một mảng trong ngữ cảnh vô hướng trả về số lượng phần tử.
Sobrique

1

Nếu các trường có thể chứa dấu phẩy hoặc dòng mới, mã của bạn cần hiểu csv. Ví dụ (có ba cột):

$ cat filter.csv
a,b,c
d,"e,f",g
1,2,3,4
one,two,"three
...continued"

$ cat filter.csv | python3 -c 'import sys, csv
> csv.writer(sys.stdout).writerows(
> row for row in csv.reader(sys.stdin) if len(row) == 3)
> '
a,b,c
d,"e,f",g
one,two,"three
...continued"

Tôi cho rằng hầu hết các giải pháp cho đến nay sẽ loại bỏ hàng thứ hai và thứ tư.

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.