Mengapa [AZ] cocok dengan huruf kecil dalam bash?

42

Dalam semua shell yang saya sadari, rm [A-Z]*menghapus semua file yang dimulai dengan huruf besar, tetapi dengan bash ini menghapus semua file yang dimulai dengan huruf.

Karena masalah ini ada di Linux dan Solaris dengan bash-3 dan bash-4, itu tidak bisa berupa bug yang disebabkan oleh pencocokan pola kereta di libc atau definisi lokal yang tidak terkonfigurasi.

Apakah ini perilaku aneh dan berisiko yang dimaksudkan atau ini hanya bug yang ada yang tidak diperbaiki sejak bertahun-tahun?

schily
sumber
3
Apa artinya localeoutput? Saya tidak dapat mereproduksi ini ( touch foo; echo [A-Z]*menampilkan pola literal, bukan "foo", di direktori yang kosong).
chepner
4
Mempertimbangkan berapa banyak orang yang mengatakan itu bekerja untuk mereka, atau telah menunjukkan contoh bagaimana LC_COLLATE memengaruhi ini, mungkin Anda dapat mengedit pertanyaan Anda untuk menambahkan sesi bash sampel yang menggambarkan dengan tepat skenario yang Anda tanyakan. Harap sertakan versi bash yang Anda gunakan.
Kenster
Jika Anda membaca semua teks di sini, Anda akan tahu versi bash apa yang saya gunakan dan apa yang saya lakukan karena saya sudah memposting solusi untuk pertanyaan saya. Biarkan saya ulangi solusinya: bash tidak mengelola lokalnya sendiri sehingga pengaturan LC_COLLATE tidak mengubah apa pun hingga Anda memulai proses bash lain dengan lingkungan baru.
schily
1
Lihat juga Apakah (harus) LC_COLLATE memengaruhi rentang karakter? (tapi pertanyaan itu tidak secara khusus tentang bash)
Gilles 'SANGAT berhenti menjadi jahat'
"pengaturan LC_COLLATE tidak mengubah apa pun sampai Anda memulai proses bash lain dengan lingkungan baru." Itu tidak cocok dengan perilaku yang saya lihat dengan bash-4 di Solaris. Itu mengubah perilaku di shell yang sedang berjalan. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Jawaban:

67

Perhatikan bahwa ketika menggunakan rentang ekspresi seperti [az], huruf dari kasus lain dapat dimasukkan, tergantung pada pengaturan LC_COLLATE.

LC_COLLATE adalah variabel yang menentukan urutan pemeriksaan yang digunakan saat menyortir hasil ekspansi pathname, dan menentukan perilaku ekspresi rentang, kelas ekivalensi, dan menyusun urutan dalam ekspansi pathname dan pencocokan pola.


Pertimbangkan yang berikut ini:

$ 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

Perhatikan ketika perintah echo [a-z]dipanggil, output yang diharapkan adalah semua file dengan karakter huruf kecil. Juga, dengan echo [A-Z], file dengan karakter huruf besar akan diharapkan.


Pengumpulan standar dengan lokal seperti en_USmemiliki urutan berikut:

aAbBcC...xXyYzZ
  • Antara adan z(dalam [a-z]) adalah SEMUA huruf besar, kecuali untuk Z.
  • Antara Adan Z(dalam [A-Z]) adalah SEMUA huruf kecil, kecuali untuk a.

Lihat:

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

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

Jika Anda mengubah LC_COLLATEvariabel menjadi Cseperti yang diharapkan:

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

Jadi, ini bukan bug , ini masalah pengumpulan .


Alih-alih rentang ekspresi Anda dapat menggunakan kelas karakter yang didefinisikan POSIX , seperti upperatau lower. Mereka juga bekerja dengan LC_COLLATEkonfigurasi yang berbeda dan bahkan dengan karakter beraksen :

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z
kekacauan
sumber
Jika perilaku ini dapat dikendalikan oleh variabel lingkungan LC_ *, saya tidak bertanya. Saya bekerja di komite standar POSIX dan saya tahu menyusun masalah dengan mis trjadi ini yang saya periksa dulu.
schily
@ mungkin saya tidak bisa mereproduksi masalah Anda dengan bash-3 atau bash-4 yang lama; keduanya dapat dikontrol melalui LC_COLLATEyang juga didokumentasikan dalam manual.
kekacauan
Maaf, saya tidak dapat mereproduksi apa yang Anda yakini, tetapi lihat jawaban saya sendiri ... Dari ide-ide dalam diskusi ini saya menemukan alasan untuk masalahnya.
schily
25

[A-Z]di bashcocokkan semua elemen penyusun (karakter tetapi panggilan juga menjadi urutan karakter seperti Dszdi lokal Hongaria) yang mengurutkan setelah Adan mengurutkan sebelumnya Z. Di tempat Anda, cmungkin di antara B dan C.

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

Jadi catau zakan dicocokkan dengan [A-Z], tetapi tidak atau 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

Di C locale, urutannya adalah:

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

Jadi [A-Z]akan cocok A, B, C, Z, tapi tidak Çdan masih tidak .

Jika Anda ingin mencocokkan huruf besar (dalam skrip apa pun), Anda dapat menggunakannya [[:upper:]]. Tidak ada jalan masuk bashuntuk hanya mencocokkan huruf besar dalam skrip latin (kecuali dengan mendaftar secara individual).

Jika Anda ingin mencocokkan huruf Ake Z bahasa Inggris tanpa diakritik, Anda dapat menggunakan [A-Z]atau [[:upper:]]tetapi di Clokal (dengan asumsi data tidak dikodekan dalam set karakter seperti BIG5 atau GB18030 yang memiliki beberapa karakter yang pengkodeannya berisi pengkodean surat-surat itu) atau daftar secara individual ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Perhatikan bahwa ada beberapa variasi antara cangkang.

Untuk zsh, bash -O globasciiranges(opsi yang dinamai aneh diperkenalkan di bash-4.3), schily-shdan yash, [A-Z]cocok dengan karakter yang titik kodenya antara Adan dari Z, jadi akan sama dengan perilaku bashdi lokal C.

Untuk abu, mksh dan cangkang kuno, sama seperti di zshatas tetapi terbatas pada rangkaian byte tunggal. Yaitu, di lokal UTF-8 misalnya, [É-Ź]tidak akan cocok Ó, tetapi karena itu [<c3><89>-<c5><b9>], itu akan cocok dengan nilai byte 0x89 hingga 0xc5!

ksh93berperilaku seperti bashkecuali bahwa itu memperlakukan rentang kasus khusus yang ujungnya dimulai dengan huruf kecil atau huruf besar. Dalam hal ini, itu hanya cocok pada elemen penyusun yang mengurutkan antara kedua ujungnya, tetapi itu adalah (atau karakter pertama mereka untuk elemen penyusun multi-karakter) juga huruf kecil (atau masing-masing huruf besar). Jadi [A-Z]akan cocok É, tetapi tidak pada eseperti ehalnya antara Adan Ztetapi tidak huruf besar seperti Adan Z.

Untuk fnmatch()pola (seperti dalam find -name '[A-Z]') atau ekspresi reguler sistem (seperti dalam grep '[A-Z]'), itu tergantung pada sistem dan lokal. Sebagai contoh, pada sistem GNU di sini, [A-Z]tidak cocok xdi en_GB.UTF-8lokal, tetapi itu cocok di th_TH.UTF-8satu. Tidak jelas bagi saya informasi apa yang digunakannya untuk menentukan hal itu, tetapi tampaknya berdasarkan tabel pencarian yang berasal dari data lokal LC_COLLATE ).

Semua perilaku diizinkan oleh POSIX karena POSIX membiarkan perilaku rentang tidak ditentukan di lokal selain dari C locale. Sekarang kita dapat berdebat tentang manfaat dari setiap pendekatan.

bashPendekatan banyak masuk akal dengan [C-G], kami ingin karakter di antara Cdan G. Dan menggunakan urutan pengguna untuk menentukan apa yang ada di antara keduanya adalah pendekatan yang paling logis.

Sekarang, masalahnya adalah itu menghancurkan harapan banyak orang, terutama orang-orang yang terbiasa dengan perilaku tradisional pra-Unicode, bahkan sebelum hari internasionalisasi. Sementara dari pengguna normal, masuk akal jika [C-I]menyertakan hkarena hsurat itu antara Cdan Idan yang [A-g]tidak termasuk Z, itu masalah yang berbeda bagi orang-orang yang berurusan dengan ASCII hanya selama beberapa dekade.

Itu bashperilaku juga berbeda dari [A-Z]pencocokan berbagai dalam alat GNU lain seperti di GNU ekspresi reguler (seperti dalam grep/ sed...) atau fnmatch()seperti dalam find -name.

Ini juga berarti bahwa apa yang [A-Z]cocok bervariasi dengan lingkungan, dengan OS dan dengan versi OS. Fakta yang [A-Z]cocok dengan Á tetapi tidak Ź juga tidak optimal.

Untuk zsh/ yash, kami menggunakan urutan penyortiran yang berbeda. Alih-alih mengandalkan gagasan pengguna tentang urutan karakter, kami menggunakan nilai kode titik karakter. Itu memiliki manfaat karena mudah dipahami, tetapi dari sudut pandang praktis, di luar ASCII, itu tidak terlalu berguna. [A-Z]cocok dengan 26 huruf besar Inggris-Inggris, [0-9]cocok dengan angka desimal. Ada poin kode di Unicode yang mengikuti urutan beberapa huruf tetapi itu tidak digeneralisasi dan tidak dapat digeneralisasi karena orang yang berbeda menggunakan skrip yang sama tidak harus menyetujui urutan huruf.

Untuk shells tradisional dan mksh, dash, itu rusak (sekarang kebanyakan orang menggunakan karakter multi-byte), tetapi terutama karena mereka belum memiliki dukungan multi-byte. Menambahkan dukungan multi-byte ke shell like bashdan zshtelah menjadi upaya besar dan masih berlangsung. yash(shell Jepang) pada awalnya dirancang dengan dukungan multi-byte sejak awal.

Pendekatan ksh93 bermanfaat untuk konsisten dengan ekspresi reguler atau fnmatch sistem () (atau setidaknya tampak setidaknya pada sistem GNU). Di sana, itu tidak melanggar harapan beberapa orang karena [A-Z]tidak termasuk huruf kecil, [A-Z]termasuk É(dan Á, tetapi tidak Ź). Itu tidak konsisten dengan sortatau umumnya strcoll()memesan.

Stéphane Chazelas
sumber
1
Jika Anda benar, ini bisa dikontrol melalui variabel LC_ *. Tampaknya ada alasan berbeda.
schily
1
@cuonglm, lebih mirip mksh(keduanya berasal dari pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'tidak mengembalikan apa pun.
Stéphane Chazelas
2
@ Schily, saya menyebutkan sortkarena bashgumpalan didasarkan pada urutan jenis karakter. Saat ini saya tidak memiliki akses ke versi lama seperti itu bash, tetapi saya dapat memeriksanya nanti. Apakah itu berbeda?
Stéphane Chazelas
1
Izinkan saya menyebutkan lagi: zsh, POSIX-ksh88, ksh93t + Bourne Shell, semua berperilaku dengan cara yang sama seperti yang saya harapkan. Bash adalah satu-satunya shell yang berperilaku berbeda dan bash tidak dapat dikontrol melalui lokal dalam kasus ini.
schily
2
@schily, perhatikan bahwa \xFFada byte 0xFF, bukan karakter U + 00FF ( ÿitu sendiri dikodekan sebagai 0xC3 0xBF). \xFFsendiri tidak membentuk karakter yang valid jadi saya tidak bisa melihat mengapa itu harus dicocokkan dengan [É-Ź].
Stéphane Chazelas
9

Ini dimaksudkan dan didokumentasikan dalam bashdokumentasi, bagian pencocokan pola . Ekspresi rentang [X-Y]akan menyertakan karakter apa pun di antara Xdan Ymenggunakan urutan susunan dan rangkaian karakter lokal saat ini:

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

Anda dapat melihat, bdiurutkan di antara Adan Zdi en_US.utf8lokal.

Anda memiliki beberapa pilihan untuk mencegah perilaku ini:

# 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:]]*'

atau aktifkan globasciiranges(dengan bash 4.3 ke atas):

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

Saya mengamati perilaku ini pada instance Amazon EC2 baru. Karena OP tidak menawarkan MCVE , saya akan memposting satu:

$ 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

Jadi, tidak memiliki LC_*set saya memimpin bash 4.1.2 (1) - rilis di Linux untuk menghasilkan perilaku yang aneh. Saya andal dapat beralih perilaku aneh dengan mengatur dan menghapus variabel lokal masing-masing. Tidak mengejutkan, perilaku ini tampak konsisten melalui ekspor:

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

Sementara saya melihat bash berperilaku sebagai Stéphane "Shellshock" jawab Chazelas , saya pikir dokumentasi bash tentang pencocokan pola adalah buggy:

Misalnya, dalam lokal C default , '[a-dx-z]' setara dengan '[abcdxyz]'

Saya membaca kalimat itu (penekanan milik saya) sebagai "jika variabel lokal yang relevan tidak disetel, maka bash akan default ke lokal C". Bash tampaknya tidak melakukan itu. Alih-alih tampaknya menjadi default ke lokal tempat karakter diurutkan dalam urutan kamus dengan lipat diakritik:

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

Saya pikir itu akan baik bagi bash untuk mendokumentasikan bagaimana perilakunya ketika LC_*(secara khusus LC_CTYPEdan LC_COLLATE) tidak ditentukan. Tetapi sementara itu, saya akan membagikan beberapa kebijaksanaan :

... Anda harus sangat berhati-hati dengan [rentang karakter] karena mereka tidak akan menghasilkan hasil yang diharapkan kecuali jika dikonfigurasi dengan benar. Untuk saat ini, Anda harus menghindari menggunakannya dan menggunakan kelas karakter sebagai gantinya.

dan

Jika Anda benar-benar layak, dan / atau sedang membuat skrip untuk lingkungan multi-lokal, mungkin yang terbaik adalah memastikan Anda tahu apa variabel lokal Anda saat Anda mencocokkan file, atau untuk memastikan bahwa Anda mengkode dalam cara yang sepenuhnya generik.


Pembaruan Berdasarkan pada komentar G-Man, mari kita lihat lebih dalam apa yang terjadi:

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

Ah, ha! Itu menjelaskan pemeriksaan yang terlihat sebelumnya. Mari kita hapus semua variabel lokal:

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

Itu dia. Sekarang bash beroperasi secara konsisten sehubungan dengan dokumentasi pada sistem Linux ini. Jika salah satu variabel lokal ditetapkan ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALL, dll) maka Bash menggunakan mereka sesuai dengan yang manual. Kalau tidak, bash jatuh kembali ke C.

The Wooledge pesta FAQ telah mengatakan ini:

Pada sistem GNU baru-baru ini, variabel digunakan dalam urutan ini. Jika LANGUAGE diatur, gunakan itu, kecuali LANG diatur ke C, dalam hal ini LANGUAGE diabaikan. Juga, beberapa program sama sekali tidak menggunakan LANGUAGE sama sekali. Jika tidak, jika LC_ALL diatur, gunakan itu. Jika tidak, jika variabel LC_ * spesifik yang mencakup penggunaan ini disetel, gunakan itu. (Misalnya, LC_MESSAGES mencakup pesan kesalahan.) Jika tidak, gunakan LANG.

Jadi masalah yang tampak, baik dalam operasi dan dokumentasi, dapat dijelaskan dengan melihat jumlah total semua variabel penggerak lokal.

uskup
sumber
Jika tidak ada LC_variable dan bash tidak berperilaku seperti yang didokumentasikan untuk Clokal, ini adalah bug.
schily
1
@ bishop: (1) Typo: MVCE harus menjadi MCVE. (2) Jika Anda ingin contoh Anda selesai, Anda harus menambahkan env | grep LANGatau echo "$LANG".
G-Man Mengatakan 'Reinstate Monica'
@schily Penyelidikan lebih lanjut meyakinkan saya bahwa tidak ada bug dalam dokumentasi atau operasi pada sistem Linux ini.
Uskup
@ G-Man Terima kasih! Saya lupa tentang LANG. Dengan petunjuk itu, semua dijelaskan.
Uskup
LANG diperkenalkan sekitar tahun 1988 oleh Sun untuk upaya lokalisasi pertama, sebelum mereka menemukan bahwa variabel tunggal tidak cukup. Hari ini digunakan sebagai fallback dan LC_ALL digunakan sebagai terpaksa ditimpa.
schily
3

Lokal dapat mengubah karakter apa yang cocok dengan [A-Z]. Menggunakan

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

untuk menghilangkan pengaruh. (Saya menggunakan subkulit untuk melokalisasi perubahan).

choroba
sumber
Ini tidak berfungsi, masih cocok dengan semua huruf
schily
7
Ini tidak akan berhasil karena glob dilakukan sebelum rm dieksekusi. Coba export LC_ALL=Cdulu.
cuonglm
Maaf, Anda salah memahami pertanyaan yang terkait dengan bash dan bukan untuk rm.
schily
@schily: Ya, saya salah, Anda harus memisahkan pernyataan. Periksa pembaruan.
choroba
2

Seperti yang telah dikatakan, ini adalah masalah "menyusun urutan".

Rentang az dapat berisi huruf besar di beberapa lokal:

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

Solusi yang benar karena bash 4.3 adalah mengatur opsi globasciiranges:

shopt -s globasciiranges

untuk membuat bash bertindak seolah-olah LC_COLLATE=Ctelah diatur dalam rentang bola global .


sumber
-6

Tampaknya saya menemukan jawaban yang tepat untuk pertanyaan saya sendiri:

Bash buggy karena tidak mengelola lokal itu sendiri. Jadi pengaturan LC_ * dalam proses bash tanpa efek dalam proses shell itu.

Jika Anda mengatur LC_COLLATE = C dan kemudian memulai bash lainnya, globbing berfungsi seperti yang diharapkan dalam proses bash baru.

schily
sumber
2
Tidak di salah satu bash saya.
chaos
2
Saya tidak mem-repro ini dalam versi bash apa pun di komputer saya, sepertinya Anda tidak melakukannya exportdengan benar.
Chris Down
Jadi, Anda yakin bahwa sesuatu yang diekspor dengan benar, sehingga memengaruhi proses bash baru tidak diekspor dengan benar?
schily
4
Penanganan Solaris terhadap lingkungan sangat kurang, jadi saya tidak akan terkejut jika "bug" di bash adalah kurangnya solusi khusus Solaris.
hobbs
1
@ Schily: Apakah Anda memiliki kutipan di mana mengubah variabel LC_ * dalam shell diperlukan untuk membuatnya memperbarui keadaan lokalnya sendiri? Saya akan berpikir sebaliknya. Khususnya untuk shell yang mengeksekusi skrip, mengubah lokal di tengah jalan melalui penguraian / eksekusi skrip bahkan tidak akan memiliki perilaku yang terdefinisi dengan baik, karena skrip adalah file teks dan "file teks" hanya bermakna dalam konteks suatu pengodean karakter tunggal.
R ..