Cách xóa dòng nếu nó chứa một ký tự chính xác một lần


10

Tôi muốn xóa một dòng khỏi một tệp chỉ chứa một ký tự cụ thể, nếu nó xuất hiện nhiều hơn một lần hoặc không có mặt thì hãy giữ dòng đó trong tệp.

Ví dụ:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Ở đây, ký tự mà tôi muốn loại bỏ là Cnhư vậy, lệnh sẽ xóa các dòng FGTHDCJUTDYCvì chúng có Cchính xác một lần.

Làm thế nào tôi có thể làm điều này bằng cách sử dụng một trong hai sedhoặc awk?

Câu trả lời:


20

Trong awkbạn có thể đặt dấu phân cách trường thành bất cứ điều gì. Nếu bạn đặt nó thành C, thì bạn sẽ có nhiều trường +1 như lần xuất hiện C.

Vì vậy, nếu bạn nói awk -F'C' '{print NF}' <<< "C1C2C3"bạn nhận được 4: CCCbao gồm 3 Cgiây và do đó có 4 trường.

Bạn muốn loại bỏ các dòng trong đó Cxảy ra chính xác một lần. Cân nhắc điều này, trong trường hợp của bạn, bạn sẽ muốn loại bỏ những dòng trong đó có chính xác hai Ctrường. Vì vậy, bỏ qua chúng:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD

4
Sử dụng tuyệt vời của awkphân cách trường!
Valentin B.

xen kẽ, như trong trường hợp mặc định (FS = ""), nó bỏ qua khoảng trắng hàng đầu ($ 1 = không phải khoảng trắng đầu tiên trên dòng) và cũng có thể lặp lại (bạn có thể có 5 khoảng trắng để tách trường 1 và trường 2) ... khoảng trắng có lẽ được đối xử đặc biệt? (để xem nó, người ta có thể làm awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'và cung cấp cho nó một số dòng, một số có nhiều lực và những thứ khác bắt đầu bằng không gian)
Olivier Dulac

2
@OlivierDulac, vâng, không gian được xử lý đặc biệt theo quy định của POSIX .
tự đại diện

8

phương pháp sed :

sed -i '/^[^C]*C[^C]*$/d' input

-i tùy chọn cho phép sửa đổi tập tin tại chỗ

/^[^C]*C[^C]*$/- khớp với các dòng Cchỉ chứa một lần

d - xóa các dòng khớp


8

Điều này có thể được thực hiện với sednhư:

Mã số:

sed '/C.*C/p;/C/d' file1

Các kết quả:

DTHGTY
HYTRHD
HTCCYD

Làm sao?

  1. Khớp và in bất kỳ dòng nào có ít nhất hai bản sao Cqua/C.*C/p
  2. Xóa bất kỳ dòng nào Cthông qua /C/d, điều này bao gồm các dòng đã được in ở bước 1
  3. Mặc định in phần còn lại của dòng

2
Phương pháp thay thế thông minh; Tôi thích nó.
tự đại diện

6

Điều này loại bỏ các dòng với chính xác một lần xuất hiện của C.

grep -v '^[^C]*C[^C]*$' file

Biểu thức chính quy [^C]khớp với một ký tự không phải là C (hoặc dòng mới) và toán tử lặp lại (còn gọi là ngôi sao Kleene) *chỉ định không hoặc nhiều lần lặp lại của biểu thức trước.

Đầu ra mặc định từ grep(và hầu hết các công cụ định hướng văn bản khác) là đầu ra tiêu chuẩn; chuyển hướng đến một tệp mới và có thể di chuyển nó lên trên cùng của tệp gốc nếu đó là những gì bạn muốn. Regex tương tự có thể được sử dụng sed -iđể chỉnh sửa tại chỗ:

sed -i '/^[^C]*C[^C]*$/d' file

(Trên một số nền tảng, đáng chú ý là * BSD bao gồm macOS, -itùy chọn yêu cầu một đối số, như -i ''.)


1
sed -i '/^[^C]*C[^C]*$/d' file- nghe có vẻ như đã được đăng trước đó, bạn nghĩ thế nào, đạo văn?
RomanPerekhrest

1
Thật vậy, có một số trùng lặp. Tôi bắt đầu với grepcâu trả lời nhưng rõ ràng nó dễ dàng mở rộng sang sed -ibiến thể. Không thấy câu trả lời của bạn vì tôi đang tìm grepcâu trả lời trước .
tripleee

1
Đó là an toàn hơn để chỉ rõ ràng tránh -ivới sedvà thay vào đó chuyển hướng đến một tập tin mới và thay thế ban đầu với điều đó nếu sedtiện ích đã thoát với không có lỗi.
Kusalananda

2
Hoặcgrep -vx '[^C]*C[^C]*'
Stéphane Chazelas

@Kusalananda Nhưng sau đó bạn cũng có thể sử dụng grepvì nó rõ ràng và mạnh mẽ hơn (đặc biệt, sedcó mã thoát ít thông tin hơn).
tripleee

4

Công cụ POSIX cho các chỉnh sửa theo kịch bản của một tệp (thay vì in các nội dung đã sửa đổi thành tiêu chuẩn) ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Tất nhiên bạn có thể sử dụngsed -i nếu phiên bản Sed của bạn hỗ trợ nó, chỉ cần lưu ý rằng nó không khả dụng nếu bạn đang viết một tập lệnh dự định chạy trên các loại hệ thống khác nhau.


David Foerster hỏi trong các ý kiến:

Có một lý do tại sao bạn đang sử dụng printfvà không echohoặc một cái gì đó như thế ex -c COMMAND?

Trả lời có.

Đối printfvới echonó là một câu hỏi về tính di động; xem Tại sao printf tốt hơn echo? Và nó cũng dễ dàng hơn để xen kẽ các dòng mới giữa các lệnh sử dụng printf.

Đối printf ... | exvới so với ex -c ..., đó là một câu hỏi về xử lý lỗi. Đối với lệnh cụ thể này, nó sẽ không thành vấn đề, nhưng nói chung là không; ví dụ: thử đặt

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

trong một kịch bản. Tương phản với những điều sau đây:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

Đầu tiên sẽ treo và chờ đầu vào; cái thứ hai sẽ thoát khi nhận được EOF bởi exlệnh, vì vậy đoạn script sẽ tiếp tục. Có các cách giải quyết khác, chẳng hạn như s///e, nhưng chúng không được chỉ định bởi POSIX. Tôi thích sử dụng các hình thức di động, được hiển thị ở trên.

Đối với glệnh, phải có một dòng mới ở cuối và tôi thích sử dụng printfđể bọc các lệnh hơn là nhúng một dòng mới trong dấu ngoặc đơn.


1
Có một lý do tại sao bạn đang sử dụng printfvà không echohoặc một cái gì đó như thế ex -c COMMAND?
David Foerster

@DavidFoerster, vâng. Tôi bắt đầu trả lời bạn trong các bình luận nhưng nó đã phát triển lâu, vì vậy tôi đã thêm nó vào câu trả lời.
tự đại diện

Cảm ơn và +1! Tôi đã biết về printfvs. echo(mặc dù tôi thường chỉ thích echokhi đối số được mã hóa cứng) nhưng excho đến nay tôi vẫn chưa sử dụng rộng rãi.
David Foerster

2

Dưới đây là một vài lựa chọn sử dụng perl.

Vì bạn chỉ khớp một ký tự duy nhất, bạn có thể sử dụng tr/C//(bản dịch, không thay thế), để trả về số lượng kết quả của C:

perl -lne 'print if tr/C// != 1' file

Tổng quát hơn, nếu bạn muốn khớp một chuỗi nhiều ký tự hoặc biểu thức chính quy, thì bạn có thể sử dụng điều này:

perl -lne 'print if (@m = /C/g) != 1' file

Điều này gán các kết quả khớp của biểu thức chính quy /C/gcho một danh sách @mvà in các dòng khi độ dài của danh sách đó không 1.

Công -itắc có thể được thêm vào để chỉnh sửa "tại chỗ".


2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

Lưu ý rằng nó giả định GNU sed, t #...thường sẽ phân nhánh đến nhãn được gọi #...trong hầu hết các sedtriển khai khác .
Stéphane Chazelas

Ngay cả !bGNU sed vì chi nhánh không thích bất cứ thứ gì ngoại trừ nhãn hoặc dòng mới sau nó.

Vâng, b, t, :, }(và r file, w file...) có thể không có một lệnh sau khi chúng trên cùng một dòng. Bạn cũng có thể sử dụng các -etùy chọn riêng biệt .
Stéphane Chazelas

Tùy chọn perl của bạn không tạo ra đầu ra chính xác. Tôi đoán bạn đã quên để thêm các gsửa đổi.
Tom Fenech

@TomFenech Bạn đã đúng. Tôi đang sửa nó. Cảm ơn.

1

Đối với bất cứ ai muốn awkcụ thể, tôi cung cấp

awk '/C[^C]*C/{next}//{print}'

bỏ qua dòng nếu nó phù hợp với mẫu, in nó khác. Bạn thực sự không cần {print}, bạn có thể sử dụng //và in mặc định, nhưng tôi nghĩ nó rõ ràng hơn.

Suy nghĩ đầu tiên của tôi là sử dụng egrep -vvới cùng một mẫu, nhưng điều đó không thực sự trả lời câu hỏi như được đặt ra.


1
Điểm phù hợp với bất cứ điều gì sau {next}? Chỉ cần nói awk '/pattern/ {next} 1'và tất cả các dòng không phù hợp với mẫu sẽ được in. Hoặc, tốt hơn, awk '!/pattern/'để trực tiếp in chúng.
fedorqui

@fedorqui quan điểm tốt về !/pattern/(điều này bằng cách nào đó đã đánh trượt tâm trí của tôi) nhưng tôi muốn thấy một sự tự giải thích //{print}hơn là một điều khó hiểu 1. Giả sử khả năng và sự lưu loát ít nhất từ ​​người tiếp theo để duy trì mã của bạn, phù hợp với việc không làm cho nó nghiêm trọng kém hiệu quả hoặc hiệu quả.
nigel222
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.