“LC_ALL=C”有什麼作用?
在類 Unix 系統中的
C
價值是什麼?LC_ALL
我知道它在所有方面都強制使用相同的語言環境,但是有什麼作用
C
呢?
它強制應用程序使用預設語言進行輸出:
$ 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
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 執行命令,以避免使用者的設置干擾您的腳本。例如,如果你想匹配從to
[a-z]
的 26 個 ASCII 字元,你必須設置.a``z``LC_ALL=C
在 GNU 系統上,
LC_ALL=C
和LC_ALL=POSIX
(或LC_MESSAGES=C|POSIX
) override$LANGUAGE
,而LC_ALL=anything-else
不會。您通常需要設置的幾種情況
LC_ALL=C
:
sort -u
或sort ... | uniq...
。在 C 以外的許多語言環境中,在某些系統(尤其是 GNU 系統)上,某些字元具有相同的排序順序。sort -u
不報告唯一行,而是每組具有相同排序順序的行之一。因此,如果您確實想要唯一的行,則需要一個字元為字節且所有字元具有不同排序順序的語言環境(C
語言環境保證)。- 這同樣適用於符合 POSIX 的
=
運算符expr
或符合 POSIX 的==
運算符awk
(在這方面不是 POSIX)mawk
,gawk
它們不檢查兩個字元串是否相同,但它們是否排序相同。- 字元範圍如
grep
. 如果您要匹配使用者語言中的字母,請使用grep '[[:alpha:]]'
並且不要修改LC_ALL
. 但是如果你想匹配a-zA-Z
ASCII 字元,你需要LC_ALL=C grep '[[:alpha:]]'
或者LC_ALL=C grep '[a-zA-Z]'
²。匹配前後[a-z]
排序的字元(儘管有許多 API 比這更複雜)。在其他語言環境中,您通常不知道它們是什麼。例如,某些語言環境會忽略排序的大小寫,因此在某些 API (如模式)中,可能包括or 。在許多 UTF-8 語言環境中(包括在大多數係統上),將包括帶有變音符號的拉丁字母 from to但不包括(因為a``z``[a-z]``bash``[B-Z]``[A-Y]``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 兼容(幾乎所有字元集都是)。例如,如果您在 UTF-8 語言環境中並且輸入以單字節 8 位字元集(如 iso8859-15)編碼,則
grep '<.*>'
查找包含<
,>
對的行將不起作用。那是因為.
在 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
這也適用於不區分大小寫的比較(如 in
grep -i
)和大小寫轉換(awk
’stoupper()
,dd conv=ucase
…)。例如:grep -i i
不保證
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:]]'
或者
LC_ALL=C grep '[a-zA-Z]'
將是錯誤的,因為在該字元集中(以及許多其他字元集中,但自 UTF-8 出現以來幾乎不使用),許多字元包含對應於 A-Za-z 字元的 ASCII 編碼的字節。例如,所有
A䨝䰲丕乙乜你再劀劈呸哻唥唧噀噦嚳坽
(以及更多)都包含A
.䨝
是 0x96 0x41,和A
ASCII 一樣是 0x41。所以我們LC_ALL=C grep '[a-zA-Z]'
會匹配那些包含這些字元的行,因為它會誤解那些字節序列。LC_COLLATE=C grep '[A-Za-z]'
會起作用,但前提
LC_ALL
是沒有另外設置(這將覆蓋LC_COLLATE
)。所以你最終可能不得不這樣做:grep '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]'
如果您想在以區域設置編碼的文件中查找英文字母。
³ 有些人會爭辯說,現在 Unicode 程式碼點(以及編碼/解碼 UTF-8 數據的庫)已被任意限制為程式碼點 U+0000 到 U+10FFFF(0xD800 到 0xDFFF 除外),因此現在它是 1 到 4 個字節從 U+7FFFFFFF 下調以適應 UTF-16 編碼,但一些應用程序仍將愉快地編碼/解碼 6 字節 UTF-8 序列(包括落在 0xD800 .. 0xDFFF 範圍內的序列)。