![为什么 macOS homebrew bash 上需要导出 LC_MESSSAGES 才能生效?](https://linux22.com/image/144259/%E4%B8%BA%E4%BB%80%E4%B9%88%20macOS%20homebrew%20bash%20%E4%B8%8A%E9%9C%80%E8%A6%81%E5%AF%BC%E5%87%BA%20LC_MESSSAGES%20%E6%89%8D%E8%83%BD%E7%94%9F%E6%95%88%EF%BC%9F.png)
在 macOS 上,从自制软件安装了 bash,我注意到该设置似乎对当前 shell 的区域设置有一些影响,但消息在导出LC_MESSAGES
之前实际上不会改变:LC_MESSAGES
取消设置LANG
和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_MESSAGES
或LANG
必须导出(并且那里列出的大多数其他变量不必导出)。
为什么是这样?
答案1
你是对的,分配LC_*
shell 变量确实会导致使用变量的值调用相应类别的bash
POSIX,无论它们是否导出。setlocale()
对于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()
应该是确定类别值的调用,但是存在一些复杂情况:
对于多线程应用程序,目前没有 gettext 可用于检索该值的标准 API。bash
不是多线程应用程序,但即使是什么setlocale(category, NULL)
回报是实施定义并且在实践中并不总是可用。
因此,在实践中,gettext 仅setlocale()
在作为 GNU libc 的一部分构建时或在 libc 是 GNU libc 的系统上(例如在 GNU 系统上使用bash
with构建的系统)上时才用于检索语言名称,因为它知道它可以依赖--with-included-gettext
它。
在其他系统上,它用于getenv()
确定区域设置,无论setlocale()
之前如何调用,这就是您看到该行为的原因。
导出这些变量是一个简单的解决方法。有人可能会说,如果它们不出口,它们就不是环境的一部分。 POSIX 对此不是很清楚。另一种看待它的方式是翻译不是由 完成的bash
,而是由第三方机制完成的,所以就像执行其他命令,我们需要使用环境变量在两个软件之间传递区域设置信息(此处bash
和gettext
)。
现在,在 GNU 系统上,情况实际上变得更糟。
如上所示,gettext 包含在 GNU libc 中。$LANGUAGE
优先于POSIX 语言环境 API,$LC_MESSAGE
但$LANGUAGE
不是其一部分,它是其之上的扩展。
因此,在 GNU 系统上, gettext 将用于setlocale(LC_MESSAGES, NULL)
获取 LC_MESSAGES 类别的名称,因为LANGUAGE
,它始终使用getenv()
,LANGUAGE
不是语言环境类别。
问题在于,bash
作为变量处理的一部分,它自己管理环境,与 libc 的environ[]
数组断开连接。它确实有自己的getenv()
,它会查询自己的环境版本,但是当它gettext
作为 libc 的一部分构建时,并且bash
动态链接时,会从 libcdgettext()
调用,因为这是 libc 内的内部调用,而不是's ,所以将只获取从开始时间开始的值。getenv()
bash
$LANGUAGE
bash
因此,在 GNU 系统上,除非bash
静态链接或使用 构建,否则对于 生成的消息,无论变量是否导出,都将忽略--with-included-gettext
对 的任何更改。在其他系统上,这很好(只要导出),因为 gettext 不是 libc 的一部分,因此它确实调用s 。$LANGUAGE
bash
$LANGUAGE
bash
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
调用的值,而不是西班牙语)。
事实上,与其他 shell 相比也好不了多少。
zsh
没有翻译成其他语言,但确实使用了,它在 GNU 系统内部strerror()
使用:gettext
$ 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
更改时应该已失效,但事实并非如此)。
答案2
看一眼这个答案解释 shell 变量和环境变量之间的差异。在本质上:
设置 shell 变量:
LANG=en_US.UTF-8
设置环境变量:
export LANG=en_US.UTF-8
您需要为语言环境设置环境变量,因为 shell 变量是 shell 私有的,不会传递给子进程。