Tại sao [AZ] khớp với chữ cái viết thường trong bash?


42

Trong tất cả các shell tôi biết, rm [A-Z]*loại bỏ tất cả các tệp bắt đầu bằng một chữ cái viết hoa, nhưng với bash, nó sẽ loại bỏ tất cả các tệp bắt đầu bằng một chữ cái.

Vì sự cố này tồn tại trên Linux và Solaris với bash-3 và bash-4, nên đó không thể là lỗi do trình so khớp mẫu lỗi trong libc hoặc định nghĩa ngôn ngữ được định cấu hình sai.

Đây có phải là hành vi kỳ lạ và rủi ro dự định hay đây chỉ là một lỗi tồn tại không được trộn từ nhiều năm?


3
Làm những gì localeđầu ra? Tôi không thể sao chép điều này ( touch foo; echo [A-Z]*xuất ra mẫu chữ, không phải "foo", trong một thư mục trống khác).
chepner

4
Xem xét có bao nhiêu người đã nói rằng nó hoạt động với họ hoặc đã cho thấy các ví dụ về cách LC_COLLATE ảnh hưởng đến điều này, có thể bạn có thể chỉnh sửa câu hỏi của mình để thêm phiên bash mẫu minh họa chính xác kịch bản bạn đang hỏi. Vui lòng bao gồm phiên bản bash mà bạn đang sử dụng.
Kenster

Nếu bạn đã đọc tất cả các văn bản ở đây, bạn sẽ biết tôi sử dụng phiên bản bash nào và tôi đã làm gì vì tôi đã đăng giải pháp cho câu hỏi của mình. Hãy để tôi nhắc lại giải pháp: bash không quản lý ngôn ngữ riêng của nó để cài đặt LC_COLLATE không thay đổi bất cứ điều gì cho đến khi bạn bắt đầu một quy trình bash khác với môi trường mới.
schily


"thiết lập LC_COLLATE không thay đổi bất cứ điều gì cho đến khi bạn bắt đầu một quy trình bash khác với môi trường mới." Điều đó không phù hợp với hành vi tôi thấy với bash-4 trên Solaris. Nó đang thay đổi hành vi trong vỏ đang chạy. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Câu trả lời:


67

Lưu ý rằng khi sử dụng các biểu thức phạm vi như [az], các chữ cái của trường hợp khác có thể được bao gồm, tùy thuộc vào cài đặt LC_COLLATE.

LC_COLLATE là một biến xác định thứ tự đối chiếu được sử dụng khi sắp xếp kết quả của việc mở rộng tên đường dẫn và xác định hành vi của các biểu thức phạm vi, các lớp tương đương và các chuỗi đối chiếu trong mở rộng tên đường dẫn và khớp mẫu.


Hãy xem xét những điều sau đây:

$ touch a A b B c C x X y Y z Z
$ ls
a  A  b  B  c  C  x  X  y  Y  z  Z
$ echo [a-z] # Note the missing uppercase "Z"
a A b B c C x X y Y z
$ echo [A-Z] # Note the missing lowercase "a"
A b B c C x X y Y z Z

Lưu ý khi lệnh echo [a-z]được gọi, đầu ra dự kiến ​​sẽ là tất cả các tệp có ký tự chữ thường. Ngoài ra, với echo [A-Z], các tệp có ký tự viết hoa sẽ được mong đợi.


Đối chiếu tiêu chuẩn với các địa phương như en_UScó thứ tự sau:

aAbBcC...xXyYzZ
  • Giữa az(trong [a-z]) là TẤT CẢ các chữ cái viết hoa, ngoại trừ Z.
  • Giữa AZ(trong [A-Z]) là TẤT CẢ các chữ cái viết thường, ngoại trừ a.

Xem:

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

     aAbBcC[...]xXyYzZ
      |              |
from  A     to       Z

Nếu bạn thay đổi LC_COLLATEbiến thành Ctrông như mong đợi:

$ export LC_COLLATE=C
$ echo [a-z]
a b c x y z
$ echo [A-Z]
A B C X Y Z

Vì vậy, nó không phải là một lỗi , đó là một vấn đề đối chiếu .


Thay vì các biểu thức phạm vi, bạn có thể sử dụng các lớp ký tự được xác định POSIX , chẳng hạn như upperhoặc lower. Chúng cũng hoạt động với các LC_COLLATEcấu hình khác nhau và thậm chí với các ký tự có dấu :

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z

Nếu hành vi này được kiểm soát bởi các biến môi trường LC_ *, tôi đã không hỏi. Tôi làm việc trong ủy ban tiêu chuẩn POSIX và tôi biết về việc đối chiếu các vấn đề với vd, trvì vậy đây là điều tôi đã kiểm tra đầu tiên.
schily

@schily Tôi không thể tái tạo vấn đề của bạn bằng bash-3 cũ hoặc bash-4; cả hai đều có thể điều khiển thông qua LC_COLLATEđó cũng được ghi lại trong hướng dẫn.
hỗn loạn

Xin lỗi, tôi không thể sao chép những gì bạn tin, nhưng hãy xem câu trả lời của riêng tôi ... Từ những ý tưởng trong cuộc thảo luận này, tôi đã phát hiện ra lý do của vấn đề.
schily

25

[A-Z]trong bashtrận đấu tất cả các đối chiếu các yếu tố (nhân vật nhưng gọi cũng được chuỗi các ký tự như Dsztrong miền địa phương Hungary) mà loại sau Avà sắp xếp trước Z. Ở địa phương của bạn, ccó thể sắp xếp ở giữa B và C.

$ printf '%s\n' A a á b B c C Ç z Z  | sort
a
A
á
b
B
c
C
Ç
z
Z

Vì vậy, choặc zsẽ được kết hợp bởi [A-Z], nhưng không hoặc a.

$ printf '%s\n' A a á b B c C Ç z Z  |
pipe>  bash -c 'while IFS= read -r x; do case $x in [A-Z]) echo "$x"; esac; done'
A
á
b
B
c
C
Ç
z
Z

Trong miền địa phương C, thứ tự sẽ là:

$ printf '%s\n' A a á b B c C Ç z Z  | LC_COLLATE=C sort
A
B
C
Z
a
b
c
z
Ç
á

Vì vậy, [A-Z]sẽ phù hợp với A, B, C, Z, nhưng không phải Çmà vẫn không .

Nếu bạn muốn khớp với chữ in hoa (trong bất kỳ tập lệnh nào), bạn có thể sử dụng [[:upper:]]thay thế. Không có cách dựng sẵn bashđể chỉ khớp các chữ cái in hoa trong tập lệnh Latin (ngoại trừ bằng cách liệt kê chúng riêng lẻ).

Nếu bạn muốn để phù hợp với Avới Z tiếng Anh chữ không dấu, bạn có thể sử dụng [A-Z]hay [[:upper:]]nhưng trong Cmiền địa phương (giả sử dữ liệu không được mã hóa trong các bộ ký tự như BIG5 hoặc GB18030 trong đó có nhiều nhân vật có mã hóa chứa mã hóa của những chữ cái) hoặc danh sách chúng riêng lẻ ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Lưu ý rằng có một số biến thể giữa các vỏ.

Đối với zsh, bash -O globasciiranges(tùy chọn có tên lạ được giới thiệu trong bash-4.3) schily-shyash, [A-Z]khớp với các ký tự có điểm mã nằm giữa điểm đó Avà điểm đó Z, do đó sẽ tương đương với hành vi của bashngôn ngữ C.

Đối với tro, mksh và vỏ cổ, giống như zshtrên nhưng giới hạn ở các bộ ký tự byte đơn. Nghĩa là, trong một ngôn ngữ UTF-8 chẳng hạn, [É-Ź]sẽ không khớp Ó, nhưng vì đó [<c3><89>-<c5><b9>], nó sẽ khớp với các giá trị byte 0x89 đến 0xc5!

ksh93hành xử như bashngoại trừ việc nó xử lý như các trường hợp đặc biệt có phạm vi cả hai bắt đầu bằng chữ in thường hoặc chữ in hoa. Trong trường hợp đó, nó chỉ khớp với các phần tử đối chiếu sắp xếp giữa các phần cuối đó, nhưng đó là (hoặc ký tự đầu tiên của chúng cho các phần tử đối chiếu nhiều ký tự) cũng viết thường (hoặc viết hoa tương ứng). Vì vậy, [A-Z]sẽ có kết quả khớp É, nhưng không egiống như esắp xếp giữa AZnhưng không phải là chữ hoa AZ.

Đối với fnmatch()các mẫu (như trong find -name '[A-Z]') hoặc các biểu thức chính quy của hệ thống (như trong grep '[A-Z]'), nó phụ thuộc vào hệ thống và miền địa phương. Ví dụ, trên một hệ thống GNU đây, [A-Z]không phù hợp trên xtrong en_GB.UTF-8miền địa phương, nhưng nó trong th_TH.UTF-8một. Tôi không rõ thông tin mà nó sử dụng để xác định thông tin đó là gì, nhưng rõ ràng nó dựa trên bảng tra cứu có nguồn gốc từ dữ liệu bản địa LC_COLLATE ).

Tất cả các hành vi đều được POSIX cho phép vì POSIX để lại hành vi của các phạm vi không xác định ở các địa điểm khác ngoài miền C. Bây giờ chúng ta có thể tranh luận về lợi ích của từng phương pháp.

bashCách tiếp cận có nhiều ý nghĩa như với [C-G], chúng tôi muốn các nhân vật ở giữa CG. Và sử dụng thứ tự sắp xếp của người dùng cho những gì xác định những gì ở giữa là cách tiếp cận hợp lý nhất.

Bây giờ, vấn đề là nó phá vỡ sự mong đợi của rất nhiều người, đặc biệt là những người đã quen với hành vi truyền thống của tiền Unicode, thậm chí cả những ngày tiền quốc tế hóa. Trong khi từ một người dùng bình thường, nó có ý nghĩa may mà [C-I]bao gồm hnhư các hchữ cái là giữa CI[A-g]không bao gồm Z, đó là một vấn đề khác nhau cho những người đã bị xử lý ASCII chỉ trong nhiều thập kỷ.

bashHành vi đó cũng khác với [A-Z]phạm vi khớp trong các công cụ GNU khác như trong các biểu thức chính quy GNU (như trong grep/ sed...) hoặc fnmatch()như trong find -name.

Điều đó cũng có nghĩa là những gì [A-Z]phù hợp sẽ thay đổi theo môi trường, với HĐH và với phiên bản HĐH. Thực tế [A-Z]phù hợp với Á nhưng không cũng không tối ưu.

Đối với zsh/ yash, chúng tôi sử dụng một thứ tự sắp xếp khác nhau. Thay vì dựa vào khái niệm về thứ tự ký tự của người dùng, chúng tôi sử dụng các giá trị mã điểm ký tự. Điều đó có lợi ích là dễ hiểu, nhưng từ quan điểm thực tế của một số ít, bên ngoài ASCII, nó không hữu ích lắm. [A-Z]khớp với 26 chữ cái in hoa-tiếng Anh, [0-9]khớp với các chữ số thập phân. Có những điểm mã trong Unicode tuân theo thứ tự của một số bảng chữ cái nhưng không được khái quát hóa và không thể khái quát hóa vì dù sao những người khác nhau sử dụng cùng một tập lệnh không nhất thiết phải đồng ý về thứ tự các chữ cái.

Đối với shell và mksh truyền thống, dấu gạch ngang, nó đã bị hỏng (hiện tại hầu hết mọi người sử dụng các ký tự nhiều byte), nhưng chủ yếu là vì họ chưa có hỗ trợ nhiều byte. Thêm hỗ trợ nhiều byte cho shell như bashzshđã là một nỗ lực rất lớn và vẫn đang tiếp tục. yash(một vỏ tiếng Nhật) ban đầu được thiết kế với sự hỗ trợ nhiều byte ngay từ đầu.

Cách tiếp cận của ksh93 có lợi ích là phù hợp với các biểu thức chính quy hoặc fnmatch () (hoặc ít nhất là xuất hiện ít nhất trên các hệ thống GNU). Ở đó, nó không phá vỡ kỳ vọng của một số người vì [A-Z]không bao gồm các chữ cái viết thường, [A-Z]bao gồm É(và Á, nhưng không phải). Nó không phù hợp với sorthoặc nói chung là strcoll()trật tự.


1
Nếu bạn đã đúng, điều này có thể được kiểm soát thông qua các biến LC_ *. Dường như có một lý do khác nhau.
schily

1
@cuonglm, thích hơn mksh(cả hai đều bắt nguồn từ pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'trả lại không có gì.
Stéphane Chazelas

2
@schily, tôi đề cập đến sortvì các khối bashđược dựa trên thứ tự sắp xếp nhân vật. Tôi hiện không có quyền truy cập vào phiên bản cũ như vậy bash, nhưng tôi có thể kiểm tra sau. Đã khác rồi sao?
Stéphane Chazelas

1
Hãy để tôi đề cập lại: zsh, POSIX-ksh88, ksh93t + Bourne Shell, tất cả đều hành xử giống như tôi mong đợi. Bash là lớp vỏ duy nhất có hành vi khác nhau và bash không thể điều khiển được thông qua miền địa phương trong trường hợp này.
schily

2
@schily, lưu ý rằng \xFFbyte 0xFF, không phải ký tự U + 00FF ( ÿchính nó được mã hóa thành 0xC3 0xBF). \xFFmột mình không tạo thành một nhân vật hợp lệ vì vậy tôi không thể hiểu tại sao nó phải phù hợp với [É-Ź].
Stéphane Chazelas

9

Đó là dự định và tài liệu trong bashtài liệu, phần phù hợp với mẫu . Biểu thức phạm vi [X-Y]sẽ được bao gồm bất kỳ ký tự nào giữa XYsử dụng trình tự đối chiếu và bộ ký tự của miền địa phương hiện tại:

LC_ALL=en_US.utf8 bash -c 'case b in [A-Z]) echo yes; esac' 
yes

Bạn có thể thấy, bsắp xếp giữa AZtrong en_US.utf8miền địa phương.

Bạn có một số lựa chọn để ngăn chặn hành vi này:

# Setting LC_ALL or LC_COLLATE to C
LC_ALL=C bash -c 'echo [A-Z]*'

# Or using POSIX character class
LC_ALL=C bash -c 'echo [[:upper:]]*'

hoặc bật globasciiranges(với bash 4.3 trở lên):

bash -O globasciiranges -c 'echo [A-Z]*'

6

Tôi đã quan sát hành vi này trên một ví dụ Amazon EC2 mới. Vì OP không cung cấp MCVE , tôi sẽ đăng một:

$ cd $(mktemp -d)
$ touch foo
$ echo [A-Z]*     # prepare for a surprise!
foo

$ echo $BASH_VERSION
4.1.2(1)-release
$ uname -a
Linux spinup-tmp12 3.14.27-25.47.amzn1.x86_64 #1 SMP Wed Dec 17 18:36:15 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

$ env | grep LC_  # no locale, let's set one
$ LC_ALL=C
$ echo [A-Z]*
[A-Z]*

$ unset LC_ALL    # ok, good. what if we go back to no locale?
$ echo [A-Z]*
foo

Vì vậy, không có LC_*tập hợp của tôi dẫn đến bash 4.1.2 (1) - hãy giải phóng Linux để tạo ra hành vi rõ ràng kỳ quặc. Tôi có thể chuyển đổi hành vi kỳ lạ một cách đáng tin cậy bằng cách đặt và bỏ đặt các biến cục bộ tương ứng. Không có gì đáng ngạc nhiên, hành vi này xuất hiện nhất quán thông qua xuất khẩu:

$ export LC_ALL=C
$ bash
$ echo [A-Z]*
[A-Z]*
$ exit
$ echo $SHLVL
1
$ unset LC_ALL
$ bash
$ echo [A-Z]*
foo

Trong khi tôi thấy bash hoạt động như Stéphane "Shellshock" Chazelas đã trả lời , tôi nghĩ rằng tài liệu bash về khớp mẫu là lỗi:

Ví dụ: trong ngôn ngữ C mặc định , '[a-dx-z]' tương đương với '[abcdxyz]'

Tôi đọc câu đó (nhấn mạnh của tôi) là "nếu các biến miền địa phương có liên quan không được đặt, thì bash sẽ mặc định là miền địa phương C". Bash dường như không làm điều đó. Thay vào đó, nó dường như được mặc định là một miền địa phương nơi các ký tự được sắp xếp theo thứ tự từ điển với cách gấp dấu phụ:

$ echo [A-E]*
[A-E]*
$ echo [A-F]*
foo
$ touch "évocateur"
$ echo [A-F]*
foo évocateur

Tôi nghĩ sẽ tốt cho bash khi ghi lại cách nó sẽ hoạt động khi LC_*(cụ thể LC_CTYPELC_COLLATE) không được xác định. Nhưng trong lúc này, tôi sẽ chia sẻ một chút khôn ngoan :

... bạn phải rất cẩn thận với [phạm vi ký tự] bởi vì chúng sẽ không tạo ra kết quả như mong đợi trừ khi được cấu hình đúng. Hiện tại, bạn nên tránh sử dụng chúng và sử dụng các lớp nhân vật thay thế.

Nếu bạn thực sự đúng và / hoặc đang viết kịch bản cho môi trường đa địa phương, có lẽ tốt nhất là đảm bảo bạn biết biến địa phương của bạn là gì khi bạn khớp tệp hoặc để chắc chắn rằng bạn đang mã hóa trong cách hoàn toàn chung chung.


Cập nhật Dựa trên nhận xét @ G-Man, chúng ta hãy tìm hiểu sâu hơn về những gì đang xảy ra:

$ env | grep LANG
LANG=en_US.UTF-8

À, ha! Điều đó giải thích sự đối chiếu nhìn thấy trước đó. Hãy xóa tất cả các biến cục bộ:

$ unset LANG LANGUAGE LC_ALL
$ env | grep 'LC_|LANG'
$ echo [A-Z]*
[A-Z]*

Chúng tôi đi đây. Bây giờ bash hoạt động nhất quán đối với tài liệu về hệ thống Linux này. Nếu bất kỳ của các biến địa phương được thiết lập ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, vv) sau đó Bash sử dụng những theo hướng dẫn của nó. Nếu không, bash rơi trở lại C.

Câu hỏi thường gặp về Wooledge có điều này để nói:

Trên các hệ thống GNU gần đây, các biến được sử dụng theo thứ tự này. Nếu LANGUAGE được đặt, hãy sử dụng nó, trừ khi LANG được đặt thành C, trong trường hợp đó, LANGUAGE bị bỏ qua. Ngoài ra, một số chương trình đơn giản là không sử dụng NGÔN NGỮ. Mặt khác, nếu LC_ALL được đặt, hãy sử dụng nó. Mặt khác, nếu biến LC_ * cụ thể bao gồm mức sử dụng này được đặt, hãy sử dụng biến đó. (Ví dụ: LC_MESSAGES bao gồm các thông báo lỗi.) Nếu không, hãy sử dụng LANG.

Vì vậy, vấn đề rõ ràng, cả trong vận hành và tài liệu, có thể được giải thích bằng cách xem xét tổng số của tất cả các biến lái xe cục bộ.


Nếu không có LC_variable có mặt và bash không hoạt động như tài liệu cho Cmiền địa phương, đây là một lỗi.
schily

1
@bishop: (1) Typo: MVCE nên là MCVE. (2) Nếu bạn muốn ví dụ của mình được hoàn thành, bạn nên thêm env | grep LANGhoặc echo "$LANG".
G-Man nói 'Tái lập Monica'

@schily Điều tra thêm đã thuyết phục tôi rằng không có lỗi trong tài liệu hoặc hoạt động trên hệ thống Linux này.
giám mục

@ G-Man Cảm ơn! Tôi quên mất LANG. Với gợi ý đó, tất cả được giải thích.
giám mục

LANG được Sun giới thiệu vào khoảng năm 1988 cho những nỗ lực bản địa hóa đầu tiên, trước khi họ phát hiện ra rằng một biến duy nhất là không đủ. Ngày nay, nó được sử dụng như một dự phòng và LC_ALL được sử dụng như là ghi đè bắt buộc.
schily

3

Bản địa có thể thay đổi những ký tự được khớp bởi [A-Z]. Sử dụng

(LC_ALL=C; rm [A-Z]*)

để loại bỏ ảnh hưởng. (Tôi đã sử dụng một subshell để bản địa hóa sự thay đổi).


Điều này không hoạt động, nó vẫn khớp với tất cả các chữ cái
schily

7
Điều này sẽ không hoạt động vì toàn cầu đã được thực hiện trước khi thực hiện rm. Hãy thử export LC_ALL=Cđầu tiên.
cuonglm

Xin lỗi, bạn đã hiểu sai câu hỏi có liên quan đến bash và không phải rm.
schily

@schily: Vâng, tôi đã sai, bạn phải tách các báo cáo. Kiểm tra cập nhật.
choroba

2

Như đã nói, đây là một vấn đề "đối chiếu".

Phạm vi az có thể chứa chữ in hoa ở một số địa phương:

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

Giải pháp đúng vì bash 4.3 là đặt tùy chọn globasciiranges:

shopt -s globasciiranges

để hành động bash làm như thể LC_COLLATE=Cđã được thiết lập trong glob dãy ing.


-6

Dường như tôi đã tìm thấy câu trả lời đúng cho câu hỏi của riêng mình:

Bash là lỗi vì nó không quản lý ngôn ngữ riêng của nó. Vì vậy, thiết lập LC_ * trong quy trình bash là không có hiệu lực trong quy trình hệ vỏ đó.

Nếu bạn đặt LC_COLLATE = C và sau đó bắt đầu một bash khác, quá trình tạo khối sẽ hoạt động như mong đợi trong quy trình bash mới.


2
Không có trong bất kỳ bash của tôi.
hỗn loạn

2
Tôi không repro điều này trong bất kỳ phiên bản bash nào trên máy của tôi, có vẻ như bạn đã không sử exportdụng đúng cách.
Chris Down

Vì vậy, bạn tin rằng một cái gì đó được xuất khẩu đúng cách, để nó ảnh hưởng đến một quá trình bash mới không được xuất khẩu đúng cách?
schily

4
Việc xử lý môi trường của Solaris nổi tiếng là thiếu sót, vì vậy tôi sẽ không ngạc nhiên nếu "lỗi" trong bash là thiếu một cách giải quyết cụ thể của Solaris.
hobbs

1
@schily: Bạn có một trích dẫn về việc thay đổi các biến LC_ * trong một vỏ được yêu cầu để làm cho nó cập nhật trạng thái miền địa phương của chính nó không? Tôi sẽ nghĩ chính xác điều ngược lại. Đặc biệt đối với trình bao thực thi tập lệnh, việc thay đổi ngôn ngữ giữa chừng thông qua phân tích cú pháp / thực thi tập lệnh thậm chí sẽ không có hành vi được xác định rõ, vì tập lệnh là tệp văn bản và "tệp văn bản" chỉ có ý nghĩa trong ngữ cảnh của mã hóa ký tự đơn.
R ..
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.