Làm thế nào để grep cho các nhóm n chữ số, nhưng không quá n?


33

Tôi đang học Linux và tôi có một thách thức mà dường như tôi không thể tự mình giải quyết. Đây là:

grep một dòng từ một tệp chứa 4 số liên tiếp nhưng không quá 4.

Tôi không chắc làm thế nào để tiếp cận điều này. Tôi có thể tìm kiếm các số cụ thể nhưng không phải số lượng của chúng trong một chuỗi.


2
Một dòng như 1234a12345được hiển thị, hay không?
Eliah Kagan

@Buddha bạn cần giải thích câu hỏi của bạn cùng với một ví dụ.
Avinash Raj

nếu các số được đi trước bởi khoảng trắng hoặc bắt đầu của neo dòng và theo sau là khoảng trắng hoặc cuối của neo dòng thì bạn có thể chỉ cần sử dụng ranh giới từ. \b\d{4}\b
Avinash Raj

1
Câu hỏi này khác với một số câu hỏi về biểu thức thông thường bằng cách rõ ràng về việc sử dụng grep . Các câu hỏi về việc sử dụng các tiện ích Unix trong Ubuntu, như grep, sed và awk, luôn được coi là tốt ở đây. Đôi khi mọi người hỏi làm thế nào để làm một công việc với công cụ sai ; sau đó thiếu bối cảnh là một vấn đề lớn, nhưng đó không phải là những gì đang xảy ra ở đây. Đây là chủ đề, đủ rõ ràng để được trả lời hữu ích, hữu ích cho cộng đồng của chúng tôi và không có lợi ích gì trong việc ngăn chặn các câu trả lời tiếp theo hoặc đẩy nó vào việc xóa hoặc di chuyển. Tôi đang bỏ phiếu để mở lại nó.
Eliah Kagan

1
Cảm ơn các bạn rất nhiều, tôi không biết tôi sẽ nhận được nhiều phản hồi này. Đây là câu trả lời tôi đang tìm kiếm: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. Lệnh phải có khả năng kéo một chuỗi như thế này (nó thực hiện): abc1234abcd99999
Đức Phật

Câu trả lời:


52

Có hai cách để giải thích câu hỏi này; Tôi sẽ giải quyết cả hai trường hợp. Bạn có thể muốn hiển thị các dòng:

  1. có chứa một chuỗi gồm bốn chữ số không phải là một phần của bất kỳ chuỗi chữ số nào dài hơn, hoặc
  2. có chứa một chuỗi bốn chữ số nhưng không còn chuỗi chữ số nữa (thậm chí không riêng biệt).

Ví dụ: (1) sẽ hiển thị 1234a56789, nhưng (2) sẽ không.


Nếu bạn muốn hiển thị tất cả các dòng có chứa một chuỗi gồm bốn chữ số không phải là một phần của bất kỳ chuỗi chữ số nào dài hơn, một cách là:

grep -P '(?<!\d)\d{4}(?!\d)' file

Điều này sử dụng các biểu thức chính quy Perl , mà Ubuntu grep( GNU grep ) hỗ trợ thông qua -P. Nó sẽ không khớp với văn bản như thế 12345, cũng không phù hợp với 1234hoặc 2345đó là một phần của nó. Nhưng nó sẽ phù hợp với 1234trong 1234a56789.

Trong biểu thức chính quy Perl:

  • \dcó nghĩa là bất kỳ chữ số nào (đó là một cách ngắn để nói [0-9]hoặc [[:digit:]]).
  • x{4}khớp x4 lần. ( { }cú pháp không dành riêng cho biểu thức chính quy Perl; nó cũng có trong các biểu thức chính quy mở rộng grep -E.) \d{4}Cũng giống như vậy \d\d\d\d.
  • (?<!\d)là một khẳng định tiêu cực về chiều rộng bằng không. Nó có nghĩa là "trừ khi đi trước \d."
  • (?!\d)là một khẳng định tiêu cực về phía trước. Nó có nghĩa là "trừ khi theo sau \d."

(?<!\d)(?!\d)không khớp văn bản ngoài chuỗi bốn chữ số; thay vào đó, chúng sẽ (khi được sử dụng cùng nhau) ngăn không cho một chuỗi gồm bốn chữ số được khớp với nhau nếu nó là một phần của chuỗi chữ số dài hơn.

Chỉ sử dụng cái nhìn phía sau hoặc chỉ nhìn phía trước là không đủ bởi vì thứ tự bốn chữ số ngoài cùng hoặc bên trái vẫn sẽ được khớp.

Một lợi ích của việc sử dụng các xác nhận nhìn phía sau và nhìn về phía trước là mẫu của bạn chỉ khớp với các chuỗi bốn chữ số chứ không phải văn bản xung quanh. Điều này rất hữu ích khi sử dụng tô sáng màu (với --colortùy chọn).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Theo mặc định trong Ubuntu, mỗi người dùng có alias grep='grep --color=auto'trong ~.bashrctệp của họ . Vì vậy, bạn sẽ tự động làm nổi bật màu khi bạn chạy một lệnh đơn giản bắt đầu bằng grep(đây là khi bí danh được mở rộng) và đầu ra tiêu chuẩnmột thiết bị đầu cuối (đây là những gì kiểm tra). Các trận đấu thường được tô sáng bằng một màu đỏ (gần với màu đỏ son ), nhưng tôi đã thể hiện nó bằng chữ in nghiêng. Đây là một ảnh chụp màn hình:--color=auto
Ảnh chụp màn hình hiển thị lệnh grep đó, với 12345abc789d0123e4 làm đầu ra, với 0123 được tô sáng màu đỏ.

Và thậm chí bạn có thể thực hiện grepin chỉ phù hợp với văn bản, chứ không phải toàn bộ dòng, với -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Cách khác, không cần nhìn phía sau và khẳng định trước

Tuy nhiên, nếu bạn:

  1. cần một lệnh cũng sẽ chạy trên các hệ thống grepkhông hỗ trợ -Phoặc không muốn sử dụng biểu thức chính quy Perl
  2. không cần phải khớp bốn chữ số cụ thể - thường là trường hợp nếu mục tiêu của bạn chỉ đơn giản là hiển thị các dòng có chứa kết quả khớp
  3. ổn với một giải pháp ít thanh lịch hơn

... Sau đó, bạn có thể đạt được điều này với một biểu thức chính quy mở rộng thay thế:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Điều này khớp với bốn chữ số và ký tự không phải chữ số - hoặc bắt đầu hoặc kết thúc dòng - bao quanh chúng. Đặc biệt:

  • [0-9]khớp với bất kỳ chữ số nào (như [[:digit:]], hoặc \dtrong biểu thức chính quy Perl) và {4}có nghĩa là "bốn lần". Vì vậy, [0-9]{4}phù hợp với một chuỗi bốn chữ số.
  • [^0-9]phù hợp với các nhân vật không trong phạm vi 0thông qua 9. Nó tương đương với [^[:digit:]](hoặc \D, trong biểu thức chính quy Perl).
  • ^, khi nó không xuất hiện trong [ ]ngoặc, khớp với đầu dòng. Tương tự, $phù hợp với kết thúc của một dòng.
  • |phương tiện hay và dấu ngoặc là dành cho nhóm (như trong đại số). Vì vậy, (^|[^0-9])khớp với đầu dòng hoặc ký tự không có chữ số, trong khi ($|[^0-9])khớp với cuối dòng hoặc ký tự không có chữ số.

Vì vậy, các kết quả khớp chỉ xảy ra trong các dòng chứa một chuỗi gồm bốn chữ số ( [0-9]{4}) đồng thời:

  • ở đầu dòng hoặc đứng trước một chữ số ( (^|[^0-9]))
  • ở cuối dòng hoặc theo sau là một chữ số ( ($|[^0-9])).

Mặt khác, nếu bạn muốn hiển thị tất cả các dòng có chứa một chuỗi gồm bốn chữ số, nhưng không chứa bất kỳ chuỗi nào có hơn bốn chữ số (thậm chí một dòng tách biệt với một chuỗi khác chỉ có bốn chữ số), thì về mặt khái niệm của bạn Mục tiêu là tìm các dòng khớp với một mẫu nhưng không phải mẫu khác.

Do đó, ngay cả khi bạn biết cách thực hiện với một mẫu duy nhất, tôi vẫn khuyên bạn nên sử dụng một cái gì đó như đề xuất thứ hai của matt , greping cho hai mẫu riêng biệt.

Bạn không được hưởng lợi nhiều từ bất kỳ tính năng nâng cao nào của biểu thức chính quy Perl khi thực hiện điều đó, vì vậy bạn có thể không muốn sử dụng chúng. Nhưng để phù hợp với phong cách trên, đây là cách rút ngắn giải pháp của matt sử dụng \d(và niềng răng) thay cho [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Vì nó sử dụng [0-9], cách của matt dễ mang theo hơn - nó sẽ hoạt động trên các hệ thống grepkhông hỗ trợ các biểu thức chính quy Perl. Nếu bạn sử dụng [0-9](hoặc [[:digit:]]) thay vì \d, nhưng tiếp tục sử dụng { }, bạn sẽ có được tính di động của matt một cách chính xác hơn một chút:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Cách khác, với một mẫu duy nhất

Nếu bạn thực sự thích một greplệnh mà

  1. sử dụng một biểu thức chính quy duy nhất (không phải hai greps cách nhau bởi một đường ống , như trên)
  2. để hiển thị các dòng chứa ít nhất một chuỗi gồm bốn chữ số,
  3. nhưng không có chuỗi gồm năm (hoặc nhiều hơn) chữ số,
  4. và bạn không ngại kết hợp toàn bộ dòng, không chỉ các chữ số (bạn có thể không quan tâm đến điều này)

... Sau đó bạn có thể sử dụng:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

Các -xlàm cho lá cờ grepchỉ hiển thị dòng mà toàn bộ các trận đấu dòng (chứ không phải bất kỳ dòng chứa một trận đấu).

Tôi đã sử dụng một biểu thức chính quy Perl bởi vì tôi nghĩ rằng sự ngắn gọn \d\Dtăng đáng kể sự rõ ràng trong trường hợp này. Nhưng nếu bạn cần thứ gì đó di động cho các hệ thống grepkhông hỗ trợ -P, bạn có thể thay thế chúng bằng [0-9][^0-9](hoặc bằng [[:digit:]][^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

Cách thức hoạt động của các biểu thức chính quy này là:

  • Ở giữa, \d{4}hoặc [0-9]{4}khớp với một chuỗi gồm bốn chữ số. Chúng tôi có thể có nhiều hơn một trong số này, nhưng chúng tôi cần phải có ít nhất một.

  • Ở bên trái, (\d{0,4}\D)*hoặc ([0-9]{0,4}[^0-9])*khớp với 0 hoặc nhiều ( *) trường hợp không quá bốn chữ số theo sau là một chữ số. Không có chữ số (nghĩa là không có gì) là một khả năng cho "không quá bốn chữ số." Điều này khớp với (a) chuỗi trống hoặc (b) bất kỳ chuỗi nào kết thúc bằng một chữ số không và không chứa bất kỳ chuỗi nào có hơn bốn chữ số.

    Vì văn bản ngay bên trái của trung tâm \d{4}(hoặc [0-9]{4}) phải trống hoặc kết thúc bằng một chữ số, điều này ngăn không cho trung tâm \d{4}khớp bốn chữ số có một chữ số (thứ năm) khác ở bên trái của chúng.

  • Ở bên phải, (\D\d{0,4})*hoặc ([^0-9][0-9]{0,4})*khớp với 0 hoặc nhiều ( *) phiên bản của một chữ số không có chữ số theo sau không quá bốn chữ số (giống như trước đây, có thể là bốn, ba, hai, một hoặc thậm chí không có gì cả). Điều này khớp với (a) chuỗi trống hoặc (b) bất kỳ chuỗi nào bắt đầu bằng một chữ số không và không chứa bất kỳ chuỗi nào có hơn bốn chữ số.

    Vì văn bản ngay bên phải của trung tâm \d{4}(hoặc [0-9]{4}) phải trống hoặc bắt đầu bằng một chữ số không, điều này ngăn không cho trung tâm \d{4}khớp bốn chữ số có một chữ số (thứ năm) khác ở bên phải chúng.

Điều này đảm bảo một chuỗi gồm bốn chữ số có mặt ở đâu đó và không có chuỗi nào có năm chữ số trở lên xuất hiện ở bất cứ đâu.

Nó không phải là xấu hay sai khi làm theo cách này. Nhưng có lẽ lý do quan trọng nhất để xem xét sự thay thế này là nó làm rõ lợi ích của việc sử dụng (hoặc tương tự) thay vào đó, như được đề xuất ở trên và trong câu trả lời của matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

Theo cách đó, rõ ràng mục tiêu của bạn là chọn các dòng có chứa một thứ chứ không phải một thứ khác. Cộng với cú pháp đơn giản hơn (do đó nhiều người đọc / người bảo trì có thể hiểu nhanh hơn).


9

Điều này sẽ cho bạn thấy 4 số liên tiếp nhưng không nhiều hơn

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Lưu ý ^ có nghĩa là không

Có một vấn đề với điều này mặc dù tôi không chắc cách khắc phục ... nếu số đó là cuối dòng thì nó sẽ không hiển thị.

Phiên bản xấu hơn này tuy nhiên sẽ hoạt động cho trường hợp đó

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

Rất tiếc, không cần phải là egrep - tôi đã chỉnh sửa nó
matt

2
Điều đầu tiên là sai - nó tìm thấy a12345b, bởi vì nó phù hợp 2345b.
Volker Siegel

0

Nếu grepkhông hỗ trợ perl biểu thức chính quy ( -P), hãy sử dụng lệnh shell sau:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

nơi printf '[0-9]%.0s' {1..4}sẽ sản xuất 4 lần [0-9]. Phương pháp này hữu ích, khi bạn có các chữ số dài và bạn không muốn lặp lại mẫu (chỉ cần thay thế4 bằng số chữ số của bạn để tìm).

Sử dụng -wsẽ tìm kiếm toàn bộ từ. Tuy nhiên, nếu bạn quan tâm đến các chuỗi chữ số, chẳng hạn như 1234a, sau đó thêm [^0-9]vào cuối mẫu, ví dụ:

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Sử dụng $()về cơ bản là một sự thay thế lệnh . Kiểm tra bài này để xem làm thế nào printflặp lại mô hình.


0

Bạn có thể thử lệnh bên dưới bằng cách thay thế filebằng tên tệp thực trong hệ thống của bạn:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Bạn cũng có thể kiểm tra hướng dẫn này để biết thêm cách sử dụng lệnh grep.

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.