Чому [AZ] відповідає малі літери в bash?


42

З усіх відомих мені оболонок rm [A-Z]*видаляються всі файли, які починаються з великої літери, але з bash це видаляє всі файли, які починаються з літери.

Оскільки ця проблема існує в Linux та Solaris з bash-3 та bash-4, вона не може бути помилкою, спричиненою помилковим відповідником шаблону в libc або пропущеним визначенням локалі.

Чи призначена ця дивна та ризикована поведінка чи це просто помилка, яка існує нефіксованою вже багато років?


3
Що означає localeвихід? Я не можу відтворити це ( touch foo; echo [A-Z]*виводить буквальний шаблон, а не "foo", в іншому випадку порожній каталог).
чепнер

4
Враховуючи, скільки людей сказали, що це працює для них, або показали приклади того, як LC_COLLATE впливає на це, можливо, ви можете відредагувати своє запитання, щоб додати зразок сеансу bash, який точно ілюструє сценарій, про який ви запитуєте. Включіть версію bash, яку ви використовуєте.
Кенстер

Якби ви прочитали весь текст тут, ви б знали, яку версію bash я використовую і що я робив, оскільки я вже розмістив рішення свого питання. Дозвольте повторити рішення: bash не керує власною локаллю, так що налаштування LC_COLLATE нічого не змінить, поки ви не запустите інший процес bash з новим середовищем.
schily

1
Див. Також Чи має (чи) LC_COLLATE впливати на діапазон символів? (але це питання стосувалося не конкретно баш)
Жил "SO- перестань бути злим"

"налаштування LC_COLLATE нічого не змінює, поки ви не запустите інший процес bash з новим середовищем." Це не відповідає поведінці, яку я бачу з bash-4 на Solaris. Це зміна поведінки в запущеній оболонці. # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

Відповіді:


67

Зауважте, що при використанні виразів діапазону, таких як [az], можуть бути включені літери іншого регістру, залежно від налаштування LC_COLLATE.

LC_COLLATE являє собою змінну, яка визначає порядок зіставлення, використовуваний при сортуванні результатів розширення імені шляху, та визначає поведінку виразів діапазонів, класів еквівалентності та послідовностей згортання в межах розширення імені тракту та відповідності шаблону.


Розглянемо наступне:

$ 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

Зауважте, коли команда echo [a-z]викликається, очікуваним результатом будуть всі файли з малими символами. Також, echo [A-Z]очікується, що файли з великими літерами очікуються.


Стандартні порівняння з локальними мовами, такі як en_US:

aAbBcC...xXyYzZ
  • Між aта z[a-z]) є ВСІ великі літери, за винятком Z.
  • Між Aта Z[A-Z]) є ВСІ малі літери, за винятком a.

Побачити:

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

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

Якщо ви зміните LC_COLLATEзмінну, Cвона виглядає так, як очікувалося:

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

Отже, це не помилка , це питання зіставлення .


Замість виразів діапазону можна використовувати визначені POSIX класи символів , такі як upperабо lower. Вони також працюють з різною LC_COLLATEконфігурацією і навіть з наголошеними символами :

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

Якщо цю поведінку контролювали змінні середовища LC_ *, я не питав. Я працюю в стандартному комітеті POSIX і мені відомо про проблеми, пов'язані з вирішенням проблем, наприклад, trце те, що я перевірив спочатку.
schily

@schily я не можу відтворити вашу проблему ні зі старим bash-3, ні з bash-4; обидва є керованими, через LC_COLLATEщо також задокументовано в посібнику.
хаос

Вибачте, я не можу відтворити те, у що ви вірите, але бачу власну відповідь ... З ідей у ​​цій дискусії я виявив причину проблеми.
schily

25

[A-Z]у bashзбігах усіх збірних елементів (символи, але виклик також є послідовністю символів, як Dszу угорських мовах), які сортують після Aта сортують раніше Z. У вашій місцевості, cймовірно, вибираєте між B і C.

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

Так cабо zбуде відповідати [A-Z], але ні або 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

У мові С порядок був би таким:

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

Так [A-Z]буде відповідати A, B, C, Z, але не Çта до сих пір не .

Якщо ви хочете співставити великі літери (у будь-якому сценарії), ви можете використовувати [[:upper:]]замість цього. Немає вбудованого способу bashзіставлення лише великих літер у латинській писемності (за винятком перерахування їх окремо).

Якщо ви хочете , щоб відповідати Aна Z англійській мові літерами без діакрітікі, ви можете використовувати [A-Z]або , [[:upper:]]але в Cлокалі (передбачається , що дані не кодуються в наборах символів , таких як BIG5 або GB18030 , який має кілька символів , чия кодування містить кодування цих букв) або список їх окремо ( [ABCDEFGHIJKLMNOPQRSTUVWXYZ]).

Зауважте, що між оболонками є певна різниця.

Для zsh, bash -O globasciiranges(дивно з ім'ям опції , введеної в Баш-4.3), schily-shі yash, [A-Z]матчі на персонажів , чий код точка знаходиться між з Aі в Z, так було б еквівалентно поведінки bashв C локалі.

Для золи, мкш та стародавніх мушлі, як і zshвище, але обмежено однобайтовими знаками. Тобто у локалі UTF-8, наприклад, [É-Ź]не збігатиметься Ó, але оскільки це [<c3><89>-<c5><b9>]відповідатиме значенням байтів 0x89 до 0xc5!

ksh93поводиться як bashвиняток, за винятком того, що він розглядає як діапазони спеціальних випадків, кінці яких обидва починаються з малих літер або великих літер. У такому випадку він відповідає лише елементам, що складаються, які сортуються між цими кінцями, але вони (або їх перший символ для елементів, що складаються з кількох символів) також малі (або великі регістри відповідно). Так [A-Z]було б відповідати на É, але не eтак eже роду між Aі , Zале не в верхньому регістрі , як Aі Z.

Для fnmatch()шаблонів (як in find -name '[A-Z]') або системних регулярних виразів (як in grep '[A-Z]') це залежить від системи та локальної локальності. Наприклад, в системі GNU тут, [A-Z]не збігається з xв en_GB.UTF-8локалі, але це відбувається в th_TH.UTF-8одному. Мені незрозуміло, яку інформацію він використовує для визначення цього, але , мабуть, він заснований на таблиці пошуку, отриманій з даних локальних даних LC_COLLATE ).

Усі способи поведінки дозволені POSIX, оскільки POSIX залишає поведінку діапазонів не визначеними в інших регіонах, ніж локалі С. Тепер ми можемо сперечатися щодо переваг кожного підходу.

bashПідхід «S робить багато сенсу , як і [C-G], ми хочемо , щоб символи між Cі G. І використання порядку сортування користувача для того, що визначає, що є між ними, є найбільш логічним підходом.

Тепер проблема полягає в тому, що вона руйнує очікування багатьох людей, особливо тих, хто звик до традиційної поведінки до Unicode, навіть до доінтернаціоналізації. Хоча від звичайного користувача це може мати сенс, що в тому [C-I]числі, hколи hлист знаходиться між Cі Iщо [A-g]не включає Z, це інша справа для людей, які мали справу з ASCII лише десятиліттями.

Така bashповедінка також відрізняється від відповідності [A-Z]діапазону в інших інструментах GNU, таких як регулярні вирази GNU (як у grep/ sed...) або fnmatch()як у find -name.

Це також означає, що [A-Z]відповідність залежить від середовища, з ОС та з версією ОС. Те, що [A-Z]відповідає Á, але не Ź, також є неоптимальним.

Для zsh/ yashми використовуємо інший порядок сортування. Замість того, щоб спиратися на поняття користувача про порядок символів, ми використовуємо значення коду символьних точок. Це має перевагу в тому, що це легко зрозуміти, але з практичної точки зору, за межами ASCII, це не дуже корисно. [A-Z]збігається з 26 великих американських великих літер, [0-9]відповідає десятковим цифрам. У Unicode є кодові точки, які дотримуються порядку деяких алфавітів, але це не узагальнено і не може бути узагальнено, оскільки все одно різні люди, що використовують один і той же сценарій, не обов'язково погоджуються на порядок букв.

Для традиційних оболонок і mksh, тире, він порушений (зараз більшість людей використовує багатобайтові символи), але насамперед через те, що вони ще не мають багатобайтової підтримки. Додавання багатобайтової підтримки оболонкам, як, bashі zshбуло величезним зусиллям і триває досі. yash(японська оболонка) спочатку розроблявся з багатобайтовою підтримкою з самого початку.

Підхід ksh93 має вигоду узгоджуватися з регулярними виразами системи або fnmatch () (або, принаймні, видається принаймні в системах GNU). Там це не порушує сподівання деяких людей, оскільки [A-Z]не включає малі літери, [A-Z]включає É(і Á, але не Ź). Це не відповідає sortабо загалом strcoll().


1
Якби ви мали рацію, це можна контролювати за допомогою змінних LC_ *. Здається, є інша причина.
schily

1
@cuonglm, більше схоже mksh(обидва походять від pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'нічого не повертає.
Stéphane Chazelas

2
@schily, я зазначу, sortтому що bashглобуси засновані на порядку сортування символів. Наразі у мене немає доступу до такої старої версії bash, але я можу перевірити її пізніше. Чи було тоді інакше?
Stéphane Chazelas

1
Нагадаю ще раз: zsh, POSIX-ksh88, ksh93t + Bourne Shell, всі поводяться так само, як я очікував. Bash - це єдина оболонка, яка поводиться по-різному, і bash не керується через локаль в цьому випадку.
schily

2
@schily, зауважте, що \xFFє байт 0xFF, а не символ U + 00FF ( ÿсам кодується як 0xC3 0xBF). \xFFпоодинці не утворює дійсного символу, тому я не можу зрозуміти, чому він повинен відповідати [É-Ź].
Стефан Шазелас

9

Це передбачено та зафіксовано в bashдокументації, розділі відповідності шаблону . Вираз діапазону [X-Y]будуть включені будь-які символи між Xі з Yдопомогою впорядкованої послідовності і набір символів поточної локалі:

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

Ви можете бачити, bвідсортовані між Aта Zза en_US.utf8місцевим розташуванням.

Ви можете запобігти такій поведінці:

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

або увімкніть globasciiranges(з bash 4.3 і вище):

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

6

Я спостерігав таку поведінку на новому екземплярі Amazon EC2. Оскільки ОП не запропонував MCVE , я опублікую його:

$ 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

Отже, не маючи LC_*набір лідерів bash 4.1.2 (1) -випуск у Linux, щоб викликати дивну поведінку. Я можу надійно переключити дивну поведінку, встановивши та скасувавши відповідні змінні мови. Не дивно, що така поведінка виглядає послідовною через експорт:

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

Поки я бачу, як Баш поводиться так, як відповів Стефан "Шелшоу" Шазелас , я думаю, що документація на баш щодо узгодження шаблонів є помилковою:

Наприклад, у локалі C за замовчуванням '[a-dx-z]' еквівалентно '[abcdxyz]'

Я читав це речення (наголос мій) як "якщо відповідні змінні локалі не встановлені, то bash буде за замовчуванням для мови C". Баш, здається, не робить цього. Натомість, здається, дефолт має місце, де символи сортуються у словниковому порядку із діакритичним складанням:

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

Я думаю, що було б добре для bash задокументувати, як він буде вести себе, коли LC_*(конкретно LC_CTYPEі LC_COLLATE) не буде визначено. Але тим часом я поділюсь деякою мудрістю :

... вам слід бути дуже обережними з [діапазонами символів], оскільки вони не дадуть очікуваних результатів, якщо не буде правильно налаштовано. Наразі вам слід уникати їх використання та замість цього використовувати класи символів.

і

Якщо ви справді належні та / або розробляєте сценарії для середовищ з декількома локалями, можливо, найкраще переконатися, що ви знаєте, які є ваші змінні локалі, коли ви підбираєте файли, або щоб бути впевненим, що ви кодуєте в повністю родовим способом.


Оновлення На основі коментаря @ G-Man, давайте глибше розглянемо те, що відбувається:

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

А-а-а! Це пояснює порівняння, яке бачили раніше. Видалимо всі змінні локалі:

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

Там ми йдемо. Зараз bash працює послідовно щодо документації на цій системі Linux. Якщо якісь - або з локалізацій змінних встановлюється ( LANGUAGE, LANG, LC_COLLATE, LC_CTYPE, LC_ALLі т.д.) , то Bash використовує ті в відповідно до її керівництвом. В іншому випадку баш падає назад до С.

Wooledge Баш FAQ має це сказати:

В останніх системах GNU змінні використовуються в цьому порядку. Якщо встановлено LANGUAGE, використовуйте це, якщо для LANG не встановлено значення C, в цьому випадку LANGUAGE ігнорується. Також деякі програми взагалі просто не використовують LANGUAGE. В іншому випадку, якщо встановлено LC_ALL, використовуйте це. В іншому випадку, якщо встановлена ​​конкретна змінна LC_ *, яка охоплює це використання, використовуйте це. (Наприклад, LC_MESSAGES охоплює повідомлення про помилки.) В іншому випадку використовуйте LANG.

Таким чином, очевидну проблему, як в експлуатації, так і в документації, можна пояснити, переглянувши загальну суму всіх змінних водія локалі.


Якщо немає LC_variable, а bash не веде себе так, як це зафіксовано для локальної точки C, це помилка.
schily

1
@bishop: (1) Typo: MVCE має бути MCVE. (2) Якщо ви хочете, щоб ваш приклад був завершеним, слід додати env | grep LANGабо echo "$LANG".
G-Man каже: "Відновіть Моніку"

@schily Подальше дослідження переконало мене, що в документації чи роботі цієї системи Linux немає помилок.
єпископ

@ G-Man Спасибі! Я про це забув LANG. З цим натяком все пояснюється.
єпископ

LANG був введений близько 1988 року Sun для перших спроб локалізації, перш ніж вони виявили, що однієї змінної недостатньо. Сьогодні він використовується як резервний запас, а LC_ALL використовується як примусове перезапис.
schily

3

Локальний вміст може змінювати те, з якими символами відповідає [A-Z]. Використовуйте

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

для усунення впливу. (Я використовував підрозділ для локалізації змін).


Це не працює, воно все ще відповідає всім листам
schily

7
Це не спрацює, оскільки глобус був зроблений до виконання rm. Спробуйте export LC_ALL=Cспочатку.
cuonglm

Вибачте, ви неправильно розумієте питання, яке стосується bash, а не rm.
schily

@schily: Так, я помилявся, ви повинні розділяти заяви. Перевірте оновлення.
choroba

2

Як уже було сказано, це питання "замовлення на збір".

Діапазон az може містити великі літери у деяких мовах:

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

Правильне рішення, оскільки bash 4.3 - це встановити параметр globasciiranges:

shopt -s globasciiranges

змусити баш діяти так, ніби LC_COLLATE=Cвстановлено в глобальних діапазонах.


-6

Здається, я знайшов правильну відповідь на власне запитання:

Bash баггі, оскільки він не керує власним мовою. Отже, встановлення LC_ * в процесі bash не має ефекту в цьому оболонковому процесі.

Якщо встановити LC_COLLATE = C, а потім запустити інший баш, глобулінг працює так, як очікувалося в новому баш-процесі.


2
Не в жодному моєму базі.
хаос

2
Я не дорікаю це в будь-якій версії баш на моїй машині, це здається, що ви цього не зробили exportналежним чином.
Кріс Даун

Отже, ви вважаєте, що те, що належним чином експортується, так що це впливає на новий процес bash, не належним чином експортується?
schily

4
Поводження Solaris з навколишнім середовищем, як відомо, є недостатнім, тому я не здивуюсь, якби "помилка" в баші була відсутністю специфічного рішення для Solaris.
варильні панелі

1
@schily: Чи є у вас цитування того, де потрібна зміна змінних LC_ * в оболонці, щоб змусити її оновити свій власний стан локалі? Я б подумав саме навпаки. Зокрема, для оболонки, що виконує скрипт, зміна локальної середини шляхом розбору / виконання сценарію навіть не матиме чітко визначеної поведінки, оскільки сценарій є текстовим файлом, а "текстовий файл" має значення лише в контексті кодування одного символу
Р ..
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.