Một nhóm không bắt trong các biểu thức thông thường là gì?


Câu trả lời:


2328

Hãy để tôi cố gắng giải thích điều này với một ví dụ.

Hãy xem xét văn bản sau:

http://stackoverflow.com/
/programming/tagged/regex

Bây giờ, nếu tôi áp dụng regex bên dưới nó ...

(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

... Tôi sẽ nhận được kết quả sau:

Match "http://stackoverflow.com/"
     Group 1: "http"
     Group 2: "stackoverflow.com"
     Group 3: "/"

Match "/programming/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"

Nhưng tôi không quan tâm đến giao thức - tôi chỉ muốn máy chủ và đường dẫn của URL. Vì vậy, tôi thay đổi regex để bao gồm nhóm không bắt giữ (?:).

(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

Bây giờ, kết quả của tôi trông như thế này:

Match "http://stackoverflow.com/"
     Group 1: "stackoverflow.com"
     Group 2: "/"

Match "/programming/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

Xem? Nhóm đầu tiên chưa bị bắt. Trình phân tích cú pháp sử dụng nó để khớp với văn bản, nhưng bỏ qua nó sau, trong kết quả cuối cùng.


BIÊN TẬP:

Theo yêu cầu, hãy để tôi cố gắng giải thích các nhóm quá.

Vâng, các nhóm phục vụ nhiều mục đích. Họ có thể giúp bạn trích xuất thông tin chính xác từ một trận đấu lớn hơn (cũng có thể được đặt tên), họ cho phép bạn sắp xếp lại một nhóm phù hợp trước đó và có thể được sử dụng để thay thế. Chúng ta hãy thử một số ví dụ, phải không?

Hãy tưởng tượng bạn có một số loại XML hoặc HTML (lưu ý rằng regex có thể không phải là công cụ tốt nhất cho công việc , nhưng nó là một ví dụ hay). Bạn muốn phân tích các thẻ, vì vậy bạn có thể làm một cái gì đó như thế này (tôi đã thêm khoảng trắng để dễ hiểu hơn):

   \<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
   \<(.+?)\> [^<]*? \</\1\>

Regex đầu tiên có một nhóm được đặt tên (TAG), trong khi nhóm thứ hai sử dụng một nhóm chung. Cả hai regex đều làm điều tương tự: chúng sử dụng giá trị từ nhóm đầu tiên (tên của thẻ) để khớp với thẻ đóng. Sự khác biệt là cái đầu tiên sử dụng tên để khớp với giá trị và cái thứ hai sử dụng chỉ mục nhóm (bắt đầu từ 1).

Hãy thử một số thay thế ngay bây giờ. Hãy xem xét văn bản sau:

Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.

Bây giờ, hãy sử dụng regex ngu ngốc này trên nó:

\b(\S)(\S)(\S)(\S*)\b

Regex này khớp với các từ có ít nhất 3 ký tự và sử dụng các nhóm để phân tách ba chữ cái đầu tiên. Kết quả là thế này:

Match "Lorem"
     Group 1: "L"
     Group 2: "o"
     Group 3: "r"
     Group 4: "em"
Match "ipsum"
     Group 1: "i"
     Group 2: "p"
     Group 3: "s"
     Group 4: "um"
...

Match "consectetuer"
     Group 1: "c"
     Group 2: "o"
     Group 3: "n"
     Group 4: "sectetuer"
...

Vì vậy, nếu chúng ta áp dụng chuỗi thay thế:

$1_$3$2_$4

... Trên đó, chúng tôi đang cố gắng sử dụng nhóm đầu tiên, thêm một dấu gạch dưới, sử dụng nhóm thứ ba, sau đó là nhóm thứ hai, thêm một dấu gạch dưới khác, và sau đó là nhóm thứ tư. Chuỗi kết quả sẽ giống như chuỗi dưới đây.

L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.

Bạn có thể sử dụng các nhóm được đặt tên để thay thế quá, sử dụng ${name}.

Để chơi xung quanh với regexes, tôi khuyên bạn nên http://regex101.com/ , nơi cung cấp một lượng chi tiết tốt về cách thức hoạt động của regex; nó cũng cung cấp một vài công cụ regex để lựa chọn.


3
@ajsie: Các nhóm truyền thống (chụp) là hữu ích nhất nếu bạn đang thực hiện thao tác thay thế trên kết quả. Đây là một ví dụ trong đó tôi lấy các tên và họ được phân tách bằng dấu phẩy và sau đó đảo ngược thứ tự của chúng (nhờ các nhóm được đặt tên) ... regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
Steve Wortham

2
Không, nó không giống nhau.
Ricardo Nolde

4
Cũng có thể chỉ ra rằng các nhóm không bắt giữ rất hữu ích khi sử dụng regex làm dấu phân cách: "Alice và Bob" -split "\ s + (?: Và | hoặc) \ s +"
Yevgeniy

7
Sẽ rất thú vị khi có sự khác biệt giữa các nhóm không bắt giữ (? :), và các xác nhận lookahead và lookbehind (? =,?!) Đã giải thích. Tôi mới bắt đầu tìm hiểu về các biểu thức thông thường, nhưng từ những gì tôi hiểu, các nhóm không bắt được sử dụng để khớp và "trả lại" những gì chúng khớp, nhưng "giá trị trả về" không được "lưu trữ" để tham chiếu lại. Mặt khác, các xác nhận của Lookahead và lookbehind không chỉ không được "lưu trữ", mà còn không phải là một phần của trận đấu, họ chỉ khẳng định rằng một cái gì đó sẽ khớp, nhưng giá trị "khớp" của chúng bị bỏ qua, nếu tôi không nhầm. (Tôi gần đúng phải không?)
Christian

5
[] là một bộ; [123] khớp với bất kỳ char nào trong tập hợp một lần; [^ 123] khớp với mọi thứ KHÔNG trong bộ một lần; [^ / \ r \ n] + khớp với một hoặc nhiều ký tự khác với /, \ r, \ n.
Ricardo Nolde

180

Bạn có thể sử dụng các nhóm bắt giữ để tổ chức và phân tích một biểu thức. Một nhóm không bắt giữ có lợi ích đầu tiên, nhưng không có chi phí hoạt động thứ hai. Bạn vẫn có thể nói một nhóm không bắt giữ là tùy chọn, ví dụ.

Giả sử bạn muốn khớp văn bản số, nhưng một số số có thể được viết là 1, 2, 3, 4, ... Nếu bạn muốn chụp phần số, nhưng không phải là hậu tố (tùy chọn), bạn có thể sử dụng nhóm không bắt giữ .

([0-9]+)(?:st|nd|rd|th)?

Điều đó sẽ khớp với các số ở dạng 1, 2, 3 ... hoặc ở dạng 1, 2, 3, ... nhưng nó sẽ chỉ bắt được phần số.


3
Súc tích và có lẽ là lời giải thích tốt nhất ở đây
NelsonGon

107

?: được sử dụng khi bạn muốn nhóm một biểu thức, nhưng bạn không muốn lưu nó dưới dạng một phần được khớp / bắt của chuỗi.

Một ví dụ sẽ là một cái gì đó phù hợp với một địa chỉ IP:

/(?:\d{1,3}\.){3}\d{1,3}/

Lưu ý rằng tôi không quan tâm đến việc lưu 3 octet đầu tiên, nhưng việc (?:...)phân nhóm cho phép tôi rút ngắn regex mà không phải chịu chi phí bắt và lưu trữ trận đấu.


38

Nó làm cho nhóm không bị bắt, điều đó có nghĩa là chuỗi con phù hợp với nhóm đó sẽ không được đưa vào danh sách các ảnh chụp. Một ví dụ trong ruby ​​để minh họa sự khác biệt:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]

Tại sao chúng ta không thể sử dụng "abc" .match (/.(.)./). Chụp ở đây?
PRASANNA SARAF

@PRASANNASARAF Tất nhiên bạn có thể. Điểm của mã là cho thấy rằng (?:)không tạo ra một bản chụp, không thể hiện một ví dụ hữu ích về (?:). (?:)rất hữu ích khi bạn muốn nhóm một biểu thức con (giả sử khi bạn muốn áp dụng các bộ lượng hóa cho một biểu thức phụ không nguyên tử hoặc nếu bạn muốn hạn chế phạm vi của a |), nhưng bạn không muốn nắm bắt bất cứ điều gì.
sepp2k

26

ĐỘNG LỰC LỊCH SỬ:

Sự tồn tại của các nhóm không bắt giữ có thể được giải thích bằng cách sử dụng dấu ngoặc đơn.

Xem xét các biểu thức (a|b)ca|bc, do mức độ ưu tiên của phép nối |, các biểu thức này đại diện cho hai ngôn ngữ khác nhau ( {ac, bc}{a, bc}tương ứng).

Tuy nhiên, dấu ngoặc đơn cũng được sử dụng như một nhóm khớp (như được giải thích bằng các câu trả lời khác ...).

Khi bạn muốn có dấu ngoặc đơn nhưng không nắm bắt được biểu thức con, bạn sử dụng NHÓM KHÔNG PHẢI. Trong ví dụ(?:a|b)c


6
Tôi đã tự hỏi tại sao. Theo tôi nghĩ "tại sao" là quan trọng để ghi nhớ thông tin này.
JMI MADISON

22

Hãy để tôi thử điều này với một ví dụ:

Mã Regex: (?:animal)(?:=)(\w+)(,)\1\2

Chuỗi tìm kiếm:

Dòng 1 - animal=cat,dog,cat,tiger,dog

Dòng 2 - animal=cat,cat,dog,dog,tiger

Dòng 3 - animal=dog,dog,cat,cat,tiger

(?:animal) -> Nhóm không bắt giữ 1

(?:=)-> Nhóm không bắt giữ 2

(\w+)-> Nhóm 1 bị bắt

(,)-> Nhóm 2 bị bắt

\1 -> kết quả của nhóm 1 bị bắt tức là Trong Dòng 1 là mèo, Trong Dòng 2 là mèo, Trong Dòng 3 là chó.

\2 -> kết quả của nhóm 2 bị bắt, tức là dấu phẩy (,)

Vì vậy, trong mã này bằng cách cho \1\2 chúng tôi nhớ lại hoặc lặp lại kết quả của nhóm 1 và 2 bị bắt tương ứng sau đó trong mã.

Theo thứ tự mã (?:animal)phải là nhóm 1 và(?:=) nên là nhóm 2 và tiếp tục ..

nhưng bằng cách cho ?:chúng tôi làm cho nhóm đối sánh không bị bắt (không được tính trong nhóm khớp, do đó, số nhóm bắt đầu từ nhóm bị bắt đầu tiên và không bị bắt), do đó việc lặp lại kết quả của nhóm khớp (?:animal)không thể được gọi sau này trong mã.

Hy vọng điều này giải thích việc sử dụng nhóm không bắt giữ.

nhập mô tả hình ảnh ở đây


14

Các nhóm thu thập bạn có thể sử dụng sau này trong regex để khớp HOẶC bạn có thể sử dụng chúng trong phần thay thế của regex. Tạo một nhóm không bắt giữ chỉ đơn giản là miễn cho nhóm đó được sử dụng vì một trong những lý do này.

Các nhóm không bắt giữ là tuyệt vời nếu bạn đang cố gắng nắm bắt nhiều thứ khác nhau và có một số nhóm bạn không muốn chụp.

Đó là khá nhiều lý do họ tồn tại. Trong khi bạn đang tìm hiểu về các nhóm, tìm hiểu về các nhóm nguyên tử , họ làm rất nhiều! Ngoài ra còn có các nhóm tìm kiếm nhưng chúng phức tạp hơn một chút và không được sử dụng nhiều.

Ví dụ về việc sử dụng sau này trong regex (backreference):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [Tìm thẻ xml (không có hỗ trợ ns)]

([A-Z][A-Z0-9]*) là một nhóm bắt giữ (trong trường hợp này là tên thẻ)

Sau này trong regex \1có nghĩa là nó sẽ chỉ khớp với cùng một văn bản trong nhóm đầu tiên ( ([A-Z][A-Z0-9]*)nhóm) (trong trường hợp này là khớp với thẻ kết thúc).


bạn có thể đưa ra một ví dụ đơn giản về cách nó sẽ được sử dụng sau này để khớp với OR không?
never_had_a_name

tôi có nghĩa là bạn có thể sử dụng để khớp sau này hoặc bạn có thể sử dụng nó để thay thế. Câu hoặc trong câu đó chỉ để cho bạn thấy có hai cách sử dụng cho một nhóm bắt giữ
Bob Fincheimer

9

Vâng, tôi là một nhà phát triển JavaScript và sẽ cố gắng giải thích tầm quan trọng của nó liên quan đến JavaScript.

Xem xét một kịch bản mà bạn muốn khớp cat is animal khi bạn muốn ghép mèo và động vật và cả hai nên có một isgiữa chúng.

 // this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]

 // using lookahead pattern it will match only "cat" we can
 // use lookahead but the problem is we can not give anything
 // at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]

 //so I gave another grouping parenthesis for animal
 // in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]

 // we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]

7

Trong các biểu thức chính quy phức tạp, bạn có thể có tình huống phát sinh khi bạn muốn sử dụng một số lượng lớn các nhóm trong số đó để khớp lặp lại và một số trong đó có để cung cấp các tham chiếu trở lại. Theo mặc định, văn bản phù hợp với từng nhóm được tải vào mảng phản hồi. Khi chúng ta có rất nhiều nhóm và chỉ cần có thể tham chiếu một số trong số chúng từ mảng phản hồi, chúng ta có thể ghi đè hành vi mặc định này để nói với biểu thức chính quy rằng các nhóm nhất định chỉ có để xử lý lặp lại và không cần phải lưu giữ và lưu trữ trong mảng backreference.


7

Tôi không thể nhận xét về các câu trả lời hàng đầu để nói điều này: Tôi muốn thêm một điểm rõ ràng chỉ được ngụ ý trong các câu trả lời hàng đầu:

Các tổ chức phi chụp (?...) không không loại bỏ bất kỳ nhân vật trong trận đấu đầy đủ ban đầu, nó chỉ reorganises regex trực quan để các lập trình viên.

Để truy cập một phần cụ thể của biểu thức chính quy mà không xác định các ký tự không liên quan, bạn sẽ luôn cần sử dụng .group(<index>)


2
Bạn đã cung cấp gợi ý quan trọng nhất còn thiếu trong phần còn lại của câu trả lời. Tôi đã thử tất cả các ví dụ trong đó và sử dụng các thám hiểm đơn giản nhất, vì tôi không nhận được kết quả mong muốn. Chỉ có bài viết của bạn cho tôi thấy tôi đã sai ở đâu.
Seshadri R

Vui mừng khi nghe nó!
Scott Anderson

6

tl; dr các nhóm không bắt giữ, như tên cho thấy là các phần của biểu thức chính mà bạn không muốn đưa vào trận đấu và ?:là một cách để xác định một nhóm là không bắt giữ.

Giả sử bạn có một địa chỉ email example@example.com. Regex sau đây sẽ tạo hai nhóm , phần id và phần @ example.com. (\p{Alpha}*[a-z])(@example.com). Để đơn giản, chúng tôi trích xuất toàn bộ tên miền bao gồm cả @ký tự.

Bây giờ hãy nói, bạn chỉ cần phần id của địa chỉ. Những gì bạn muốn làm là lấy nhóm đầu tiên của kết quả trận đấu, được bao quanh ()trong regex và cách để làm điều này là sử dụng cú pháp nhóm không bắt giữ, tức là ?:. Vì vậy, regex (\p{Alpha}*[a-z])(?:@example.com)sẽ chỉ trả lại phần id của email.


5

Một điều thú vị mà tôi bắt gặp là thực tế là bạn có thể có một nhóm bắt giữ trong một nhóm không bắt giữ. Hãy xem regex dưới đây để tìm các url web phù hợp:

var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

Chuỗi url đầu vào:

var url = "http://www.ora.com:80/goodparts?q#fragment";

Nhóm đầu tiên trong regex của tôi (?:([A-Za-z]+):)là nhóm không bắt giữ phù hợp với sơ đồ giao thức và :ký tự dấu hai chấm, http:nhưng khi tôi đang chạy bên dưới mã, tôi đã thấy chỉ số đầu tiên của mảng trả về có chứa chuỗi httpkhi tôi nghĩ rằng httpvà dấu hai chấm :cả hai sẽ không được báo cáo vì họ ở trong một nhóm không bị bắt.

console.debug(parse_url_regex.exec(url));

nhập mô tả hình ảnh ở đây

Tôi nghĩ nếu nhóm đầu tiên (?:([A-Za-z]+):)là một nhóm không bắt giữ thì tại sao nó lại trả về httpchuỗi trong mảng đầu ra.

Vì vậy, nếu bạn nhận thấy rằng có một nhóm lồng nhau ([A-Za-z]+)trong nhóm không bắt giữ. Nhóm lồng nhau đó ([A-Za-z]+)là một nhóm bắt giữ (không có ?:lúc ban đầu) trong chính nó trong một nhóm không bắt giữ (?:([A-Za-z]+):). Đó là lý do tại sao văn bản httpvẫn bị bắt nhưng :ký tự dấu hai chấm nằm trong nhóm không bắt nhưng bên ngoài nhóm chụp không được báo cáo trong mảng đầu ra.


2

Mở devTools Google Chrome của bạn và sau đó là tab Console: và nhập vào đây:

"Peace".match(/(\w)(\w)(\w)/)

Chạy nó và bạn sẽ thấy:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

Công JavaScriptcụ RegExp chụp ba nhóm, các mục có chỉ số 1,2,3. Bây giờ sử dụng dấu không bắt để xem kết quả.

"Peace".match(/(?:\w)(\w)(\w)/)

Kết quả là:

["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]

Đây là rõ ràng những gì là không bắt nhóm.


2

Tôi nghĩ rằng tôi sẽ cung cấp cho bạn câu trả lời. Đừng sử dụng các biến chụp mà không kiểm tra xem trận đấu đã thành công.

Các biến bắt giữ $1, v.v., không hợp lệ trừ khi trận đấu thành công và chúng cũng không bị xóa.

#!/usr/bin/perl  
use warnings;
use strict;   
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
    print "Fred wants a  $1";
}
else
{
    print "Fred dont wants a $1 $2";
}

Trong ví dụ trên, để tránh bắt bronto trong $1, (?:)được sử dụng.

Nếu mẫu được khớp, thì $1được chụp thành mẫu được nhóm tiếp theo.

Vì vậy, đầu ra sẽ như sau:

Fred wants a burger

Sẽ rất hữu ích nếu bạn không muốn các trận đấu được lưu lại.


1

Rất đơn giản, chúng ta có thể hiểu với ví dụ ngày đơn giản, giả sử nếu ngày được đề cập là ngày 1 tháng 1 năm 2019 hoặc ngày 2 tháng 5 năm 2019 hoặc bất kỳ ngày nào khác và chúng tôi chỉ đơn giản muốn chuyển đổi sang định dạng dd / mm / yyyy, chúng tôi sẽ không cần đến tháng tên là tháng một hoặc tháng hai cho vấn đề đó, vì vậy để chụp phần số, nhưng không phải là hậu tố (tùy chọn), bạn có thể sử dụng một nhóm không bắt giữ.

vì vậy biểu thức chính quy sẽ là,

([0-9]+)(?:January|February)?

Nó đơn giản như vậy.

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.