“LC_ALL=C”有什么作用?

jcu*_*bic 409 environment-variables locale

在类 Unix 系统中的C值是LC_ALL什么?

我知道它对所有方面都强制使用相同的语言环境,但是有什么作用C呢?

Sté*_*las 428

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=
Run Code Online (Sandbox Code Playgroud)

例如,我可以覆盖单个设置:

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

或者:

$ LC_MONETARY=fr_FR.UTF-8 locale currency_symbol
€
Run Code Online (Sandbox Code Playgroud)

或者用 LC_ALL 覆盖所有内容。

$ LC_ALL=C LANG=fr_FR.UTF-8 LC_MESSAGES=fr_FR.UTF-8 cat /
cat: /: Is a directory
Run Code Online (Sandbox Code Playgroud)

在脚本中,如果您想强制进行特定设置,因为您不知道用户强制进行了哪些设置(也可能是 LC_ALL),最好、最安全且通常唯一的选择是强制 LC_ALL。

C区域是指是最简单的语言环境的特殊区域。您也可以说其他语言环境适用于人类,而 C 语言环境适用于计算机。在 C 语言环境中,字符是单字节,字符集是 ASCII(嗯,不是必需的,但实际上在我们大多数人都会使用的系统中),排序顺序基于字节值¹,语言通常是美国英语(尽管对于应用程序消息(与系统库中的月份或日期名称或消息相对),由应用程序作者自行决定)并且未定义货币符号等内容。

在某些系统上,POSIX 语言环境存在差异,例如未定义非 ASCII 字符的排序顺序。

您通常使用 LC_ALL=C 运行命令以避免用户设置干扰您的脚本。例如,如果要[a-z]匹配 26 个 ASCII 字符az,则必须设置LC_ALL=C.

在 GNU 系统上,LC_ALL=CLC_ALL=POSIX(或LC_MESSAGES=C|POSIX) override $LANGUAGE,而LC_ALL=anything-else不会。

您通常需要设置的几种情况LC_ALL=C

  • sort -usort ... | uniq...。在 C 以外的许多语言环境中,在某些系统(特别是 GNU 系统)上,某些字符具有相同的排序顺序sort -u不报告唯一的行,而是报告具有相同排序顺序的每组行中的一个。因此,如果您确实需要唯一的行,则需要一个字符为字节且所有字符具有不同排序顺序的C语言环境(语言环境保证这一点)。

  • 这同样适用于=符合POSIX标准的运营商expr==与POSIX兼容的操作awk秒(mawkgawk不POSIX在这方面),不检查两个字符串是否相同,但他们是否排序相同。

  • 字符范围如 in grep。如果您想匹配用户语言中的字母,请使用grep '[[:alpha:]]'而不是修改LC_ALL. 但是如果要匹配a-zA-ZASCII 字符,则需要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]将包括拉丁字母 from atoy和变音符号,但不包括那些z(因为z在他们之前排序)我无法想象这会是你想要的(你为什么要包括é而不是??)。

  • 中的浮点运算ksh93ksh93尊重中的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
    
    Run Code Online (Sandbox Code Playgroud)

然后你需要这样的东西:

    #! /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
Run Code Online (Sandbox Code Playgroud)

附带说明:,小数点分隔符与,算术运算符冲突,这可能会导致更多混淆。

  • 当您需要字符为字节时。如今,大多数语言环境都基于 UTF-8,这意味着字符可以占用 1 到 6 个字节³。当使用文本实用程序处理打算是字节的数据时,您需要设置 LC_ALL=C。它还将显着提高性能,因为解析 UTF-8 数据是有成本的。

  • 上一点的推论:在处理文本时,您不知道输入是用什么字符集写入的,但可以假设它与 ASCII 兼容(几乎所有字符集都是如此)。例如,grep '<.*>'要查找包含<, >pair 的行,如果您在 UTF-8 语言环境中并且输入以单字节 8 位字符集(如 iso8859-15)编码,则该行将不起作用。那是因为.在 iso8859-15 中仅匹配字符和非 ASCII 字符可能不会在 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
    
    Run Code Online (Sandbox Code Playgroud)

这也适用于不区分大小写的比较(如 in grep -i)和大小写转换(awk's toupper()dd conv=ucase...)。例如:

    grep -i i
Run Code Online (Sandbox Code Playgroud)

不能保证I在用户的语言环境中匹配。在比如一些土耳其语言环境,它没有为大写的i?(注意点)有和小写I?(注漏点)。


笔记

¹ 再次,仅在基于 ASCII 的系统(绝大多数系统)上。POSIX 要求 C 语言环境的整理顺序与 ASCII 字符集中的字符顺序相同,即使在不允许在 C 语言环境中进行strcoll()===strcmp()优化的EBCDIC 系统上也是如此。


² 根据文本的编码,这不一定是正确的做法。这对 UTF-8 或单字节字符集(如 iso-8859-1)有效,但不一定适用于非 UTF-8 多字节字符集。

例如,如果您在zh_HK.big5hkscs区域设置中(香港,使用 BIG5 中文字符编码的香港变体),并且您想在以该字符集编码的文件中查找英文字母,请执行以下任一操作:

LC_ALL=C grep '[[:alpha:]]'
Run Code Online (Sandbox Code Playgroud)

或者

LC_ALL=C grep '[a-zA-Z]'
Run Code Online (Sandbox Code Playgroud)

将是错误的,因为在该字符集中(以及许多其他字符集,但自从 UTF-8 出现后就很少使用了),很多字符包含与 A-Za-z 字符的 ASCII 编码相对应的字节。例如,所有A?????????????????(以及更多)都包含A. ?是 0x96 0x41,和AASCII 一样是 0x41。所以我们LC_ALL=C grep '[a-zA-Z]'会匹配那些包含这些字符的行,因为它会误解这些字节序列。

LC_COLLATE=C grep '[A-Za-z]'
Run Code Online (Sandbox Code Playgroud)

会起作用,但前提LC_ALL是没有另外设置(这会覆盖LC_COLLATE)。所以你最终可能不得不这样做:

grep '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]'
Run Code Online (Sandbox Code Playgroud)

如果您想在以区域设置编码的文件中查找英文字母。


³ 有些人会争辩说,现在 Unicode 代码点(以及编码/解码 UTF-8 数据的库)被任意限制为代码点 U+0000 到 U+10FFFF(不包括 0xD800 到 0xDFFF),现在它是 1 到 4 个字节从 U+7FFFFFFF 向下以适应 UTF-16 编码,但一些应用程序仍然可以愉快地编码/解码 6 字节 UTF-8 序列(包括落在 0xD800 .. 0xDFFF 范围内的序列)。

  • +1,这是最好的答案(用于指出覆盖等)。但缺少伊格纳西奥回答的(好的)例子^^ (16认同)
  • @AndrewJanke,是的。请注意,可移植字符集并不意味着 ASCII 或 0-127。关于“C”语言环境字符集的属性是什么,奥斯汀组邮件列表上有很多讨论,普遍的共识(这将在下一个规范中澄清)是该字符集将是单一的-字节,并包含完整的 8 位范围(具有此处描述的属性)。与此同时,是的,可能会有一些分歧(作为错误或因为规范不够明确)。在任何情况下 LC_ALL=C 是最接近你能得到理智行为的。 (3认同)
  • @12431234123412341234123,原来的UTF-8编码最多覆盖U+7FFFFFFFF(6个字节,还有一些扩展可以达到13个字节,比如`perl`的`\x{7FFFFFFFFFFFFFFFF}`),而Unicode的范围代码点被任意限制为 U+10FFFF(由于 UTF-16 设计限制),一些工具仍然可以识别/生成 6 字节字符。这就是我所说的 6 字节字符的意思。在 Unix 语义中,一个字符是一个代码点。您的_多个代码点“字符”_ 通常被称为字形簇,以消除字符的歧义。 (3认同)
  • 一个小问题:`C` 语言环境只需要支持“可移植字符集”(ASCII 0-127),字符 &gt; 127 的行为[技术上未指定](http://pubs.opengroup.org/onlinepubs /9699919799/basedefs/V1_chap07.html#tag_07_02)。在实践中,大多数程序会将它们视为不透明数据并按照您的描述传递它们。但并非全部:特别是,如果在 `C` 语言环境中运行,Ruby 可能会阻塞字节大于 127 的字符数据。老实说,我不知道这在技术上是否“符合”,但我们已经[在野外看到过](https://github.com/Homebrew/homebrew/issues/41632)。 (2认同)
  • @UlysseBN,这与 bash 无关。这是关于语言环境的定义。有关更新的示例,请参阅 https://lists.gnu.org/archive/html/bug-bash/2019-12/msg00098.html。我曾经使用①②③④⑤作为引人注目的例子(例如参见[“sort -u”和“sort | uniq”之间有什么区别?](//unix.stackexchange.com/a/76095)),但他们已经现在已修复。仍然在当前的 GNU 语言环境中(至少从 glibc 2.30 开始),超过 99% 的字符没有定义的顺序。看到那些 (2认同)

Ign*_*ams 266

它强制应用程序使用默认语言进行输出:

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

$ LC_ALL=C man
What manual page do you want?
Run Code Online (Sandbox Code Playgroud)

并强制按字节排序:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 好的例子+1,但缺少斯蒂芬的回答中的重要信息...... (30认同)
  • _默认语言_是什么意思? (12认同)
  • 是的,我理解作者可以随心所欲,包括不做它在罐头上所说的。事情是。美国英语是唯一可以用 LC_ALL=C 中的字符集正确表示的语言,LC_ALL=C (LC_COLLATE) 中的排序顺序是唯一有意义的语言,LC_ALL=C (LC_TIME) 具有英文月份和日期名称。我从未见过 LC_ALL=C 以与 LC_ALL=en LANGUAGE=en 不同的语言返回消息的应用程序。如果情况并非如此,我是否有权报告针对程序的错误?(不是在这里谈论未翻译成英文的应用程序)。 (3认同)
  • 问题是“美国英语是唯一可以用 LC_ALL=C 中的字符集正确表示的语言”。这通常只在 C/C++ 程序中使用窄字符时才成立,但即便如此,也有例外(因为有几种语言只使用 ASCII 中的字符和符号)。当默认语言不是英语时报告错误会让你看起来......偏执。 (3认同)
  • 请注意,在英语(意味着 LANG=en_US.utf8)中,消息可以(并且应该)使用 unicode 字符,例如“”来引用字符串。而在 LANG=C 中,它只有 ASCII 字符(双引号、反引号和撇号)。 (3认同)

Edw*_*hen 10

C是默认语言环境,“POSIX”是“C”的别名。我猜“C”是从 ANSI-C 派生出来的。也许 ANSI-C 定义了“POSIX”语言环境。

  • @MichaelKjörling:你没有抓住重点。引入语言环境时,“C”已经表示“ANSI C”。这意味着过去的 K&amp;R C 无关紧要。 (4认同)

nis*_*ama 6

据我所知,OS X 在 UTF-8 语言环境中使用代码点整理顺序,因此它是 Stéphane Chazelas 的答案中提到的一些要点的一个例外。

这在 OS X 中打印 26,在 Ubuntu 中打印 310:

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

下面的代码在 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
Run Code Online (Sandbox Code Playgroud)

下面的代码在 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
Run Code Online (Sandbox Code Playgroud)

(上面的例子使用%b因为printf \\U25在 zsh 中导致错误。)

在 GNU 系统中具有相同整理顺序的某些字符和字符序列在 OS X 中没有相同的整理顺序。这会打印 ? 首先在 OS X 中(使用 OS Xsort或 GNU sort)但是?首先在 Ubuntu 中:

export LC_ALL=en_US.UTF-8;printf %s\\n ? ?|sort
Run Code Online (Sandbox Code Playgroud)

这会在 OS X 中打印三行(使用 OS Xsort或 GNU sort),但在 Ubuntu 中打印一行:

export LC_ALL=en_US.UTF-8;printf %b\\n \\u0d4c \\u0d57 \\u0d46\\u0d57|sort -u
Run Code Online (Sandbox Code Playgroud)


小智 5

似乎也LC_COLLATE控制了 ls 使用的“字母顺序”。美国语言环境将按如下方式排序:

a.C
aFilename.C
aFilename.H
a.H
Run Code Online (Sandbox Code Playgroud)

基本上忽略了时期。您可能更喜欢:

a.C
a.H
aFilename.C
aFilename.H
Run Code Online (Sandbox Code Playgroud)

我当然知道。设置LC_COLLATEC实现这一点。请注意,它还会在所有大写字母之后对小写字母进行排序:

A.C
A.H
AFilename.C
a.C
a.H
Run Code Online (Sandbox Code Playgroud)