Por que o LC_MESSSAGES precisa ser exportado no bash homebrew do macOS para entrar em vigor?

4

No macOS, com o bash instalado do homebrew, notei que a configuração LC_MESSAGES parece ter algum efeito nas configurações de localidade do shell atual, mas as mensagens realmente não mudam até que LC_MESSAGES seja exportado:

Desativando LANG e LC_MESSAGES , recebo uma mensagem de erro em inglês como esperado:

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

A definição de LC_MESSAGES como um valor incorreto gera um erro sobre setlocale :

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

Então, algo muda quando eu defino LC_MESSAGES . Mas configurá-lo para um valor razoável não tem efeito:

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

Até exportar:

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

(Tudo isso vale para LANG também, parece.)

A seção do manual do Bash sobre Variáveis de Bash não diz LC_MESSAGES ou LANG precisa ser exportado (e a maioria das outras variáveis listadas não precisa ser exportada).

Por que isso acontece?

    
por JohnDoea 16.11.2018 / 07:42

2 respostas

3

Você está certo que atribuir as variáveis LC_* shell faz com que bash chame POSIX setlocale() para a categoria correspondente com o valor da variável, sejam eles exportados ou não. Para LANG , ele chama setlocale(LC_ALL, thevalue) seguido de setlocale(LC_*) novamente para toda a variável LC_* . Para LANGUAGE , não faz nada.

Agora, bash é o shell do projeto GNU. Para localização de texto, ele usa o GNU gettext , também conhecido como libintl . Ele ainda vem com sua própria versão empacotada com a fonte que você pode compilar em bash se você chamar o script configure com --with-included-gettext .

gettext procura traduções de mensagens em um banco de dados por idioma. Qual idioma é determinado pelo valor da categoria LC_MESSAGES , embora possa ser substituído pela variável de ambiente $LANGUAGE .

De acordo com a documentação do gettext, a chamada anterior para setlocale() deve ser a que determina o valor da categoria, mas há algumas complicações:

Para aplicativos multithread, não há atualmente nenhuma API padrão que o gettext possa usar para recuperar esse valor . bash não é um aplicativo multithread, mas até mesmo o que é um setlocale(category, NULL) retornos é implementação definida e na prática nem sempre utilizável .

Portanto, na prática, o gettext usa apenas setlocale() para recuperar o nome do idioma quando compilado como parte do GNU libc ou em um sistema onde o libc é o GNU libc (como o construído com bash with --with-included-gettext em um sistema GNU) porque sabe que pode confiar nele.

Em outros sistemas, ele usa getenv() para determinar a localidade, independentemente de como setlocale() foi chamado anteriormente, e é por isso que você está vendo esse comportamento.

Exportar essas variáveis é um trabalho fácil. Pode-se argumentar que, se não forem exportados, não farão parte do ambiente. POSIX não é muito claro sobre isso. Outra maneira de ver isso é que a tradução não é feita por bash , mas por um mecanismo de terceiros, assim como quando executamos outros comandos, precisamos usar variáveis de ambiente para passar a localidade informações entre os dois softwares (aqui bash e gettext ).

Agora, nos sistemas GNU, na verdade fica pior.

Como visto acima, o gettext está incluído no GNU libc. $LANGUAGE tem precedência sobre $LC_MESSAGE , mas $LANGUAGE não faz parte da API de locale POSIX, que é uma extensão sobre ela.

Assim, em um sistema GNU, o gettext usará setlocale(LC_MESSAGES, NULL) para obter o nome da categoria LC_MESSAGES, para LANGUAGE , ele sempre usa getenv() , LANGUAGE não é uma categoria de localidade.

O problema é que bash gerencia o ambiente por si só como parte de sua manipulação de variáveis, desconectada da matriz environ[] da libc. Ele possui seu próprio getenv() , que consulta sua própria versão do ambiente, mas quando gettext é construído como parte da libc e bash é vinculado dinamicamente dgettext() chama o getenv() da libc como isso é uma chamada interna dentro da libc, não a de bash , portanto, só obteremos o valor $LANGUAGE do tempo em que bash foi iniciado.

Portanto, nos sistemas GNU, a menos que bash tenha sido vinculado estaticamente ou criado com --with-included-gettext , qualquer alteração em $LANGUAGE será ignorada para as mensagens geradas por bash , independentemente de a variável ser exportada ou não. Em outros sistemas, tudo bem (contanto que $LANGUAGE seja exportado) já que o gettext não faz parte da libc, então ele chama bash ' getenv() .

No 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'

(mensagem em francês, o valor de $LANGUAGE no momento em que bash foi invocado, não em espanhol).

Na verdade, não é muito melhor com outras camadas.

zsh não está traduzido para outros idiomas, mas usa strerror() , que usa gettext internamente nos sistemas 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

O LANGUAGE=es foi honrado, mas veja como a segunda mensagem para ENOENT não foi exibida em inglês (presumivelmente em cache pelo gettext de alguma forma; esse cache deveria ter sido invalidado quando $LANGUAGE foi alterado, mas não foi o caso). / p>     

por 18.11.2018 / 18:53
0

Dê uma olhada em esta resposta para obter uma explicação da diferença entre uma variável de shell e uma variável de ambiente. Em essência:

Definindo uma variável de shell:

LANG=en_US.UTF-8

Definindo uma variável de ambiente:

export LANG=en_US.UTF-8

Você deseja definir variáveis de ambiente para o código do idioma, pois as variáveis do shell são privadas para o shell e não serão transmitidas para processos filhos.

    
por 16.11.2018 / 08:26