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:
- 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
- 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 1234
hoặc 2345
đó là một phần của nó. Nhưng nó sẽ phù hợp với 1234
trong 1234a56789
.
Trong biểu thức chính quy Perl:
\d
có 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 x
4 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)
và (?!\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 --color
tù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 ~.bashrc
tệ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ẩn là mộ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
Và thậm chí bạn có thể thực hiện grep
in 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:
- cần một lệnh cũng sẽ chạy trên các hệ thống
grep
không hỗ trợ -P
hoặc không muốn sử dụng biểu thức chính quy Perl và
- 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 và
- ổ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 \d
trong 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 0
thô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])
) và
- ở 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 , grep
ing 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 grep
khô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 grep
lệnh mà
- sử dụng một biểu thức chính quy duy nhất (không phải hai
grep
s cách nhau bởi một đường ống , như trên)
- để hiển thị các dòng chứa ít nhất một chuỗi gồm bốn chữ số,
- nhưng không có chuỗi gồm năm (hoặc nhiều hơn) chữ số,
- 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 -x
làm cho lá cờ grep
chỉ 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
và \D
tă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 grep
không hỗ trợ -P
, bạn có thể thay thế chúng bằng [0-9]
và [^0-9]
(hoặc bằng [[:digit:]]
và [^[: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).
1234a12345
được hiển thị, hay không?