Pythonで日本語 + パイプを使うと死ぬ
イマイチPythonに慣れないので、普段はちょっとした自動化とかは基本的にシェルスクリプトと併用してるんですけど、コマンドラインで直接実行すれば動作するのに、シェルスクリプトからPythonを呼び出すと動作しないみたいなことがあったので、その時にやった対策の話。
ハマったコード
今回ハマったスクリプトは、簡略化するとこんな感じでPythonとシェルスクリプトを組み合わせたものです。
Python側(title.py)は、引数で与えられたURLからダウンロードしたhtmlのtitle要素をスクレイピングして出力するだけ、
シェルスクリプト側(mktitle.sh)は、引数で与えられたURLをtitle.pyに渡して、返ってきたtitle要素の名前でディレクトリを作成するだけのシンプルなスクリプトです。
$ python title.py http://precure-3dprinter.hatenablog.jp/
3DプリンタとITとプリキュアのブログ
当然期待した動作をします。
でもシェルスクリプトを実行してみると、
$ sh mktitle.sh http://precure-3dprinter.hatenablog.jp/
Traceback (most recent call last):
File "title.py", line 12, in
print(title.string)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 2-6: ordinal not in range(128)
と出る。
でもいつも失敗するわけではなくて、例えばGoogleなら(ascii codec can't encode characters って言ってるからなんとなくわかるけど)
$ sh mktitle.sh https://google.com
$ ls
Google mktitle.sh title.py
問題なく成功する。
なんじゃこりゃ。
日本語 + パイプのコンボがダメっぽい
とりあえずググったら、日本語(というか非ASCII全て)をtty以外に出すと死ぬらしい。PythonがUnicodeで持っている文字列をそのままasciiで吐こうとしちゃうみたい。
今回の例はパイプで死んでるけど、これはリダイレクトでもコマンド置換(バッククォートとか$( ... )とか)でも死ぬと思う。
なんでttyだけ色々上手いことやってくれるの〜(この辺詳しい人誰か教えてほしい)
というかむしろtty以外はなんで上手いことやってくれないんだ。
解決策その①
要はちゃんとUTF-8でエンコードしてあげましょうねっていうだけなので、
こんな感じでencode("utf-8")を付け足して出力すれば解決できる。
$ sh mktitle.sh http://precure-3dprinter.hatenablog.jp/ $ ls 3DプリンタとITとプリキュアのブログ mktitle.sh title.py
簡単だね。
解決策その②
解決策その①でも問題ないんだけど、いちいちprintする度にencode("utf-8")をタイプするのは指に優しくないので、stdoutへの出力を全てUTF-8でエンコードしちゃう荒技もあります。
sys.stdoutのコーデックを書き換えています。
$ sh mktitle.sh http://precure-3dprinter.hatenablog.jp/ $ ls 3DプリンタとITとプリキュアのブログ mktitle.sh title.py
こっちもちゃんと動いてそうですね。
Pythonの作法的にこれが良いのか悪いのかは知らないけど、どうせPythonでまともなプログラムを書く予定はないので、僕はこっちを使っていくかなぁ
まとめ
やっぱりPython慣れなさすぎる(そもそもこんな仕様が許容されているのが意味不明すぎる)