Python による ESC[?1034h の表示について

2022年5月26日

Python@CentOS6 の標準出力と readline の絡みでハマったことがあったので、いろいろ調べた記録を残しておきます。

再現実験

テスト用に以下のようなスクリプトを作ってみます。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import readline

print raw_input("")
そして以下のような3種のテストを実施してみますと、
$ # ↓実行環境
$ cat /etc/centos-release && python --version
CentOS release 6.7 (Final)
Python 2.6.6
$
$ # ↓テスト1(OK, ターミナル出力)
$ ./test.py
aaaa(<-入力文字)
aaaa(<-出力文字)
$
$ # ↓テスト2(NG, リダイレクト)
$ ./test.py > /tmp/hoge
aaaa(<-入力文字)
$ xxd /tmp/hoge
0000000: 1b5b 3f31 3033 3468 6161 6161 0a         .[?1034haaaa.
$
$ # ↓テスト3(NG, パイプ)
$ ./test.py | xxd
aaaa(<-入力文字)
0000000: 1b5b 3f31 3033 3468 6161 6161 0a         .[?1034haaaa.
標準出力先がターミナル以外の場合は、想定していない文字列(エスケープシーケンス ESC[?1034h)が先頭に紛れ込んでしまってます。このゴミは実際は import readline を実行した直後に出力されているので、
$ python -c 'import readline' | xxd
0000000: 1b5b 3f31 3033 3468                      .[?1034h
だけでも動作の可否が確認できます。

対処1: import readline を使わない

readline を使わない場合は input()raw_input() による文字入力時にカーソルキーが効かないなどのデメリットがありますが、それが問題にならないなら import readline を省いてしまったほうがてっとり早いです。

対処2: 環境変数 TERM を一時的に変えてみる

どうしても readline を使いたいのならば、一時的に環境変数 TERM を変更する、というテもあります。環境変数 TERM の中身が xterm 以外だとほぼ大丈夫なようです。
$ echo $TERM
xterm
$ python -c 'import readline' | xxd
0000000: 1b5b 3f31 3033 3468                      .[?1034h
$ TERM=vt100 python -c 'import readline' | xxd
$ TERM= python -c 'import readline' | xxd

他の対処法は?

上記のいずれかで対処はできるのですが、他に方法がないものかどうか調べてみました。 この問題は Python 2.7.9 にて初めて着手されたようです。
+++++++++++ Python News +++++++++++ What's New in Python 2.7.9 release candidate 1? =============================================== *Release date: 2014-11-25* (略) - Issue #19884: readline: Disable the meta modifier key if stdout is not a terminal to not write the ANSI sequence "\033[1034h" into stdout. This sequence is used on some terminal (ex: TERM=xterm-256color") to enable support of 8 bit characters.
CentOS6 では centos-sclo-rh リポジトリから Python 2.7.13 が提供されているので、それなら大丈夫だろうと思って試してみると・・・
$ scl enable python27 bash
$ cat /etc/centos-release && python --version
CentOS release 6.7 (Final)
Python 2.7.13
$
$ python -c 'import readline' | xxd
0000000: 1b5b 3f31 3033 3468                      .[?1034h
予想に反して、何故か相変わらずゴミが出てしまいます。

readline のバージョンで挙動が変わる

Python 2.7.9 の対策は、readline の初期化時に 標準出力先がパイプやリダイレクトの場合は enable-meta-key の設定値を off にする というものですが、readline のバージョンによっては enable-meta-key 自体が実装されてなかったりします。これが初めて実装されたのは readline 6.1 のようです。
readline/NEWS This is a terse description of the new features added to readline-6.1 since the release of readline-6.0. 1. New Features in Readline (略) i. New bindable variable: enable-meta-key. Controls whether or not readline sends the smm/rmm sequences if the terminal indicates it has a meta key that enables eight-bit characters.
CentOS6 に入っている readline のバージョンは 6.0 になので、readline のバージョンを上げない限りどの Python のバージョンでもゴミが表示されることになりそうです。
$ ldconfig -v | grep libreadline
        libreadline.so.6 -> libreadline.so.6.0

readline をバージョンアップしてみる

果たして readline が 6.1 であれば解決するのでしょうか? ということで、実験してみます。 ビルドの流れはざっと以下のとおりです。今回は共有ライブラリのみで充分なので、configure--disable-static (静的ライブラリを作成しない)を付けています。
$ # ↓ダウンロード&ビルド
$ wget http://git.savannah.gnu.org/cgit/readline.git/snapshot/readline-86cfd01f1d547422a1fb0365719491a355847dc0.tar.gz
$ tar xvf readline-86cfd01f1d547422a1fb0365719491a355847dc0.tar.gz
$ cd readline-86cfd01f1d547422a1fb0365719491a355847dc0
$ ./configure --disable-static
$ make
$ sudo make install
$ 
$ # ↓ライブラリの読み込み先を一時的に追加して反映
$ sudo sh -c 'echo /usr/local/lib > /etc/ld.so.conf.d/test.conf'
$ ldconfig -v | grep -E '(^/|libreadline)'
/usr/local/lib:
	libreadline.so.6 -> libreadline.so.6.1
/lib64:
	libreadline.so.6 -> libreadline.so.6.0
(略)
$
$ # ↓Python2.6 でテスト (NG)
$ python --version && python -c 'import readline' | xxd
Python 2.6.6
0000000: 1b5b 3f31 3033 3468                      .[?1034h
$
$ # ↓Python2.7 でテスト (OK)
$ scl enable python27 bash
$ python --version && python -c 'import readline' | xxd
Python 2.7.13
$ 
$ # ↓Python2.6 の環境に戻す
$ exit 
$ 
確かに readline 6.1 にすると、Python 2.7.13 ではゴミが発生しないことが確認できました。 では、readline を元のバージョンに戻しておきます。
$ 
$ # ↓ライブラリの読み込み先を元に戻して反映
$ sudo rm /etc/ld.so.conf.d/test.conf
$ ldconfig -v | grep -E '(^/|libreadline)'
/lib64:
	libreadline.so.6 -> libreadline.so.6.0
(略)
$
$ # ↓readline 6.1 のアンインストール
$ sudo make uninstall
$
$ # ↓readline 6.0 の下でもう1回ずつテスト
$ python --version && python -c 'import readline' | xxd
Python 2.6.6
0000000: 1b5b 3f31 3033 3468                      .[?1034h
$
$ scl enable python27 bash
$ python --version && python -c 'import readline' | xxd
Python 2.7.13
0000000: 1b5b 3f31 3033 3468                      .[?1034h
$

まとめ

Python の import readline によるゴミ表示の(おそらく)正当な対策は
  • Python は 2.7.9 以上
  • readline は 6.1 以上
の両方が必要、ということだろうと思います。 ・・・が、CentOS7 では
$ cat /etc/centos-release && python --version
CentOS Linux release 7.3.1611 (Core) 
Python 2.7.5
$ ldconfig -v | grep -E '(^/|libreadline)'
/lib64:
	libreadline.so.6 -> libreadline.so.6.2
(略)
$
$ python -c 'import readline' | xxd
$
readline のバージョンこそ 6.2 ですが、Python は 2.7.9 未満でありながら正常に動いてしまってます。 これについてはまた別記事で。

centos6,python,readline

Posted by plkl