あるファイルから、特定の文字列を含む行を抽出する必要があった。
これをPythonで実装したところ、いくつか問題にぶつかったのでまとめておく。
[公開後追記]
grep -e ‘^■’じゃダメだったのかな
との声をいただきました。確かに…!(;ω;)
な、泣いてなんかないんだからねっ。
[追記ここまで]
[やりたいこと]
問題のファイル(memo.txt
)は、以下のような内容になっている。
■ 1. XXXXX 1.1. XXXXX YYYYYYYYYY 1.2. XXXXX YYYYYYYYYY ■ 2. XXXXX 2.1. XXXXX YYYYYYYYYY 2.2. XXXXX YYYYYYYYYY
ここから■
を含む行を抽出して、以下のような出力結果を得たい。
■ 1. XXXXX ■ 2. XXXXX
なお、利用している環境は以下の通り。
macOS Catalina バージョン 10.15.7
[エンコードの問題]
何も考えずにPythonでこれを実装した場合、以下のようになった。
path = 'memo.txt' with open(path) as f: for line in f: if line.startswith('■'): print(line)
上記をextract.py
というファイル名で保存しpython
コマンドで実行すると、以下のようにエンコードの指定が不足しているとエラーが表示された。
$ python extract.py File "extract.py", line 5 SyntaxError: Non-ASCII character '\xe2' in file extract.py on line 5,but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
指定されたURLを確認すると、ファイルの先頭でエンコードを指定する必要があるようだ。
ファイルの内容を以下のように修正する。
# coding=utf8 path = 'memo.txt' with open(path) as f: for line in f: if line.startswith('■'): print(line)
すると、実行結果が表示されるようになった。
$ python extract1.py ■ 1. XXXXX ■ 2. XXXXX
しかし、意図した結果と異なり、余分な改行が加えられてしまっている。が、この問題は一旦おいておく。
エンコードの問題に戻る。Python 3系のドキュメントをみると、以下のような記述がある。
Python ソースコードのデフォルトエンコーディングは UTF-8 なので、文字列リテラルの中に Unicode 文字をそのまま含めることができます
Unicode HOWTO — Python 3.9.0 ドキュメント
もしや、と思いPythonのバージョンを表示してみると、なんと2系であった。不覚……。
$ python -V Python 2.7.16
Python 3系を利用するには、python3 extract.py
のようにして実行する必要があった。Python 3系を利用する場合、extract.py
の先頭にエンコード指定がなくとも、エラーが発生しなかった。
$ python3 -V Python 3.8.5 $ python3 extract.py ■ 1. XXXXX ■ 2. XXXXX
[改行コードが重複する問題]
エンコードの問題は解決したので、改行コードが多い問題を考える。
とりあえず、何も見ずに考えてみると、以下のあたりに原因がありそうだった。
- 変数
line
には元々のファイルの改行コード込みの文字列が格納されている print()
関数がJavaのSystem.out.println()
のように改行コードを自動的に付加する
2のprint()
関数をいじる方法がパッと思いつかなかったので、以下のように実装することで1の問題点を解決した。
path = 'memo.txt' with open(path) as f: for line in f: if line.startswith('■'): print(line[0:len(line)-1])
これを実行すると、以下のように不要な改行が削除されている。
$ python3 extract.py ■ 1. XXXXX ■ 2. XXXXX
これで一応やりたいことはできたのだが、print()
関数をどうにかできないか調べてみた。
公式ドキュメントのprint()
関数の説明を読んでみる。
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
objects を sep で区切りながらテキストストリーム file に表示し、最後に end を表示します。sep 、 end 、 file 、 flush を与える場合、キーワード引数として与える必要があります。
endというキーワード引数で、自動的に末尾に改行コードが付与されるということがわかったので、以下のように実装を修正した。
path = 'memo.txt' with open(path) as f: for line in f: if line.startswith('■'): print(line, end='')
これを実行すると、同じように、不要な改行が削除されている。
$ python3 extract.py ■ 1. XXXXX ■ 2. XXXXX
ちなみに、Python 2系ではprint()
関数はデフォルトでは使用できず、print
文として扱われてしまう。
注釈 この関数は print という名前が print ステートメン トとして解釈されるため、通常は使用できません。ステートメントを無効化して、 print() 関数を使うためには、以下の future ステートメントをモジュールの最初に書いて下さい。
: from __future__ import print_function
バージョン 2.6 で追加.
2. 組み込み関数 — Python 2.7.18 ドキュメント
6. 単純文 (simple statement) — Python 2.7.18 ドキュメント
そのため、以下のように実装することで、Python 2系でもprint()
関数を利用することができる。
# coding=utf8 from __future__ import print_function path = 'memo.txt' with open(path) as f: for line in f: if line.startswith('■'): print(line, end='')
[bashで実装した場合]
ファイルを読み込んで処理するだけでPythonなんか使わなくても、という声が聞こえてきたので、bashでも実装してみた。
#!/bin/bash while read line do if $line == '■'* ; then echo $line fi done < ./memo.txt
以下の記事を参考に実装した。
bashで実装すればPythonのことであれこれ悩まずに済んだのに……とは思うが、Pythonの理解が深まったので良しとする。