Grep: Dấu hoa thị (*) không phải lúc nào cũng hoạt động


11

Nếu tôi grep một tài liệu có chứa những điều sau đây:

ThisExampleString

... cho biểu thức This*Stringhoặc *String, không có gì được trả lại. Tuy nhiên, This*trả về dòng trên như mong đợi.

Cho dù biểu thức được đính kèm trong dấu ngoặc kép không làm cho sự khác biệt.

Tôi nghĩ dấu hoa thị chỉ ra bất kỳ số lượng các ký tự chưa biết? Tại sao nó chỉ hoạt động nếu nó ở đầu biểu thức? Nếu đây là hành vi dự định, tôi sẽ sử dụng cái gì thay cho biểu thức This*String*String?


bởi vì đó không phải là cách regex hoạt động ... (cụ thể là : * != any number of unknown characters. đọc tài liệu.)
njzk2

Câu trả lời:


18

Dấu hoa thị trong biểu thức chính quy có nghĩa là "khớp với phần tử trước 0 lần trở lên".

Trong trường hợp cụ thể của bạn với grep 'This*String' file.txt, bạn đang cố gắng nói, "hey, grep, khớp với tôi từ Thi, theo sau là chữ thường shoặc không nhiều lần, theo sau là từ String". Chữ thường skhông được tìm thấy trong Exampleđó, do đó grep bỏ qua ThisExampleString.

Trong trường hợp grep '*String' file.txt, bạn đang nói "grep, khớp với tôi chuỗi trống - không có nghĩa đen - trước từ String". Tất nhiên, đó không phải ThisExampleStringlà cách đọc. (Có những ý nghĩa khả thi khác - bạn có thể thử điều này có và không có -Ecờ - nhưng không có ý nghĩa nào giống như những gì bạn thực sự muốn ở đây.)

Biết điều đó .có nghĩa là "bất kỳ nhân vật nào", chúng ta có thể làm điều này : grep 'This.*String' file.txt. Bây giờ lệnh grep sẽ đọc chính xác: Thistheo sau là bất kỳ ký tự nào (nghĩ về nó như là lựa chọn các ký tự ASCII) lặp lại bất kỳ số lần nào, theo sau là String.


6
Trong Bash (và hầu hết các shell Unix) *là một ký tự đặc biệt và nó nên được trích dẫn hoặc thoát ra ví dụ như thế này: grep 'This*String' file.txthoặc thế này: grep This\*String file.txtđể không ngạc nhiên trước kết quả bất ngờ.
pabouk

2
@pabouk trong shells, *là ký tự đại diện. Trong grep, *là một toán tử biểu thức chính quy. Xem unix.stackexchange.com/q/57957/70524
muru

11
pabouk là đúng, mở rộng tên tệp diễn ra trước khi lệnh được chạy; so sánh strace grep .* file.txt |& head -n 1 strace grep '.*' file.txt |& head -n 1. Ngoài ra thực tế grepcũng hoạt động với bất kỳ ký tự Unicode nào (ví dụ: echo -ne ⇏ | grep ⇏đầu ra )
kos

1
@Serg: bạn có uy tín cao ở đây nên tôi nghĩ rằng bạn sẽ chú ý ngay đến ý tôi. OP đã gắn thẻ bash câu hỏi vì vậy tôi giả sử các lệnh được thảo luận được diễn giải bởi bash. Điều này có nghĩa là đầu tiên bashdiễn giải các ký tự đặc biệt của nó và chỉ sau khi tất cả các mở rộng được thực hiện, nó mới chuyển các tham số cho quá trình sinh ra. ----- Ví dụ lệnh này trong Bash: grep This.\*String file.txtsẽ sinh ra /bin/grepvới các tham số 0 : grep, 1 : This.*String, 2 : file.txt. Lưu ý rằng Bash đã xóa dấu gạch chéo ngược và thoát ban đầu *được truyền theo nghĩa đen.
pabouk

7
Điều buồn cười (và để khắc phục sự cố khá khó chịu :) là các lệnh của bạn grep This.*String file.txtthường sẽ hoạt động vì hầu hết có thể sẽ không có tệp phù hợp với biểu thức ký tự đại diện This.*String. Trong trường hợp như vậy theo mặc định Bash sẽ vượt qua đối số theo nghĩa đen bao gồm *.
pabouk

8

Các *metacharacter trong BRE 1 s, ERE 1 s và PCRE 1 s khớp với 0 hoặc nhiều lần xuất hiện của mẫu được nhóm trước đó (nếu một mẫu được nhóm trước *metacharacter), 0 hoặc nhiều lần xuất hiện của lớp ký tự trước đó (nếu một lớp ký tự là trước *metacharacter) hoặc 0 hoặc nhiều lần xuất hiện của ký tự trước (nếu không phải là một mẫu được nhóm hoặc một lớp ký tự trước *metacharacter);

Điều này có nghĩa là trong This*Stringmẫu, là *metacharacter không có trước mẫu hoặc nhóm nhân vật, *metacharacter khớp với 0 hoặc nhiều lần xuất hiện của ký tự trước (trong trường hợp này là ský tự):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Để khớp 0 hoặc nhiều lần xuất hiện của bất kỳ ký tự nào, bạn muốn khớp 0 hoặc nhiều lần xuất hiện của .metacharacter, khớp với bất kỳ ký tự nào:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

Các *metacharacter trong BREs và ERE luôn "tham lam", tức là nó sẽ phù hợp với trận đấu dài nhất:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Đây có thể không phải là hành vi mong muốn; trong trường hợp không, bạn có thể bật grepcông cụ PCRE của mình (sử dụng -Ptùy chọn) và nối thêm ?metacharacter, khi đặt sau *và các +ký tự đại diện có tác dụng thay đổi sự tham lam của chúng:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: Biểu thức chính quy cơ bản, biểu thức chính quy mở rộng và biểu thức chính quy tương thích Perl


Cảm ơn bạn đã trả lời rất nhiều thông tin. Tuy nhiên, tôi đã chọn một câu trả lời khác vì nó ngắn hơn và dễ hiểu hơn. +1 để cung cấp rất nhiều chi tiết.
Trae

@Trae Chào mừng bạn. Thật tốt, tôi đồng ý rằng có lẽ điều này quá phức tạp và đưa ra quá nhiều giả định cho một người không quá quen thuộc với chủ đề này.
kos

4

Một trong những lời giải thích được tìm thấy ở đây liên kết :

Dấu hoa thị " *" không có nghĩa tương tự trong các biểu thức thông thường như trong ký tự đại diện; nó là một công cụ sửa đổi áp dụng cho ký tự đơn trước hoặc biểu thức, chẳng hạn như [0-9]. Một dấu hoa thị khớp với 0 hoặc nhiều hơn những gì trước nó. Do đó, [A-Z]*khớp với bất kỳ số lượng chữ in hoa nào, kể cả không có chữ cái nào, trong khi [A-Z][A-Z]*khớp với một hoặc nhiều chữ cái viết hoa.


1

*có một đặc biệt có nghĩa là cả hai như là một vỏ globbing ký tự ( "ký tự đại diện") và như là một biểu thức chính quy metacharater . Bạn phải tính đến cả hai, mặc dù nếu bạn trích dẫn biểu thức chính quy của mình thì bạn có thể ngăn vỏ đặc biệt xử lý nó và đảm bảo rằng nó chuyển nó không thay đổi grep. Mặc dù về mặt khái niệm tương tự nhau, những gì *có nghĩa là vỏ hoàn toàn khác với ý nghĩa của nó grep.

Đầu tiên , vỏ được coi *là ký tự đại diện.

Bạn đã nói:

Cho dù biểu thức được đính kèm trong dấu ngoặc kép không làm cho sự khác biệt.

Điều đó phụ thuộc vào những tập tin tồn tại trong bất kỳ thư mục nào bạn có mặt khi bạn chạy lệnh. Đối với các mẫu có chứa dấu phân cách thư mục /, nó có thể phụ thuộc vào tệp nào tồn tại trên toàn bộ hệ thống của bạn. Bạn phải luôn trích dẫn các biểu thức chính quy cho grep- và các trích dẫn đơn thường là tốt nhất-- trừ khi bạn chắc chắn rằng mình ổn với chín loại biến đổi có khả năng gây ngạc nhiên mà trình bao thực hiện trước khi thực hiện greplệnh.

Khi shell gặp một *ký tự không được trích dẫn , nó sẽ có nghĩa là "không hoặc nhiều hơn bất kỳ ký tự nào" và thay thế từ có chứa nó bằng một danh sách tên tệp khớp với mẫu. (Tên tệp bắt đầu bằng .được loại trừ - trừ khi mẫu của bạn bắt đầu bằng . hoặc bạn đã định cấu hình trình bao của mình để bao gồm chúng.) Điều này được gọi là globalbing - và cũng bởi tên mở rộng tên tệpmở rộng tên đường dẫn .

Hiệu ứng grepthường sẽ là tên tệp phù hợp đầu tiên được lấy làm biểu thức chính quy - ngay cả khi người đọc khá rõ ràng rằng nó không có nghĩa là một biểu thức thông thường - trong khi tất cả các tên tệp khác được liệt kê tự động từ bạn global được lấy làm tập tin bên trong để tìm kiếm kết quả khớp. (Bạn không nhìn thấy danh sách - nó được chuyển qua một cách ngẫu nhiên grep.) Bạn hầu như không bao giờ muốn điều này xảy ra.

Lý do điều này đôi khi không phải là một vấn đề - và trong trường hợp cụ thể của bạn, ít nhất là cho đến nay , nó đã không - là điều đó *sẽ bị bỏ lại một mình nếu tất cả những điều sau đây là đúng :

  1. Không tập tin nào có tên trùng khớp. ... Hoặc bạn đã vô hiệu hóa hình cầu trong vỏ của bạn, thường là set -fhoặc tương đương set -o noglob. Nhưng điều này là không phổ biến và bạn có thể sẽ biết bạn đã làm nó.

  2. Bạn đang sử dụng hệ vỏ có hành vi mặc định là để *yên khi không có tên tệp phù hợp. Đây là trường hợp trong Bash, mà bạn có thể đang sử dụng, nhưng không phải trong tất cả các shell kiểu Bourne. (Ví dụ, hành vi mặc định trong Zsh shell phổ biến là dành cho các khối u để (a) mở rộng hoặc (b) tạo ra lỗi.) ... Hoặc bạn đã thay đổi hành vi này của vỏ của mình - cách thực hiện khác nhau trên vỏ.

  3. Bạn chưa khác nói với shell của bạn để cho phép những đống để được thay thế bằng khi không có file phù hợp, cũng không phải để thất bại với một thông báo lỗi trong tình huống này. Trong Bash, điều đó đã được thực hiện bằng cách bật tùy chọnnullglob hoặc failglob shell tương ứng.

Đôi khi bạn có thể dựa vào # 2 và # 3 nhưng hiếm khi bạn có thể dựa vào # 1. Một greplệnh có mẫu không được trích dẫn hiện hoạt động có thể ngừng hoạt động khi bạn có các tệp khác nhau hoặc khi bạn chạy nó từ một nơi khác. Trích dẫn biểu hiện thường xuyên của bạn và vấn đề biến mất.

Sau đó, các greplệnh xử lý *như một lượng hóa.

Các câu trả lời khác - chẳng hạn như câu trả lời của Sergiy Kolodyazhnyybởi kos - cũng giải quyết khía cạnh này của câu hỏi này, theo những cách khác nhau. Vì vậy, tôi khuyến khích những người chưa đọc chúng làm như vậy, trước hoặc sau khi đọc phần còn lại của câu trả lời này.

Giả sử điều *đó làm cho nó thành grep - mà trích dẫn phải đảm bảo - grepsau đó lấy nó để có nghĩa là mục trước nó có thể xảy ra bất kỳ số lần nào , thay vì phải xảy ra chính xác một lần . Nó vẫn có thể xảy ra một lần. Hoặc nó có thể không có mặt ở tất cả. Hoặc nó có thể được lặp đi lặp lại. Văn bản phù hợp với bất kỳ khả năng nào sẽ được khớp.

"Mục" có nghĩa là gì?

  • Một nhân vật duy nhất . Kể từ btrận đấu một chữ b, b*phù hợp với không hay nhiều bs, do đó ab*cphù hợp ac, abc, abbc, abbbcvv

    Tương tự như vậy, kể từ khi .trận đấu bất kỳ ký tự , .*phù hợp với không hoặc nhiều ký tự 1 , do đó a.*ctrận đấu ac, akc, ahjglhdfjkdlgjdfkshlgc, thậm chí acccccchjckhccvv Hoặc

  • Một lớp nhân vật . Kể từ [xy]trận đấu xhay y, [xy]*trận đấu zero ký tự trở lên trong đó mỗi một là một trong hai xhoặc y, do đó p[xy]*qphù hợp pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq,, vv

    Điều này cũng áp dụng đối với tốc ký hình thức của các tầng lớp nhân vật như \w, \W, \s, và \S. Vì \wkhớp với bất kỳ ký tự từ nào, \w*khớp với 0 hoặc nhiều ký tự từ. Hoặc là

  • Một nhóm . Kể từ \(bar\)trận đấu bar, \(bar\)*trận đấu bằng không hoặc nhiều bars, do đó foo\(bar\)*bazphù hợp foobaz, foobarbaz, foobarbarbaz, foobarbarbarbazvv

    Với các tùy chọn -Ehoặc -P, hãy grepcoi biểu thức thông thường của bạn là ERE hoặc PCRE tương ứng, thay vì BRE , và sau đó các nhóm được bao quanh ( )thay vì \( \), sau đó bạn sẽ sử dụng (bar)thay vì \(bar\)foo(bar)bazthay vì foo\(bar\)baz.

man grepở phần cuối có thể giải thích hợp lý về cú pháp BRE và ERE, cũng như liệt kê tất cả các tùy chọn dòng lệnh grepchấp nhận ở đầu. Tôi khuyên bạn nên sử dụng trang thủ công đó dưới dạng tài nguyên và tài liệu GNU Greptrang hướng dẫn / tham khảo này (mà tôi đã liên kết với một số trang trên, ở trên).

Để thử nghiệm và học tập grep, tôi khuyên bạn nên gọi nó bằng một mẫu nhưng không có tên tệp. Sau đó, nó nhận đầu vào từ thiết bị đầu cuối của bạn. Nhập dòng; các dòng được lặp lại cho bạn là những dòng chứa văn bản mẫu của bạn phù hợp. Để thoát, nhấn Ctrl+ Dở đầu một dòng, báo hiệu kết thúc đầu vào. (Hoặc bạn có thể nhấn Ctrl+ Cnhư với hầu hết các chương trình dòng lệnh.) Ví dụ:

grep 'This.*String'

Nếu bạn sử dụng --colorcờ, grepsẽ làm nổi bật các phần cụ thể của các dòng khớp với biểu thức chính quy của bạn, điều này rất hữu ích cho cả việc tìm ra biểu thức chính quy làm gì và tìm kiếm những gì bạn đang tìm kiếm khi bạn làm. Theo mặc định, người dùng Ubuntu có bí danh Bash gây ra grep --color=autođể chạy - đủ cho mục đích này - khi bạn chạy greptừ dòng lệnh, do đó bạn có thể không cần phải vượt qua --colorthủ công.

1 Do đó, .*trong một biểu thức chính quy có nghĩa là những gì *có nghĩa là trong một vỏ toàn cầu. Tuy nhiên, điểm khác biệt là greptự động in các dòng có chứa kết quả khớp của bạn ở bất kỳ đâu trong đó, do đó, thông thường không cần thiết phải có .*ở đầu hoặc cuối của biểu thức thông thường.

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.