3 điểm bởi GN⁺ 2024-03-21 | 1 bình luận | Chia sẻ qua WhatsApp

Hiểu cách ký tự "$" hoạt động trong biểu thức chính quy của Python

  • Khi dùng mô-đun re của Python, ^ thường được biết đến là "đầu chuỗi", còn $ là "cuối chuỗi".
  • Tuy nhiên, $ không phải lúc nào cũng chỉ có nghĩa là "cuối chuỗi", và cách hoạt động của nó có thể khác nhau tùy nền tảng.
  • Trong Python, khi chế độ multiline bị tắt, ký tự $ có thể khớp với cuối chuỗi hoặc vị trí ngay trước ký tự xuống dòng ở cuối chuỗi.

Sự khác biệt giữa khớp cuối chuỗi và ký tự xuống dòng

  • Khi chế độ multiline bị tắt, nếu muốn khớp đúng cuối chuỗi trong Python mà không bao gồm ký tự xuống dòng, chỉ dùng $ là không đủ.
  • Có thể dùng \z\Z để khớp với cuối chuỗi.
  • Trong Python, khi dùng re.MULTILINE, $ sẽ khớp với cuối chuỗi và cuối mỗi dòng (ngay trước ký tự xuống dòng).

So sánh hành vi biểu thức chính quy trên nhiều nền tảng

  • Bảng so sánh việc mẫu có khớp với cat\n hay không trên nhiều nền tảng cho thấy rằng nếu chấp nhận khớp cả ký tự xuống dòng, thì dùng $ trong chế độ multiline sẽ cho hành vi nhất quán.
  • Nếu muốn khớp mà không bao gồm ký tự xuống dòng, thì trên mọi nền tảng trừ Python và ECMAScript nên dùng \z, còn với Python và ECMAScript thì lần lượt dùng \Z hoặc $ khi không bật chế độ multiline.

Ý kiến của GN⁺

  • Bài viết này có thể giúp các lập trình viên dùng biểu thức chính quy cảnh giác hơn về hành vi không như mong đợi của ký tự $ trong Python.
  • Biểu thức chính quy rất mạnh trong xử lý chuỗi, nhưng cần chú ý vì hành vi có thể khác nhau giữa các nền tảng.
  • Các lập trình viên cần nhận thức rõ những khác biệt này và thực hiện thêm kiểm thử để tránh vấn đề tương thích khi phát triển ứng dụng đa nền tảng.
  • Những thư viện biểu thức chính quy khác cung cấp chức năng tương tự gồm java.util.regex của Java và System.Text.RegularExpressions của .NET; với các thư viện này cũng cần hiểu và sử dụng đúng theo khác biệt hành vi của từng nền tảng.
  • Khi đưa vào cú pháp hoặc hành vi biểu thức chính quy mới, cần cân nhắc khả năng tương thích với mã hiện có, tác động đến hiệu năng và độ dốc học tập trong nhóm, đồng thời đánh giá kỹ lợi ích và chi phí mà thay đổi đó mang lại.

1 bình luận

 
GN⁺ 2024-03-21
Ý kiến Hacker News
  • Những người quen với biểu thức chính quy đều biết rằng ^ có nghĩa là "bắt đầu chuỗi" và $ có nghĩa là "kết thúc chuỗi". Nhưng cá nhân tôi lại nghĩ về chúng như "bắt đầu dòng" và "kết thúc dòng". Trong hầu hết trường hợp, vì văn bản được xử lý từng dòng một nên kết quả là như nhau, nhưng góc nhìn đó khi nghĩ về các toán tử này thì không thay đổi. Có lẽ là vì tôi lần đầu tiếp xúc với biểu thức chính quy qua grep và chủ yếu nghĩ đầu vào theo "dòng".

    • Biểu thức chính quy POSIX và biểu thức chính quy Python là khác nhau. Nói chung cần tham khảo tài liệu regex của trình triển khai mà bạn đang dùng, vì cú pháp không mang tính phổ quát.
    • Theo Chương 9 của POSIX, biểu thức chính quy nhìn chung gắn với xử lý văn bản và hoạt động trên các chuỗi kết thúc bằng NUL, dấu hiệu cho phần cuối chuỗi. Một số tiện ích giới hạn xử lý theo từng dòng. $ có thể tương ứng với cuối chuỗi hoặc cuối dòng, và điều đó được định nghĩa tùy theo tiện ích (hoặc chế độ). Hầu hết các tiện ích phổ biến (grep, sed, awk, Python, v.v.) mặc định xử lý nó là cuối dòng.
    • Không có một cú pháp biểu thức chính quy phổ quát duy nhất. Nếu không biết ngôn ngữ và tùy chọn đang dùng, bạn không thể đọc hoặc viết regex một cách đáng tin cậy.
  • Đây là cơ hội hoàn hảo để giới thiệu Robert Elder. Anh ấy làm nội dung trên YouTube và blog, có một loạt bài về biểu thức chính quy và đào rất sâu vào sự khác biệt hành vi giữa các công cụ.

    • Nội dung mới nhất của anh ấy cũng rất hay: https://www.youtube.com/watch?v=ys7yUyyQA-Y
    • Anh ấy có nhiều nội dung mà người dùng HN có thể quan tâm, chẳng hạn như thực tế và những khó khăn của nghề tư vấn.
  • Biểu thức chính quy là một trong những thứ đầu tiên tôi thật sự thấm được khi mới học Perl. (Perl đến giờ vẫn giữ một vị trí ấm áp trong lòng tôi nhờ cuốn sách "Camel")

    • Điều quan trọng nhất ngày nay là biết rằng các trình triển khai khác nhau, và nên tạo thói quen mở tài liệu tham khảo cho đúng thứ mình đang làm việc.
    • Ví dụ, regex của Emacs dùng "\s_-" (hoặc một thứ gì đó trên màn hình mà không có tài liệu tham khảo) làm lớp ký tự thay vì "\w", nhưng Emacs lại có tài liệu và khả năng khám phá rất tốt.
    • Một số tiện ích yêu cầu escape dấu ngoặc, một số thì không. Đôi khi hành vi này có thể cấu hình được, đôi khi thì không.
    • Tôi đã đi qua đủ các giai đoạn bối rối, bực bội và phủ nhận, giờ thì просто chấp nhận. Khái niệm ở đâu cũng giống nhau, nhưng "hương vị" thì thay đổi.
  • Tôi có thể tưởng tượng các quản lý tuyển dụng tệ hại sẽ thêm câu hỏi 'Trong regex, làm sao để khớp với phần cuối chuỗi?' vào danh sách những câu kiểu 'Ha! Hóa ra ngươi không biết mẹo này!'.

  • Thật kỳ lạ khi nhắc đến regex mà lại bỏ Perl ra khỏi danh sách.

    • Mô tả về $ trong tài liệu perlre: khớp với phần cuối chuỗi (hoặc trước ký tự xuống dòng ở cuối chuỗi; hoặc trước mọi ký tự xuống dòng nếu dùng /m)
  • Raku (trước đây là Perl 6) chọn ^ và $ để biểu thị đầu và cuối chuỗi, đồng thời đưa vào ^^ và $$ để biểu thị đầu và cuối dòng. Chế độ nhiều dòng không tồn tại hoặc không cần thiết.

    • Một trong những lợi ích của việc suy nghĩ lại/viết lại hoàn toàn là có thể rút kinh nghiệm từ thực tế rằng hành vi trước đây đã làm mọi người ngạc nhiên.
  • Có ai thực sự nghĩ rằng regex đã được chuẩn hóa sao? Chuyển sang một ngữ cảnh mới luôn là một quá trình học lại.

  • Có sự nhầm lẫn giữa chuỗi và dòng. Chuỗi là một dãy ký tự, còn dòng có thể là hai thứ khác nhau. Nếu coi ký tự xuống dòng là ký tự kết thúc dòng, thì dòng là một dãy ký tự không-phải-xuống-dòng có kèm ký tự xuống dòng. Nếu không có ký tự xuống dòng thì chưa phải là một dòng hoàn chỉnh. Đây là cách POSIX dùng. Nếu coi ký tự xuống dòng là dấu phân cách dòng, thì dòng là một dãy ký tự không-phải-xuống-dòng. Trong cả hai trường hợp, nội dung của dòng đều kết thúc trước ký tự xuống dòng, vì hoặc nó kết thúc dòng hoặc nó tách dòng đó với dòng tiếp theo.

    • Ý nghĩa của ^ và $ dựa trên dòng — bất kể là chế độ một dòng hay nhiều dòng. Với ý nghĩa dựa trên chuỗi — khi làm việc với tệp, bạn thậm chí có thể coi đó là toàn bộ tệp — thì dùng \A và \Z hoặc các ký hiệu tương đương.
  • Điều này đã dẫn đến một vài lỗi nghiêm trọng trong các ứng dụng dựa trên Ruby. Tôi luôn dùng \A\z.