Почему [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 с новой средой.
Сhily
1
Смотрите также Влияет ли (должно) LC_COLLATE на диапазоны символов? (но этот вопрос не был конкретно о bash)
Жиль "ТАК - перестань быть злым"
msgstr "установка 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 Я не могу воспроизвести вашу проблему ни со старым bash-3, ни с bash-4; оба являются управляемыми, LC_COLLATEчто также задокументировано в руководстве.
хаос
Извините, я не могу воспроизвести то, во что вы верите, но вижу свой собственный ответ ... Из идей этой дискуссии я обнаружил причину проблемы.
Щил
25

[A-Z]in bashсопоставляет все элементы сортировки (символы, но call также являются последовательностью символов, как 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

В локали C, порядок будет:

$ 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]).

Обратите внимание, что есть некоторые различия между оболочками.

For zsh, bash -O globasciiranges(странно названная опция, введенная в bash-4.3), schily-shи yash, [A-Z]совпадает с символами, чья кодовая точка находится между той из Aи той из Z, так что будет эквивалентно поведению bashв локали Си.

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

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

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

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

bashЭтот подход имеет большой смысл, так как [C-G]мы хотим, чтобы символы между ними Cи G. И использование порядка сортировки пользователя для определения того, что находится между ними, является наиболее логичным подходом.

Теперь проблема в том, что это разрушает ожидания многих людей, особенно тех, кто привык к традиционному поведению до Юникода, даже до интернационализации. В то время как от обычного пользователя, это , возможно , ощущение того, что [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, dash, он не работает (теперь, когда большинство людей используют многобайтовые символы), но прежде всего потому, что у них пока нет многобайтовой поддержки. Добавление многобайтовой поддержки для таких оболочек, как bashи zshбыло огромным усилием, все еще продолжается. yash(японская оболочка) изначально была разработана с многобайтовой поддержкой с самого начала.

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

Стефан Шазелас
источник
1
Если вы были правы, это можно контролировать с помощью переменных LC_ *. Кажется, есть другая причина.
Сhily
1
@cuonglm, больше похоже mksh(оба получены из pdksh). posh -c $'case Ó in [É-Ź]) echo yes; esac'ничего не возвращает
Стефан Шазелас
2
@schily, я упоминаю, sortпотому что bashглобусы основаны на порядке сортировки символов. В настоящее время у меня нет доступа к такой старой версии bash, но я могу проверить позже. Было ли это иначе?
Стефан Шазелас
1
Позвольте мне еще раз упомянуть: zsh, POSIX-ksh88, ksh93t + Bourne Shell, все ведут себя так же, как я ожидаю. Bash - единственная оболочка, которая ведет себя по-другому, и в этом случае bash не контролируется через локаль.
Щил
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]*'
cuonglm
источник
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

В то время как я вижу, что bash ведет себя так, как ответил Шезел Стефан "Shellshock" , я думаю, что документация bash по сопоставлению с образцом содержит ошибки:

Например, в по умолчанию C локали , «[а-ах-г]» эквивалентно '[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 использует те в соответствии с ее руководством. В противном случае Bash возвращается к C.

В FAQ по Wooledge bash есть следующее:

В последних системах GNU переменные используются в этом порядке. Если установлен LANGUAGE, используйте его, если только LANG не установлен на C, и в этом случае LANGUAGE игнорируется. Кроме того, некоторые программы просто не используют ЯЗЫК вообще. В противном случае, если установлен LC_ALL, используйте это. В противном случае, если установлена ​​конкретная переменная LC_ *, которая охватывает это использование, используйте это. (Например, LC_MESSAGES охватывает сообщения об ошибках.) В противном случае используйте LANG.

Таким образом, очевидную проблему, как в работе, так и в документации, можно объяснить, посмотрев на общую сумму всех движущих переменных локали.

епископ
источник
Если переменная LC_variable отсутствует и bash не ведет себя так, как описано для Cлокали, это ошибка.
Щили
1
@bishop: (1) Опечатка: MVCE должен быть MCVE. (2) Если вы хотите, чтобы ваш пример был завершен, вы должны добавить env | grep LANGили echo "$LANG".
G-Man говорит: «Восстановите Монику»
@schily Дальнейшие исследования убедили меня, что в документации или работе этой системы Linux нет ошибок.
епископ
@ G-Man Спасибо! Я забыл о LANG. С этим намеком все объясняется.
епископ
LANG был введен Sun в 1988 году для первых попыток локализации, прежде чем они обнаружили, что одной переменной недостаточно. Сегодня он используется как запасной вариант, а LC_ALL используется как принудительная перезапись.
Щил
3

Локаль может изменить какие символы соответствуют [A-Z]. использование

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

устранить влияние. (Я использовал подоболочку для локализации изменений).

choroba
источник
Это не работает, оно все равно соответствует всем буквам
schily
7
Это не сработает, потому что glob был сделан до выполнения rm. Попробуй export LC_ALL=Cпервым.
Cuonglm
Извините, вы неправильно поняли вопрос, связанный с bash, а не с rm.
Щил
@schily: Да, я был не прав, вы должны разделить заявления. Проверьте обновление.
Чороба
2

Как уже было сказано, это проблема «порядка упорядочения».

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

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

Правильное решение, начиная с bash 4.3, заключается в установке опции globasciiranges:

shopt -s globasciiranges

заставить bash действовать так, как если бы LC_COLLATE=Cон был установлен в диапазонах глобализации .


источник
-6

Кажется, я нашел правильный ответ на свой вопрос:

Bash глючит, так как не управляет собственной локалью. Таким образом, установка LC_ * в процессе bash не влияет на этот процесс оболочки.

Если вы установите LC_COLLATE = C и затем запустите другой bash, глобализация будет работать, как и ожидалось, в новом процессе bash.

Шили
источник
2
Ни в одном из моих зазоров.
хаос
2
Я не повторяю это ни в одной версии bash на моей машине, похоже, вы сделали exportэто неправильно.
Крис Даун
То есть вы считаете, что что-то, что правильно экспортируется, так что это влияет на новый процесс bash, не экспортируется должным образом?
Щил
4
Обработка среды Solaris общеизвестно несовершенна, поэтому я не удивлюсь, если бы «ошибка» в bash заключалась в отсутствии обходного пути для Solaris.
Хоббс
1
@schily: У вас есть цитата, где изменение переменных LC_ * в оболочке требуется, чтобы заставить его обновить свое собственное состояние локали? Я бы подумал с точностью до наоборот. В частности, для оболочки, выполняющей сценарий, изменение локали в середине процесса синтаксического анализа / выполнения сценария даже не будет иметь четко определенного поведения, поскольку сценарий является текстовым файлом, а «текстовый файл» имеет смысл только в контексте контекста. односимвольная кодировка.
R ..