Bash

為什麼 LC_MESSSAGES 需要在 macOS homebrew bash 上導出才能生效?

  • November 19, 2018

在 macOS 上,從自製軟體安裝了 bash,我注意到該設置LC_MESSAGES似乎對目前 shell 的語言環境設置有一些影響,但LC_MESSAGES在導出之前消息實際上並沒有改變:

取消設置LANGand LC_MESSAGES,我按預期收到一條英文錯誤消息:

bash-4.4$ unset LANG LC_MESSAGES
bash-4.4$ if :;  fi
bash: syntax error near unexpected token `fi'

設置LC_MESSAGES為不正確的值會導致錯誤setlocale

bash-4.4$ LC_MESSAGES=foo
bash: warning: setlocale: LC_MESSAGES: cannot change locale (foo): No such file or directory

因此*,*當我設置LC_MESSAGES. 但是將其設置為合理的值沒有效果:

bash-4.4$ LC_MESSAGES=ja_JP.UTF-8
bash-4.4$ if :;  fi
bash: syntax error near unexpected token `fi'

直到我導出它:

bash-4.4$ export LC_MESSAGES
bash-4.4$ if :;  fi
bash: 予期しないトークン `fi' 周辺に構文エラーがあります

(所有這LANG一切似乎都適用。)

Bash 手冊中關於Bash 變數的部分沒有說LC_MESSAGESLANG必須導出(並且那裡列出的大多數其他變數不必導出)。

為什麼是這樣?

你是對的,分配LC_*shell變數確實會導致使用變數值bash呼叫setlocale()相應類別的POSIX,無論它們是否被導出。對於,它再次LANG呼叫所有變數。因為,它什麼也沒做。setlocale(LC_ALL, thevalue)``setlocale(LC_*)``LC_*``LANGUAGE

現在,bash是 GNU 項目的外殼。對於文本的本地化,它使用 GNU gettext,也稱為libintl. 它甚至帶有自己的版本,與原始碼捆綁在一起,bash如果您configure使用--with-included-gettext.

gettext在每種語言的數據庫中查找消息翻譯。它是哪種語言由LC_MESSAGES類別的值決定,但可以被$LANGUAGE環境變數覆蓋。

根據 gettext 文件,之前的呼叫setlocale()應該是確定類別值的呼叫,但有一些複雜性:

對於多執行緒應用程序,目前沒有標準 API 可用於 gettext 檢索該值bash不是多執行緒應用程序,但即使setlocale(category, NULL)返回的是實現定義的,實際上並不總是可用的。

因此,在實踐中,gettext 僅用於setlocale()在作為 GNU libc 的一部分建構或在 libc 是 GNU libc 的系統上檢索語言名稱(就像在 GNU 系統上建構的那樣bash--with-included-gettext,因為它知道它可以依賴它。

在其他系統上,它用於getenv()確定語言環境,而不管setlocale()之前是如何呼叫的,這就是您看到這種行為的原因。

導出這些變數很容易解決。有人可能會爭辯說,如果它們不出口,它們無論如何都不是環境的一部分。POSIX 對此不是很清楚。另一種看待它的方式是,翻譯不是由bash,而是由第三方機製完成的,所以就像在執行其他命令時一樣,我們需要使用環境變數在兩個軟體之間傳遞語言環境資訊(這里bashgettext)。

現在,在 GNU 系統上,它實際上變得更糟了。

如上所示,gettext 包含在 GNU libc 中。$LANGUAGE優先於$LC_MESSAGE$LANGUAGE不是 POSIX 語言環境 API 的一部分,這是它之上的擴展。

因此,在 GNU 系統上,gettext 將用於setlocale(LC_MESSAGES, NULL)獲取 LC_MESSAGES 類別的名稱,因為LANGUAGE它始終使用getenv(),LANGUAGE不是語言環境類別。

問題是它自己bash管理環境作為其變數處理的一部分,與 libc 的environ[]數組斷開連接。它確實有自己的getenv(),它確實查詢自己的環境版本,但是當gettext它作為 libc 的一部分建構並且bash動態連結時dgettext(),它會從 libc 呼叫,getenv()因為這是 libc 內的內部呼叫,而不是bash’’,所以將僅從開始$LANGUAGE時獲取值bash

因此,在 GNU 系統上,除非bash是靜態連結或使用 建構的,否則無論變數是否被導出,對 生成的消息的--with-included-gettext任何更改都$LANGUAGE將被忽略。bash在其他系統上,這很好(只要$LANGUAGE被導出),因為 gettext 不是 libc 的一部分,所以它確實呼叫bash’s getenv()

在 Debian 上:

$ LANGUAGE=fr bash -c 'LANGUAGE=es; eval fi'
bash: eval: ligne 0: erreur de syntaxe près du symbole inattendu « fi »
bash: eval: ligne 0: `fi'

(法語消息,呼叫$LANGUAGE了當時的值,而不是西班牙語)。bash

實際上,使用其他外殼並沒有好多少。

zsh沒有翻譯成其他語言,但確實使用strerror()gettext在 GNU 系統內部使用的語言:

$ LANGUAGE=fr zsh -c 'LANGUAGE=es; true</x; LANGUAGE=en; true</a; true < /etc/shadow'
zsh:1: no existe el archivo o el directorio: /x
zsh:1: no existe el archivo o el directorio: /a
zsh:1: permission denied: /etc/shadow

很榮幸,LANGUAGE=es但看到 ENOENT 的第二條消息沒有以英語顯示(大概是由 gettext 以某種方式記憶體;該記憶體在$LANGUAGE更改時應該已失效,但事實並非如此)。

引用自:https://unix.stackexchange.com/questions/482084