SSH远程执行Python3 Error: UnicodeEncodeError: 'ascii' codec

背景

最近项目上需要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"));
原文地址:https://www.cnblogs.com/nm666/p/15660884.html