Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

~/.python_history に空白文字が正しく記録されない

この記事は、上智大学エレクトロニクス研究部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 と互換性がなく、このような問題を引き起こしている。
それを回避するためには、純正の readlinehomebrew でインストールし、冒頭の通りライブラリのパスさえしっかりと設定する。この設定を行えば問題は解消する。

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のビルド

ちなみに、 Homebrewreadline をインストールした場合、 caveatsreadline を使いたいなら 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
上智大学エレクトロニクス研究部の公式サイト

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?