为什么 macOS homebrew bash 上需要导出 LC_MESSSAGES 才能生效?

为什么 macOS homebrew bash 上需要导出 LC_MESSSAGES 才能生效?

在 macOS 上,从自制软件安装了 bash,我注意到该设置似乎对当前 shell 的区域设置有一些影响,但消息在导出LC_MESSAGES之前实际上不会改变:LC_MESSAGES

取消设置LANGLC_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必须导出(并且那里列出的大多数其他变量不必导出)。

为什么是这样?

答案1

你是对的,分配LC_*shell 变量确实会导致使用变量的值调用相应类别的bashPOSIX,无论它们是否导出。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 可用于检索该值的标准 APIbash不是多线程应用程序,但即使是什么setlocale(category, NULL)回报是实施定义并且在实践中并不总是可用

因此,在实践中,gettext 仅setlocale()在作为 GNU libc 的一部分构建时或在 libc 是 GNU libc 的系统上(例如在 GNU 系统上使用bashwith构建的系统)上时才用于检索语言名称,因为它知道它可以依赖--with-included-gettext它。

在其他系统上,它用于getenv()确定区域设置,无论setlocale()之前如何调用,这就是您看到该行为的原因。

导出这些变量是一个简单的解决方法。有人可能会说,如果它们不出口,它们就不是环境的一部分。 POSIX 对此不是很清楚。另一种看待它的方式是翻译不是由 完成的bash,而是由第三方机制完成的,所以就像执行其他命令,我们需要使用环境变量在两个软件之间传递区域设置信息(此处bashgettext)。

现在,在 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$LANGUAGEbash

因此,在 GNU 系统上,除非bash静态链接或使用 构建,否则对于 生成的消息,无论变量是否导出,都将忽略--with-included-gettext对 的任何更改。在其他系统上,这很好(只要导出),因为 gettext 不是 libc 的一部分,因此它确实调用s 。$LANGUAGEbash$LANGUAGEbashgetenv()

在 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 私有的,不会传递给子进程。

相关内容