背景
最近项目上需要SSH远程到Centos7容器上执行Python代码,Python版本为3.6。在执行打开中文名称的文件时,在Centos7容器上本地执行open('狼来了.txt')
不会有任何问题,但当通过SSH连接过去后发现open()方法报如下错误:
UnicodeEncodeError :'ascii' codec can't encode characters in position 0-2
问题出现在open的时候将 '狼来了.txt'(中文文件名称)作为参数按照ascii编码方式进行encode,而我们知道ascii编码是只有128位包含数字、大小写字母和一些特殊符号,是不包含中文汉字的。【至于为什么会进行encode,本文不做介绍】
所以将代码做如下更改便可以正常运行:
open('狼来了.txt'.encode('utf8'))
因为这里我们指定了了utf-8
的编码格式(当然,需要运行环境支持)。
找到问题的本质,我们就可以接着来看SSH的问题。
语言编码环镜
Python运行容器环境
Python运行环境容器里通过locale
以及locale -a
命令来查看发现语言编码环境如下所示:
[root@pyrun-test-69d4d45d79-mqg6n /]# locale -a
C
en_US.utf8
POSIX
[root@pyrun-test-69d4d45d79-mqg6n /]# locale
LANG=en_US.utf8
LC_CTYPE="en_US.utf8"
LC_NUMERIC="en_US.utf8"
LC_TIME="en_US.utf8"
LC_COLLATE="en_US.utf8"
LC_MONETARY="en_US.utf8"
LC_MESSAGES="en_US.utf8"
LC_PAPER="en_US.utf8"
LC_NAME="en_US.utf8"
LC_ADDRESS="en_US.utf8"
LC_TELEPHONE="en_US.utf8"
LC_MEASUREMENT="en_US.utf8"
LC_IDENTIFICATION="en_US.utf8"
LC_ALL=
SSH客户端的语言编码环境
而SSH客户端的语言编码环境如下:
root@coding-editor-test-8c6cdfdd8-9tpcl:/app# locale -a
C
C.UTF-8
POSIX
root@coding-editor-test-8c6cdfdd8-9tpcl:/app# locale
LANG=C.UTF-8
LANGUAGE=
LC_CTYPE="C.UTF-8"
LC_NUMERIC="C.UTF-8"
LC_TIME="C.UTF-8"
LC_COLLATE="C.UTF-8"
LC_MONETARY="C.UTF-8"
LC_MESSAGES="C.UTF-8"
LC_PAPER="C.UTF-8"
LC_NAME="C.UTF-8"
LC_ADDRESS="C.UTF-8"
LC_TELEPHONE="C.UTF-8"
LC_MEASUREMENT="C.UTF-8"
LC_IDENTIFICATION="C.UTF-8"
LC_ALL=
配置语言编码环境
通过对比发现SSH客户端的编码环境是C.UTF-8
,而Python运行环境为en_US.utf8
(ps : C表示的是ascii编码, en_US.utf8 与 zh_CN.utf8 都是包含汉字的)
首先想到的是可能是SSH客户端影响了Python运行时的默认encode方式,那么我们把SSH客户端的环境设置成和Python运行环境一样的『这里客户端为Ubuntu』:
#!/bin/sh
# 1.设置语言为en_US_UTF-8
echo -e 'LANG="en_US_UTF-8"\nLANGUAGE="en_US:en"' >> /etc/default/locale
# 生效配置
source /etc/default/locale
# 2.如果缺少en_US_UTF-8语言包 安装locales工具并设置en_US_UTF-8
apt-get install --no-install-recommends -y locales
locale-gen en_US.UTF-8
localedef -v -c -i en_US -f UTF-8 en_US.UTF-8
设置完成后再尝试SSH到Python运行环境容器,发现open('狼来了.txt')
运行不再报错。
那么问题来了,为什么SSH会用客户端的编码运行环境
SSH ENV机制探究
cat /etc/ssh/ssh_config
在ssh的配置文件中发现如下内容:
上面配置,会将本地的语言环境SendEnv到Python运行环境,那么我们就不需要保证客户端和Python运行环境的语言环境一致了。
只需要将配置修改为:
SendEnv LANG en_US.utf8
Mina SSH 的设置
由于项目是由Java通过Mina SSH 调用Python运行环境终端去执行的代码,而非直接用的SSH,因此对/etc/ssh/ssh_config
的修改并不能解决问题。
通过对Mina的观察发现,再创建ChannelShell的时候可以传递参数,源码接口如下:
/**
* Create a channel to start a shell using specific PTY settings and/or environment.
*
* @param ptyConfig The PTY configuration to use - if {@code null} then internal defaults are used
* @param env Extra environment configuration to be transmitted to the server - ignored if
* {@code null}/empty.
* @return The created {@link ChannelShell}
* @throws IOException If failed to create the requested channel
*/
ChannelShell createShellChannel(
PtyChannelConfigurationHolder ptyConfig, Map<String, ?> env)
throws IOException;
那么在创建ChannelShell的时候可以这样写,将语言编码配置:
shellChannel = session.createShellChannel(new PtyChannelConfiguration(), Map.of("LANG","en_US.utf8"));