Що робить "LC_ALL = C"?


324

Яке Cзначення має LC_ALLробити в Unix-подібних системах?

Я знаю, що воно має однаковий локал для всіх аспектів, але що Cробити?


Якщо ви хочете вирішити проблему з xclockпопередженням ( Missing charsets in String to FontSet conversion), буде краще, якщо ви використаєте, LC_ALL=C.UTF-8щоб уникнути проблем із кирилицею. Щоб встановити цю змінну середовища, потрібно додати наступний рядок до кінця ~/.bashrcфайлу -export LC_ALL=C.UTF-8
fedotsoldier

@fedotsoldier ви, певно, повинні задати питання і дати відповідь самостійно, я не думаю, що це пов'язано з питанням. Це просто відповідь на різні проблеми, які у вас виникли.
jcubic

Так, ти маєш рацію, нормально
fedotsoldier

Відповіді:


209

Він змушує програми використовувати мову за замовчуванням для виводу:

$ LC_ALL=es_ES man
¿Qué página de manual desea?

$ LC_ALL=C man
What manual page do you want?

і змушує сортування бути байтовим:

$ LC_ALL=en_US sort <<< $'a\nb\nA\nB'
a
A
b
B

$ LC_ALL=C sort <<< $'a\nb\nA\nB'
A
B
a
b

20
+1 для хороших прикладів, але не вистачає важливої ​​інформації, що є у відповіді Стефана ...
Олів'є Дулак

4
Що ви маєте на увазі під мовою за замовчуванням ?
Stéphane Chazelas

2
Так, я розумію, автор може робити все, що йому заманеться, в тому числі не робити те, що написано на бляшанці. Річ у тім. Американська англійська мова є єдиною мовою, яку можна правильно зобразити за допомогою шаблону в LC_ALL = C, єдиною мовою, де порядок сортування в LC_ALL = C (LC_COLLATE) має сенс, LC_ALL = C (LC_TIME) має англійські імена місяця та дня. Я ніколи не бачив програм, де LC_ALL = C повертав повідомлення іншою мовою від LC_ALL = en LANGUAGE = en. Тож я маю право повідомити про помилку в програмі, якщо це не так? (не кажучи про додатки, не перекладені тут англійською).
Stéphane Chazelas

2
Проблема полягає в тому, що "американська англійська мова є єдиною мовою, яку можна правильно представити за допомогою шаблону в LC_ALL = C". Зазвичай це стосується лише програм C / C ++ при використанні вузьких символів, але навіть тоді є винятки (оскільки є кілька мов, які використовують лише символи та символи, знайдені в ASCII). Повідомлення про помилку, коли мова за замовчуванням не є англійською, зробить вас здаватись ... недоліком.
Ігнасіо Васкес-Абрамс

3
Зауважте, що англійською мовою (що означає LANG = en_US.utf8) повідомлення можуть (і повинні) використовувати символи unicode, наприклад "" для цитування рядків. Тоді як у LANG = C він має лише ASCII (подвійні лапки, зворотні котирування та апострофи).
Анхель

332

LC_ALL- це змінна середовище, яка перекриває всі інші параметри локалізації ( за винятком $LANGUAGEдеяких обставин ).

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

Зазвичай ви встановлюєте $LANGсвої переваги зі значенням, яке ідентифікує ваш регіон (наприклад, fr_CH.UTF-8якщо ви перебуваєте у франкомовної Швейцарії, використовуючи UTF-8). Окремі LC_xxxзмінні перекривають певний аспект. LC_ALLперекриває їх усіх. localeКоманди, при виклику без аргументів дає коротку інформацію про поточні настройки.

Наприклад, у системі GNU я отримую:

$ locale
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=

Я можу замінити індивідуальне налаштування, наприклад:

$ LC_TIME=fr_FR.UTF-8 date
jeudi 22 août 2013, 10:41:30 (UTC+0100)

Або:

$ LC_MONETARY=fr_FR.UTF-8 locale currency_symbol
€

Або замініть усе за допомогою LC_ALL.

$ LC_ALL=C LANG=fr_FR.UTF-8 LC_MESSAGES=fr_FR.UTF-8 cat /
cat: /: Is a directory

У сценарії, якщо ви хочете застосувати певний параметр, оскільки ви не знаєте, які налаштування примусив користувач (можливо, також LC_ALL), ваш найкращий, найбезпечніший і, як правило, єдиний варіант - це застосувати LC_ALL.

CЛокаль спеціальна мова , який призначається , щоб бути самим простим локалі. Ви також можете сказати, що хоча інші локалі для людей, мова C є для комп'ютерів. У мові C символи - це одиничні байти, набір символів - ASCII (ну, не потрібно, але на практиці буде в системах, які більшість з нас коли-небудь отримає для використання), порядок сортування базується на значеннях байтів, мова зазвичай є англійською мовою США (хоча для повідомлень додатків (на відміну від речей, таких як імена місяця чи дня або повідомлення системних бібліотек), це на розсуд автора програми), і такі речі, як символи валюти, не визначені.

У деяких системах є різниця з локальним пунктом POSIX, де, наприклад, порядок сортування для символів, що не належать до ASCII, не визначений.

Зазвичай ви виконуєте команду з LC_ALL = C, щоб уникнути налаштувань користувача для втручання у ваш сценарій. Наприклад, якщо ви хочете [a-z]відповідати 26 символам ASCII від aдо z, вам доведеться встановити LC_ALL=C.

У системах GNU LC_ALL=Cі LC_ALL=POSIX(або LC_MESSAGES=C|POSIX) переосмислити $LANGUAGE, хоча LC_ALL=anything-elseне буде.

Кілька випадків, коли вам зазвичай потрібно встановити LC_ALL=C:

  • sort -uабо sort ... | uniq.... У багатьох локальних системах, окрім C, у деяких системах (зокрема в GNU) деякі символи мають той самий порядок сортування . sort -uне повідомляє унікальні рядки, але один з кожної групи рядків, що мають рівний порядок сортування. Отже, якщо ви хочете унікальних ліній, вам потрібна локаль, де символи є байтом, а всі символи мають різний порядок сортування (що Cгарантує локаль).
  • те ж саме стосується =оператора сумісного з POSIX exprабо ==оператора сумісного з POSIX awks ( mawkі gawkне є POSIX у цьому відношенні), який не перевіряє, чи є два рядки однаковими, але чи сортують вони однакові.
  • Діапазон символів, як у grep. Якщо ви хочете відповідати букві мовою користувача, використовуйте grep '[[:alpha:]]'та не змінюйте LC_ALL. Але якщо ви хочете відповідати a-zA-Zсимволам ASCII, вам потрібно LC_ALL=C grep '[[:alpha:]]'або LC_ALL=C grep '[a-zA-Z]'¹. [a-z]відповідає символам, які сортуються після aі раніше z(хоча з багатьма API це складніше). В інших регіонах ви, як правило, не знаєте, що це. Наприклад, деякі локалі ігнорують випадок для сортування, тому [a-z]в деяких API, таких як bashшаблони, можуть бути включені [B-Z]або [A-Y]. У багатьох локальних точках UTF-8 (у тому числі en_US.UTF-8в більшості систем) [a-z]будуть входити латинські літери від aдо yдіакритики, але не ті, що є z(оскількиzсорти перед ними), які я не можу уявити, було б те, що ви хочете (чому б ви хотіли включити, éа ні ź?).
  • Арифметика з плаваючою комою в ksh93. ksh93відзначає decimal_pointналаштування в LC_NUMERIC. Якщо ви пишете скрипт, який містить a=$((1.2/7)), він перестане працювати, коли його виконує користувач, у локалі якого є коска як десятковий роздільник:

    $ ksh93 -c 'echo $((1.1/2))'
    0.55
    $ LANG=fr_FR.UTF-8  ksh93 -c 'echo $((1.1/2))'
    ksh93: 1.1/2: arithmetic syntax error
    

    Тоді вам потрібні такі речі, як:

    #! /bin/ksh93 -
    float input="$1" # get it as input from the user in his locale
    float output
    arith() { typeset LC_ALL=C; (($@)); }
    arith output=input/1.2 # use the dot here as it will be interpreted
                           # under LC_ALL=C
    echo "$output" # output in the user's locale
    

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

  • Коли вам потрібні символи в байтах. На сьогоднішній день більшість локалів на основі UTF-8, що означає, що символи можуть займати від 1 до 6 байт. У роботі з даними, призначеними для байтів, з текстовими утилітами, вам потрібно встановити LC_ALL = C. Це також значно покращить продуктивність, оскільки аналіз даних UTF-8 має вартість.
  • слідство попереднього пункту: при обробці тексту, де ви не знаєте, в який набір символів записаний вхід, але ви можете припустити, що він сумісний з ASCII (як практично всі діаграми). Наприклад, grep '<.*>'шукати рядки, що містять a <, >пара не спрацює, якщо ви знаходитесь у локалі UTF-8 і вхід кодується в однобайтовому 8-бітовому наборі символів типу iso8859-15. Це тому, що .лише відповідні символи та символи, що не належать до ASCII, в ізо8859-15, ймовірно, не утворюють дійсного символу в UTF-8. З іншого боку, LC_ALL=C grep '<.*>'буде працювати, тому що будь-яке значення байту утворює дійсний символ у Cлокалі.
  • Будь-який час, коли ви обробляєте вхідні дані або вихідні дані, не призначені для / для людини. Якщо ви розмовляєте з користувачем, ви можете скористатися їх умовою та мовою, але, наприклад, якщо ви генеруєте деякі цифри, щоб подати якусь іншу програму, яка очікує десяткових знаків в англійському стилі, або англійські імена місяця, вам потрібно встановити LC_ALL = C:

    $ printf '%g\n' 1e-2
    0,01
    $ LC_ALL=C printf '%g\n' 1e-2
    0.01
    $ date +%b
    août
    $ LC_ALL=C date +%b
    Aug
    

    Це також стосується таких речей, як порівняння нечутливих до регістру (наприклад, у grep -i) та перетворення справ ( awk's toupper(), dd conv=ucase...). Наприклад:

    grep -i i
    

    не гарантується відповідність Iу місцевості користувача. У деяких турецьких районах , наприклад, це робить не як великі букви iє İ(зверніть увагу на точку) там і рядковим Iє ı(зверніть увагу на відсутню точку).


Залежно від кодування тексту, це не обов'язково робити правильно. Це справедливо для наборів символів UTF-8 або однобайтових наборів символів (наприклад, iso-8859-1), але необов'язково багатобайтових наборів символів, що не належать до UTF-8.

Наприклад, якщо ви знаходитесь у zh_HK.big5hkscsмісцевому значенні (Гонконг, використовуючи гонконгський варіант кодування китайських символів BIG5), і ви хочете шукати англійські літери у файлі, закодованому в цих діаграм, виконуючи будь-яке:

LC_ALL=C grep '[[:alpha:]]'

або

LC_ALL=C grep '[a-zA-Z]'

було б помилково, тому що в цій наборі (та багатьох інших, але майже не використовуються з моменту виходу UTF-8) багато символів містять байти, що відповідають кодуванню ASCII символів A-Za-z. Наприклад, усі A䨝䰲丕乙乜你再劀劈呸哻唥唧噀噦嚳坽(та багато іншого) містять кодування A. є 0x96 0x41, і 0x41, Aяк в ASCII. Отже, наші LC_ALL=C grep '[a-zA-Z]'відповідатимуть тим рядкам, які містять ці символи, як би неправильно трактували ці послідовності байтів.

LC_COLLATE=C grep '[A-Za-z]'

працював би, але тільки якщо LC_ALLінше не встановлено (що би перекрило LC_COLLATE). Тож вам може знадобитися:

grep '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]'

якщо ви хотіли шукати англійські літери у файлі, закодованому в кодуванні локалу.


12
+1, це найкраща відповідь (для вказівки на перевагу тощо). Але бракує (приємних) прикладів відповіді Ігнасіо ^^
Олів'є Дулак

1
Незначна нитка: Місцевість Cпотрібна лише для підтримки "портативного набору символів" (ASCII 0-127), а поведінка для символів> 127 технічно не визначено . На практиці більшість програм трактує їх як непрозорі дані та передаватиме їх, як ви описали. Але не все: зокрема, Ruby може задихатися в даних char із байтами> 127, якщо працює в Cлокалі. Я, чесно кажучи, не знаю, чи технічно це "відповідне", але ми це бачили в дикій природі .
Ендрю Янке

2
@AndrewJanke, так. Зауважте, що переносний набір символів не передбачає ASCII, ні 0-127. У списку розсилки групи Остін було багато дискусій щодо того, якими будуть властивості локального набору символів "C", і загальний консенсус (і це буде з'ясовано в наступній специфікації) полягає в тому, що ця набір буде однозначним, байт і охоплюють весь 8-бітовий діапазон (із властивостями, описаними тут). У середній час, так, може виникнути певна розбіжність (як помилка або тому, що специфікація недостатньо явна). У будь-якому випадку LC_ALL = C є найближчим, ви можете отримати розумну поведінку.
Стефан Шазелас

1
Кодова точка Unicode в UTF-8 може мати максимум 4 октети (або байти), але для деяких символів потрібно більше однієї кодової точки, що може призвести до більш довгих послідовностей, ніж 6 октетів.
12431234123412341234123

1
@ +12431234123412341234123, оригінальний UTF-8 кодування покриває до U + 7fffffff (6 байт, і є деякі розширення йти до 13 байт , як perl«з \x{7FFFFFFFFFFFFFFF}) і в той час як діапазон кодових точок Unicode був довільно обмежений U + 10FFFF (через обмеження дизайну UTF-16) деякі інструменти все ще розпізнають / видають 6 байт-символів. Ось що я мав на увазі під 6 байтовими символами. У семантиці Unix один символ - це одна кодова точка. Ваші більш ніж одні "символи" кодової точки загалом позначаються як кластери графем, щоб роз'єднати їх із символами.
Стефан Шазелас

7

Cє мовою за замовчуванням, "POSIX" - псевдонім "C". Я здогадуюсь, що "C" походить від ANSI-C. Можливо, ANSI-C визначить локальний пункт "POSIX".


І C, і UNIX значно передували ANSI C.
CVn

@ MichaelKjörling: Так? Я бачив документацію до ANSI, і вона не мала локалів. Внутрішньо в AT&T Bell Labs всі розмовляли англійською.
MSalters

@MSalters Справа в тому, що в документації до ANSI для мови C не згадуються локалі (що може означати або не означати, що до ANSI, C не було поняття локалів; зрештою, я впевнений, що мова все ще не відповідає , але це поруч із пунктом) не означає, що Cназва мови походить від "ANSI C".
CVn

2
@ MichaelKjörling: Ви пропускаєте суть. Коли були введені локалі, "C" вже означав "ANSI C". Те, що це означало K&R C в минулому, не має значення.
MSalters

3

Наскільки я можу сказати, OS X використовує порядок зіставлення кодових точок у локаціях UTF-8, тож це виняток із деяких пунктів, зазначених у відповіді Стефана Шазеласа.

Це друкує 26 в OS X і 310 в Ubuntu:

export LC_ALL=en_US.UTF-8
printf %b $(printf '\\U%08x\\n' $(seq $((0x11)) $((0x10ffff))))|grep -a '[a-z]'|wc -l

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

export LC_ALL=en_US.UTF-8
for ((i=1;i<=0x1fffff;i++));do
  x=$(printf %04x $i)
  [[ $x = @(000a|d800|db7f|db80|dbff|dc00|dfff) ]]&&continue
  printf %b \\U$x\\n
done|sort -c

Код нижче нічого не друкує в OS X, що вказує на відсутність двох послідовних кодових точок (принаймні між U + 000B та U + D7FF), які мають однаковий порядок порівняння.

export LC_ALL=en_US.UTF-8
for ((i=0xb;i<=0xd7fe;i++));do
  printf %b $(printf '\\U%08x\\n' $((i+1)) $i)|sort -c 2>/dev/null&&echo $i
done

(Наведені вище приклади використовуються, %bоскільки printf \\U25призводять до помилки в zsh.)

Деякі символи та послідовності символів, що мають однаковий порядок порівняння в системах GNU, не мають однакового порядку упорядкування в OS X. Це друкується ① спочатку в OS X (використовуючи OS X sortабо GNU sort), але ② спочатку в Ubuntu:

export LC_ALL=en_US.UTF-8;printf %s\\n ② ①|sort

Це друкує три рядки в OS X (використовуючи OS X sortабо GNU sort), але один рядок в Ubuntu:

export LC_ALL=en_US.UTF-8;printf %b\\n \\u0d4c \\u0d57 \\u0d46\\u0d57|sort -u

Хтось знає, чому є така різниця?
1.61803,

3

Здається, що також LC_COLLATEкерує "алфавітним порядком", який використовується ls. Місцевість США буде сортована таким чином:

a.C
aFilename.C
aFilename.H
a.H

в основному ігнорування періодів. Ви можете віддати перевагу:

a.C
a.H
aFilename.C
aFilename.H

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

A.C
A.H
AFilename.C
a.C
a.H
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.