この記事は、上智大学エレクトロニクス研究部Advent Calendar第4日目の記事です。
エレラボでは、Pythonユーザーが多く、僕のそのうちの一人です。そんな愛してやまないPythonですが、最近 ~/.python_history
の形式がおかしくなってしまっていることに気が付きました。これは一大事です。なんとかして解決しないと…!
事象
MacOS Catalinaで ~/.python_history
ファイルの空白文字が \040
に、 \
が \134
に、 水平タブ が \011
置き換わってしまう。
また、 ~/.python_history
の先頭に _HiStOrY_V2_
という謎のおまじないが書き込まれる。
❯ head -n 5 ~/.python_history
_HiStOrY_V2_
import\040secrets
[secrets.token_urlsafe(16)\040for\040_\040in\040range(10)]
[secrets.token_urlsafe(12)\040for\040_\040in\040range(10)]
[secrets.token_urlsafe(8)\040for\040_\040in\040range(10)]
対処方法
GNU版の readline
をリンクできるように設定した状態で Pythonのビルドを行う。
export CFLAGS="-I$(brew --prefix readline)/include -I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/usr/include"
export LDFLAGS="-L$(brew --prefix readline)/lib -L$(brew --prefix openssl)/lib"
# Pythonのビルド
(上記は Homebrew
を用いて GNU版の(以下純正と表記) readline
をインストールした場合)
書き換わってしまった ~/.python_history
は、以下のように行頭を削除し、 エスケープされた文字を元の文字に置き換える。
cp ~/.python_history ~/.python_history.bak && cat ~/.python_history.bak | sed -e '1d' | unvis > ~/.python_history
ただし、行頭の _HiStOrY_V2_
を消すと、原因となっているPythonインタプリタを実行し、終了した時点ですべての履歴を消される。原因となっているPythonインタプリタと readline
がリンクされたPythonインタプリタを共存して使いたい場合、行頭を残すために以下のように編集すると良いだろう。
cp ~/.python_history ~/.python_history.bak && cat ~/.python_history.bak | unvis > ~/.python_history
/usr/bin/python3
を使わないようにする
xcode-select --install
によってインストールされるPythonには、 libedit
がリンクされている。readline
がリンクされているPythonインタプリタを常用する場合、/usr/bin/python3
を使わないことをおすすめする。
再現手順
以下のように、 LDFLAGS
を設定せずにPythonのビルドを行うと、 XCode SDKに含まれる readline
のフリをした libedit
がPythonの readline
モジュールとしてリンクされてしまう。
wget https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tgz
tar -xvzof Python-3.8.1.tgz
mv Python-3.8.1 Python-3.8.1.tgz ~/Downloads
cd Python-3.8.1
./configure --enable-optimizations --prefix=$HOME/Downloads
make
make test # この時点で `~/.python_history` が書き換わる
./python.exe
>>> import secrets
>>> [secrets.token_urlsafe(8) for _ in range(10)]
このようにlibedit
がリンクされたPythonを開き、コマンドを実行すると、 空白文字が \040
に置き換わってしまうことがわかる。
❯ tail -n 2 ~/.python_history
import\040secrets
[secrets.token_urlsafe(8)\040for\040_\040in\040range(10)]
原因
そもそも、なぜ XCode SDK
に純正の readline
が入っていないのか?それは、 readline
がGPLライセンスで、 libedit
がBSDライセンスだからだと言われている。Appleとしても自社のコードを守るためにGPLライセンスのものを極力同梱したくないだろう。そんな理由で使われている libedit
は上記の通り readline
と互換性がなく、このような問題を引き起こしている。
それを回避するためには、純正の readline
を homebrew
でインストールし、冒頭の通りライブラリのパスさえしっかりと設定する。この設定を行えば問題は解消する。
export CFLAGS="-I$(brew --prefix readline)/include -I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/usr/include"
export LDFLAGS="-L$(brew --prefix readline)/lib -L$(brew --prefix openssl)/lib"
# Pythonのビルド
ちなみに、 Homebrew
で readline
をインストールした場合、 caveats
に readline
を使いたいなら LDFLAGS
, CFLAGS
をいじるように警告が出るようになっている。
readline is keg-only, which means it was not symlinked into /usr/local,
because macOS provides the BSD libedit library, which shadows libreadline.
In order to prevent conflicts when programs look for libreadline we are
defaulting this GNU Readline installation to keg-only.
For compilers to find readline you may need to set:
export LDFLAGS="-L/usr/local/opt/readline/lib"
export CPPFLAGS="-I/usr/local/opt/readline/include"
そもそも readline
って何??
この記事を参考にしてください
この記事はアドベントカレンダーのものなので続きは別の日に公開します。
readline
の解説記事を 明日(12/5)に公開予定します。ぜひご購読お願いします!!
他にもネタが思いつく限りどんどん投稿していこうと思います。他の部員も面白い投稿をたくさんしてくれているので、ぜひいいね/購読よろしくお願いします!
上智大学エレクトロニクス研究部Advent Calendar
上智大学エレクトロニクス研究部の公式サイト