いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Pythonメモ-16 (paramiko, ssh, UnicodeDecodeError, stdoutとstderrがテキストモード, monkey_patch関数)

概要

python で sshとかsftp処理しようとすると大抵出て来る paramiko モジュールさん。

超便利なのですが、sshでやり取りする処理を書いて

標準出力を受け取ろうとすると、UnicodeDecodeError が発生するときがあります。

なんでなのかというと、paramiko内部処理の exec_command 関数にて

stdin のみがバイナリモードで、stdoutとstderrがテキストモードで処理しようとしているためです。

たまたま、euc-jpなlinuxマシン用に処理書いててエラーが発生したので知りました。

で、ソースみてみると、モードの部分はパラメータにもなっていないので、ネットで情報探してみると

以下を発見。

Monkey patch for paramiko issue 291 · GitHub

なるほど。置き換えればいいのねってことで、以下自分用の処理つくったのでメモメモ。

サンプル

# coding: utf-8
"""
paramiko の SSHClient.exec_command は内部で stdin のみ binary-mode で
処理しているが、stdout と stderr はテキストモードで処理している。

そのため、euc-jp な環境で動かすと UnicodeDecodeError が発生してしまう。

それを防ぐために、stdout, stderr を binary-mode で処理するパッチ関数を以下に定義している。

以下の情報を参考にした。
  https://gist.github.com/smurn/4d45a51b3a571fa0d35d
"""
import paramiko


def monkey_patch():
    paramiko.SSHClient.exec_command = _patched_exec_command


def _patched_exec_command(
        self,
        command: str,
        bufsize: int = -1,
        timeout: int = None,
        get_pty: bool = False,
        environment: dict = None,
) -> tuple:
    """
    元の exec_command の処理そのままで stdout, stderr を binary-mode で処理します。
    """
    chan = self._transport.open_session(timeout=timeout)
    if get_pty:
        chan.get_pty()
    chan.settimeout(timeout)
    if environment:
        chan.update_environment(environment)
    chan.exec_command(command)
    stdin = chan.makefile('wb', bufsize)
    stdout = chan.makefile('rb', bufsize)
    stderr = chan.makefile_stderr('rb', bufsize)
    return stdin, stdout, stderr

あとは、paramiko使う前にmonkey_patchします。

# coding: utf-8

import paramiko
import trypython.extlib.paramiko_monkeypatch as paramiko_patch

paramiko_patch.monkey_patch()

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect(hostname='xxx.xxx.xxx.xxx', username='user', password='passwd')
sin, sout, serr = client.exec_command('ls -l')

for x in sout:
    print(x.decode('euc-jp'), end='')

client.close()

参考情報

Monkey patch for paramiko issue 291 · GitHub

UnicodeDecodeError: 'utf8' codec can't decode byte 0x83 in position 20: invalid start byte · Issue #707 · paramiko/paramiko · GitHub

paramiko/client.py at master · paramiko/paramiko · GitHub

Welcome to Paramiko! — Paramiko documentation


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。