Các nhóm chụp lồng nhau được đánh số như thế nào trong biểu thức chính quy?


84

Có một hành vi được xác định cho cách các biểu thức chính quy sẽ xử lý hành vi bắt của các dấu ngoặc đơn lồng nhau không? Cụ thể hơn, bạn có thể mong đợi một cách hợp lý rằng các công cụ khác nhau sẽ nắm bắt các dấu ngoặc đơn bên ngoài ở vị trí đầu tiên và các dấu ngoặc đơn lồng nhau ở các vị trí tiếp theo không?

Hãy xem xét mã PHP sau (sử dụng biểu thức chính quy PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

Toàn bộ biểu thức trong ngoặc đơn được ghi lại đầu tiên (tôi muốn kiểm tra), và sau đó các mẫu có dấu ngoặc đơn bên trong được ghi lại tiếp theo ("muốn" và "đến"). Điều này có ý nghĩa hợp lý, nhưng tôi có thể thấy một trường hợp hợp lý tương tự được thực hiện khi đầu tiên chụp các dấu ngoặc phụ và SAU đó chụp toàn bộ mẫu.

Vì vậy, đây là hành vi được xác định "nắm bắt toàn bộ điều đầu tiên" trong công cụ biểu thức chính quy hay nó sẽ phụ thuộc vào ngữ cảnh của mẫu và / hoặc hành vi của công cụ (PCRE khác với C # khác với Java là khác hơn vv)?


Nếu bạn thực sự quan tâm đến tất cả các hương vị regex, thì thẻ "ngôn ngữ bất khả tri" là những gì bạn muốn. Có quá nhiều hương vị để liệt kê tất cả chúng và hầu hết chúng không tuân theo bất kỳ tiêu chuẩn thực sự nào (mặc dù chúng rất nhất quán khi nói đến việc đánh số nhóm chụp).
Alan Moore

Nhóm có thể được truy cập bằng cách sử dụng $ 1, $ 2, $ 3 ... vv. Làm thế nào để truy cập nhóm thứ 10? Nó sẽ là $ 10? Tôi không nghĩ $ 10 sẽ hoạt động vì nó sẽ được hiểu là $ 1 theo sau là 0. Điều này có nghĩa là chúng ta chỉ có thể có tối đa 9 nhóm? Nếu tác giả có thể, vui lòng bao gồm điều này như một phần của câu hỏi thì đây sẽ là nơi duy nhất để biết tất cả về các nhóm lồng nhau trong biểu thức chính quy.
LionHeart

Câu trả lời:


59

Từ perlrequick

Nếu các nhóm trong một regex được lồng vào nhau, $ 1 nhận được nhóm có dấu ngoặc mở ngoài cùng bên trái, $ 2 là dấu ngoặc mở tiếp theo, v.v.

Lưu ý : Không bao gồm dấu ngoặc đơn mở nhóm không chụp (? =)

Cập nhật

Tôi không sử dụng PCRE nhiều, vì tôi thường sử dụng đồ thật;), nhưng tài liệu của PCRE hiển thị giống như tài liệu của Perl:

CÁC MẪU

2.Nó thiết lập subpattern như một subpattern thu giữ. Điều này có nghĩa là, khi toàn bộ mẫu khớp, phần đó của chuỗi chủ đề khớp với bài viết con sẽ được chuyển trở lại người gọi thông qua ovectorđối số của pcre_exec(). Các dấu ngoặc đơn mở được đếm từ trái sang phải (bắt đầu từ 1) để lấy số thứ tự cho các vật liệu con thu được.

Ví dụ: nếu chuỗi "vua màu đỏ" được so khớp với mẫu

the ((red|white) (king|queen))

các chuỗi con được bắt là "red king", "red" và "king", và được đánh số lần lượt là 1, 2 và 3.

Nếu PCRE không tương thích với Perl regex, có lẽ nên định nghĩa lại từ viết tắt - "Biểu thức chính quy Perl Cognate", "Biểu thức chính quy có thể so sánh Perl" hoặc một cái gì đó. Hoặc chỉ phân chia các chữ cái có nghĩa.


1
@Sinan: anh ấy đang sử dụng PCRE trong PHP, là "Biểu thức chính quy tương thích Perl"; vì vậy nó sẽ hoàn toàn giống như sử dụng Perl trực tiếp
Pascal MARTIN

3
Pascal, PCRE bắt đầu như một nỗ lực để trở thành một tập hợp Biểu thức chính quy Tương thích Perl, nhưng trong những năm gần đây cả hai đã khác nhau một chút. Vẫn rất giống nhau, nhưng có sự khác biệt nhỏ trong bộ tính năng nâng cao teh. (Ngoài ra, theo câu hỏi, tôi quan tâm đến tất cả các nền tảng)
Alan Storm

1
Trên thực tế, Perl đang làm hầu hết "trôi đi" trong những ngày này, nhưng bạn nói đúng: "Perl-tương thích" đang nhanh chóng chuyển từ một từ nhầm lẫn thành không theo trình tự. : D
Alan Moore

1
@Alan, Perl chắc chắn đang di chuyển. P5.10 đã thay đổi một vài thứ, nhưng 6 sẽ khác rất nhiều. P gần như chắc chắn sẽ cần được hiểu là "Perl 5". PCRE là một dự án tuyệt vời, tôi không thể khen ngợi đủ, nó là một món quà trời cho hơn một vài dự án.
daotoad 24-08-09

1
Tôi đã thêm điều này vào phần trích dẫn đầu tiên. Lưu ý : Không bao gồm dấu ngoặc đơn mở nhóm không chụp (? =). Tôi đã không nhận ra rằng tôi đã không đăng nhập khi tôi chỉnh sửa nó. Chỉ khi tôi thêm nhận xét này, tôi mới được nhắc nhập thông tin đăng nhập. Vì vậy, bây giờ nó cần thêm 1 người để phê duyệt!
JGFMK

17

Vâng, tất cả điều này được xác định khá rõ ràng cho tất cả các ngôn ngữ mà bạn quan tâm:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Chụp các nhóm được đánh số bằng cách đếm các dấu ngoặc đơn mở từ trái sang phải. ... Nhóm số không luôn là đại diện cho toàn bộ biểu thức. "
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Các ảnh chụp sử dụng () được đánh số tự động dựa trên thứ tự của dấu ngoặc đơn mở, bắt đầu từ dấu đầu tiên capture, nắm bắt số phần tử bằng không, là văn bản được khớp với toàn bộ mẫu biểu thức chính quy. ")
  • PHP (các hàm PCRE) - http://www.php.net/manual/en/ Chức năng.preg-replace.php# Chức năng.preg-replace.parameters
    "\ 0 hoặc $ 0 đề cập đến văn bản khớp với toàn bộ mẫu. Các dấu ngoặc đơn mở được đếm từ trái sang phải (bắt đầu từ 1) để thu được số lượng của vật liệu con thu được. " (Nó cũng đúng với các hàm POSIX không được dùng nữa)
  • PCRE - http://www.pcre.org/pcre.txt
    Để thêm vào những gì Alan M đã nói, hãy tìm kiếm "Cách pcre_exec () trả về chuỗi con bị bắt" và đọc đoạn thứ năm sau:

    Cặp số nguyên đầu tiên, ovector [0] và ovector [1], xác định
    một phần của chuỗi chủ đề được khớp với toàn bộ mẫu. Tiếp theo
    cặp được sử dụng cho bản chụp con đầu tiên, v.v. Giá trị
    trả về bởi pcre_exec () là một nhiều hơn cặp được đánh số cao nhất
    đã được chuẩn bị. Ví dụ: nếu hai chuỗi con đã được bắt,
    giá trị trả về là 3. Nếu không có bản sao con nào bắt được, giá trị trả về
    giá trị từ một trận đấu thành công là 1, cho biết rằng chỉ cặp đầu tiên
    hiệu số đã được thiết lập.
    
  • Perl's khác - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2, v.v. khớp các nhóm chụp như bạn mong đợi (tức là bằng cách xuất hiện dấu ngoặc mở), tuy nhiên $ 0 trả về tên chương trình, không toàn bộ chuỗi truy vấn - để có được điều đó, bạn sử dụng $ & thay thế.

Nhiều khả năng bạn sẽ tìm thấy các kết quả tương tự cho các ngôn ngữ khác (Python, Ruby và các ngôn ngữ khác).

Bạn nói rằng việc liệt kê các nhóm nắm bắt bên trong trước tiên là hợp lý như nhau và bạn đã đúng - vấn đề chỉ là lập chỉ mục khi đóng, thay vì mở, parens. (nếu tôi hiểu bạn một cách chính xác). Tuy nhiên, việc làm này kém tự nhiên hơn (ví dụ như nó không tuân theo quy ước hướng đọc) và do đó làm cho nó khó hơn (có thể không đáng kể) để xác định, bằng cách xem xét kỹ, nhóm thu thập nào sẽ ở một chỉ số kết quả nhất định.

Đặt toàn bộ chuỗi đối sánh ở vị trí 0 cũng có ý nghĩa - chủ yếu là để nhất quán. Nó cho phép toàn bộ chuỗi đã khớp vẫn ở cùng một chỉ mục bất kể số lượng nhóm thu thập từ regex đến regex và bất kể số lượng nhóm thu thập thực sự khớp với bất kỳ thứ gì (ví dụ: Java sẽ thu gọn độ dài của mảng nhóm đã khớp cho mỗi lần thu thập nhóm không khớp với bất kỳ nội dung nào (ví dụ như "một mẫu (. *)"). Bạn luôn có thể kiểm tra capture_group_results [capture_group_results_length - 2], nhưng điều đó không dịch tốt sang các ngôn ngữ sang Perl. Tính năng này sẽ tự động tạo các biến ($ 1 , $ 2, v.v.) (Tất nhiên, Perl là một ví dụ tồi, vì nó sử dụng $ & cho biểu thức đã so khớp, nhưng bạn hiểu rõ :).


1
Câu trả lời hay đấy .. Nhưng còn cập nhật cho Python (2 & 3) nữa thì sao :-)
JGFMK

Còn JavaScript thì sao!?!
mesqueeb

9

Mỗi hương vị regex tôi biết đánh số nhóm theo thứ tự xuất hiện của dấu ngoặc đơn mở đầu. Các nhóm bên ngoài được đánh số trước các nhóm con chứa của chúng chỉ là một kết quả tự nhiên, không phải là chính sách rõ ràng.

Nơi mà nó trở nên thú vị là với các nhóm được đặt tên . Trong hầu hết các trường hợp, chúng tuân theo cùng một chính sách đánh số theo vị trí tương đối của các parens - tên chỉ đơn thuần là một bí danh của số. Tuy nhiên, trong .NET regexes các nhóm được đặt tên được đánh số riêng biệt với các nhóm được đánh số. Ví dụ:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

Trên thực tế, số là một bí danh cho tên ; số được gán cho các nhóm được đặt tên bắt đầu từ khi các nhóm được đánh số "thực" rời đi. Đó có vẻ như là một chính sách kỳ lạ, nhưng có một lý do chính đáng cho nó: trong .NET regexes, bạn có thể sử dụng cùng một tên nhóm nhiều lần trong một regex. Điều đó làm cho các regex có thể có giống như từ chuỗi này để so khớp các số dấu phẩy động từ các ngôn ngữ khác nhau:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

Nếu có dấu phân cách hàng nghìn, nó sẽ được lưu trong nhóm "nghìn" bất kể phần nào của regex khớp với nó. Tương tự, dấu phân tách thập phân (nếu có) sẽ luôn được lưu trong nhóm "thập phân". Tất nhiên, có những cách để xác định và trích xuất các dấu phân cách mà không cần sử dụng lại các nhóm được đặt tên, nhưng cách này thuận tiện hơn nhiều, tôi nghĩ nó không chỉ là biện minh cho sơ đồ đánh số kỳ lạ.

Và sau đó là Perl 5.10+, cho phép chúng tôi kiểm soát nhiều hơn việc nắm bắt các nhóm mà tôi không biết phải làm gì. : D


4

Thứ tự chụp theo thứ tự dấu ngoặc bên trái là tiêu chuẩn trên tất cả các nền tảng mà tôi đã làm việc. (Perl, php, ruby, egrep)


"nắm bắt theo thứ tự của dấu ngoặc đơn bên trái" Cảm ơn vì điều đó, đó là một cách ngắn gọn hơn nhiều để mô tả hành vi.
Alan Storm

1
Bạn có thể đánh số lại các lần chụp ở Perl 5,10 và Perl 6.
Brad Gilbert
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.