Đối sánh Regex trong câu lệnh Bash if


86

Tôi đã làm gì sai ở đây?

Cố gắng so khớp bất kỳ chuỗi nào có chứa khoảng trắng, chữ thường, chữ hoa hoặc số. Các ký tự đặc biệt cũng sẽ tốt, nhưng tôi nghĩ rằng điều đó yêu cầu thoát các ký tự nhất định.

TEST="THIS is a TEST title with some numbers 12345 and special char *&^%$#"

if [[ "$TEST" =~ [^a-zA-Z0-9\ ] ]]; then BLAH; fi

Điều này rõ ràng chỉ kiểm tra cho chữ hoa, chữ thường, số và khoảng trắng. Không hoạt động mặc dù.

* CẬP NHẬT *

Tôi đoán tôi nên cụ thể hơn. Đây là dòng mã thực tế.

if [[ "$TITLE" =~ [^a-zA-Z0-9\ ] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; fi

* CẬP NHẬT *

./anm.sh: line 265: syntax error in conditional expression
./anm.sh: line 265: syntax error near `&*#]'
./anm.sh: line 265: `  if [[ ! "$TITLE" =~ [a-zA-Z0-9 $%^\&*#] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; return; fi'

Bạn thực sự đang sử dụng shell nào? / bin / sh? / bin / bash? / bin / csh?
Willem Van Onsem

8
Sẽ an toàn hơn nếu đặt regex vào một biến. re='...whatever...'; [[ $string =~ $re ]](không có dấu ngoặc kép - đây là một trong những trường hợp hiếm hoi mà họ sẽ phá vỡ thứ gì đó sẽ hoạt động nếu không có chúng).
Charles Duffy

3
Thay vào đó, hãy đặt những dấu ngoặc kép xung quanh bài tập. Dấu ngoặc kép sẽ không bảo vệ các ký tự đặc biệt đúng cách.
ba

Nhiều thx Charles! Vẫn ok khi không đặt nó vào một biến, nhưng nó KHÔNG được trích dẫn gì cả! Ví dụ: [[ $var =~ .* ]]cho đối sánh regex .*(bất kỳ thứ gì). Tôi đoán rằng nếu bạn sử dụng dấu ngoặc kép, dấu ngoặc kép tự được coi là một phần của regex ...
Stéphane

4
tóm tắt gotcha Tôi đã tìm thấy: (1.) lưu mẫu trong một biến bằng cách sử dụng dấu ngoặc kép pattern='^hello[0-9]*$'(2.) trong biểu thức hình vuông kép nếu bạn cần đối sánh regex KHÔNG trích dẫn mẫu vì trích dẫn CHIA SẺ đối sánh mẫu regex. (nghĩa là biểu thức [[ "$x" =~ $pattern ]]sẽ đối sánh bằng cách sử dụng regex và biểu thức [[ "$x" =~ "$pattern" ]]vô hiệu hóa đối sánh regex và tương đương với[[ "$x" == "$pattern" ]] ).
Trevor Boyd Smith

Câu trả lời:


177

Có một số điều quan trọng cần biết về [[ ]]cấu trúc của bash . Đầu tiên:

Việc tách từ và mở rộng tên đường dẫn không được thực hiện trên các từ giữa [[]]; mở rộng dấu ngã, mở rộng tham số và biến, mở rộng số học, thay thế lệnh, thay thế quy trình và xóa dấu ngoặc kép được thực hiện.

Điều thứ hai:

Một toán tử nhị phân bổ sung, '= ~', có sẵn, ... chuỗi ở bên phải của toán tử được coi là một biểu thức chính quy mở rộng và được so khớp tương ứng ... Bất kỳ phần nào của mẫu có thể được trích dẫn để buộc nó phải khớp như một chuỗi .

Do đó, $vở một trong hai bên của =~sẽ được mở rộng thành giá trị của biến đó, nhưng kết quả sẽ không được mở rộng từ hoặc tên đường dẫn. Nói cách khác, hoàn toàn an toàn nếu để các mở rộng biến không được trích dẫn ở phía bên trái, nhưng bạn cần biết rằng các mở rộng có thể thay đổi sẽ xảy ra ở phía bên phải.

Vì vậy, nếu bạn viết: [[ $x =~ [$0-9a-zA-Z] ]], các $0bên trong regex ở bên phải sẽ được mở rộng trước khi regex được giải thích, mà có lẽ sẽ làm cho regex để thất bại trong việc biên dịch (trừ khi việc mở rộng $0đầu với một biểu tượng chữ số hoặc dấu chấm câu có giá trị ascii là ít hơn một chữ số). Nếu bạn trích dẫn phía bên phải tương tự như vậy [[ $x =~ "[$0-9a-zA-Z]" ]], thì phía bên phải sẽ được coi là một chuỗi thông thường, không phải là một regex (và $0vẫn sẽ được mở rộng). Điều bạn thực sự muốn trong trường hợp này là[[ $x =~ [\$0-9a-zA-Z] ]]

Tương tự, biểu thức giữa [[]]được tách thành các từ trước khi regex được diễn giải. Vì vậy, khoảng trắng trong regex cần phải được thoát hoặc được trích dẫn. Nếu bạn muốn để phù hợp với chữ cái, chữ số hoặc các không gian bạn có thể sử dụng: [[ $x =~ [0-9a-zA-Z\ ] ]]. Các ký tự khác tương tự cần phải được thoát ra, chẳng hạn như #, sẽ bắt đầu nhận xét nếu không được trích dẫn. Tất nhiên, bạn có thể đặt mẫu vào một biến:

pat="[0-9a-zA-Z ]"
if [[ $x =~ $pat ]]; then ...

Đối với các regex chứa nhiều ký tự cần phải được thoát hoặc trích dẫn để chuyển qua bash's lexer, nhiều người thích kiểu này hơn. Nhưng hãy cẩn thận: Trong trường hợp này, bạn không thể trích dẫn mở rộng biến:

# This doesn't work:
if [[ $x =~ "$pat" ]]; then ...

Cuối cùng, tôi nghĩ những gì bạn đang cố gắng làm là xác minh rằng biến chỉ chứa các ký tự hợp lệ. Cách dễ nhất để thực hiện kiểm tra này là đảm bảo rằng nó không chứa ký tự không hợp lệ. Nói cách khác, một biểu thức như thế này:

valid='0-9a-zA-Z $%&#' # add almost whatever else you want to allow to the list
if [[ ! $x =~ [^$valid] ]]; then ...

!phủ định phép kiểm tra, biến nó thành một toán tử "không khớp" và một [^...]lớp ký tự regex có nghĩa là "bất kỳ ký tự nào khác ngoài ...".

Sự kết hợp giữa mở rộng tham số và toán tử regex có thể làm cho cú pháp biểu thức chính quy bash "gần như có thể đọc được", nhưng vẫn có một số lỗi sai. (Không phải lúc nào cũng có?) Một là bạn không thể đưa ]vào $valid, ngay cả khi $validđã được trích dẫn, ngoại trừ ngay từ đầu. (Đó là quy tắc Posix regex: nếu bạn muốn đưa ]vào một lớp ký tự, nó cần phải bắt đầu từ đầu. -Có thể đi ở đầu hoặc cuối, vì vậy nếu bạn cần cả hai ]-, bạn cần bắt đầu bằng ]và kết thúc bằng -, dẫn đến biểu tượng cảm xúc regex "Tôi biết tôi đang làm gì" [][-]:)


6
Chỉ muốn chỉ ra rằng "! ~ Là toán tử" không khớp "" là không đúng. Sử dụng if ! [[ $x =~ $y ]]hoặcif [[ ! $x =~ $y ]]
rượu.

shellchecker không đồng ý ...SC2076: Don't quote rhs of =~, it'll match literally rather than as a regex.
Leonardo

4
@leonard: điều đó khác với tuyên bố của tôi "bạn không thể trích dẫn mở rộng biến" và nhận xét "Điều này không hoạt động" như thế nào? Điều gì không rõ ràng về điều đó?
rici

1
@jinbeomhong: bản thân biểu thức được tách thành các từ như bình thường, sử dụng khoảng trắng. Nhưng mở rộng tham số và lệnh không phải là tách từ.
rici

1
@jinbeomhong: Tôi không nói gì khác với hướng dẫn sử dụng bash. "các từ giữa [[]]" được phân tích cú pháp ra khỏi văn bản chương trình, giống như cách các dòng lệnh được phân tích cú pháp thành các từ. Tuy nhiên, không giống như các dòng lệnh, các từ không bị tách ra sau khi mở rộng.
rici

27

Trong trường hợp ai đó muốn một ví dụ sử dụng các biến ...

#!/bin/bash

# Only continue for 'develop' or 'release/*' branches
BRANCH_REGEX="^(develop$|release//*)"

if [[ $BRANCH =~ $BRANCH_REGEX ]];
then
    echo "BRANCH '$BRANCH' matches BRANCH_REGEX '$BRANCH_REGEX'"
else
    echo "BRANCH '$BRANCH' DOES NOT MATCH BRANCH_REGEX '$BRANCH_REGEX'"
fi

13

Tôi muốn sử dụng [:punct:]cho điều đó. Ngoài ra, a-zA-Z09-9có thể chỉ là [:alnum:]:

[[ $TEST =~ ^[[:alnum:][:blank:][:punct:]]+$ ]]
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.