created: 2021-01-31T02:17:59.000Z

pipeされた標準入力がseekできなかった話

こんなコードを書こうとした。

if filepath == '-':
    fo = sys.stdin
else:
    fo = open(filepath, mode=read_mode)

ファイルパスの引数に - を渡したら標準入力の内容を使うというもの。

標準入力のシノニムとして - を使うコードは python の fileinput や awscli, gsutil にもあったものなのでいいかなと思ったのだが、後続の処理で fo.seek(0) が呼ばれてエラーになることがわかった。

# pipeすると fo.seek(0) で IOError: [Errno 29] Illegal seek になる
$ cat /tmp/_.csv | ./csv2sqlite.py - /tmp/_.sqlite3
Traceback (most recent call last):
  File "./csv2sqlite.py", line 210, in <module>
    convert(args.csv_file, args.sqlite_db_file, args.table_name, args.headers, compression, args.types)
  File "./csv2sqlite.py", line 47, in convert
    fo.seek(0)
IOError: [Errno 29] Illegal seek

pythonのstdinが抽象化してくれていると勝手に思っていたが、調べてみるとpipeやsocket由来の標準入力はseekできないルールがちゃんとあり、そのルールに抵触しているのでエラーになっているようだった。

If it's a terminal or a pipe or a socket, no. If it's a file, yes (usually).

OSにもよるのかもしれない (Linuxはダメ)

Some devices are incapable of seeking and POSIX does not specify which devices must support lseek().

ファイル由来の標準入力だとseekできる

$ ./csv2sqlite.py - /tmp/_.sqlite3 < /tmp/_.csv
プログラマのためのSQL 第4版 すべてを知り尽くしたいあなたに
[ad] プログラマのためのSQL 第4版 すべてを知り尽くしたいあなたに
Joe Celko, ミック (Kindle版)