Pythonで学ぶ「詳解 UNIXプログラミング」(その1) 第1章 概論

1.1 ディレクトリ内の全てのファイルをリストする

import sys, os

if len(sys.argv) != 2:
    sys.exit("a single argument (the directory name) is required")
try:
    filenames = os.listdir(sys.argv[1])
except OSError:
    sys.exit("can't open {0}".format(sys.argv[1]))

for filename in filenames:
    print filename
sys.exit(0)

Pythonでは、opendir()/readdir()/closedir()を個別に呼び出す必要はなく、os.listdir()だけでファイル一覧を取得することができる。ただし、readdir()と違って、os.listdir()では親ディレクトリとカレントディレクトリを示す" . "" .. "を含まない。

エラーの検出は、os.listdir()の戻り値をチェックするのではなく、Pythonの例外処理で検出する。ここで、例外ハンドラを

except:
    ...

ではなく、

except OSError:
    ...

としている点に注意して欲しい。except:〜の形式では全ての例外を捕捉してしまうため、例えばos.listdir()内でsys.exit()が呼び出されたり、Ctrl+Cを打ってシグナルが発生した場合なども、except:節が実行され、エラーメッセージが表示されてしまう。特に必要がなければ、 except:ではなく、except OSError:のように例外クラス名を指定するようにしよう。
具体的な例外クラス名を調べるのがめんどくさければ、except Exception:でもシグナルやsys.exit()の例外を捕捉せずに終了するとこができる。

このスクリプトでは、エラー表示と終了状態の設定をsys.exit()で行っている。終了状態が1ならば、この方法が一番簡単だろう。正常終了した場合には、sys.exit(0)のように、終了状態として0を指定して終了している。

1.2 低レベルファイルIOを使って、標準入力から標準出力にファイルをコピーする

import os, sys

STDIN_FILENO = 0
STDOUT_FILENO = 1

BUFSILZE = 8192

while True:
    try:
        buf = os.read(STDIN_FILENO, BUFSILZE)
    except Exception:
        sys.exit("read error")

    if not buf:
        break

    try:
        os.write(STDOUT_FILENO, buf)
    except Exception:
        sys.exit("write error")

sys.exit(0)

ファイル記述子を使った低レベルファイルIOは、os.read()/os.write()で行う。

1.3 標準入出力を使用したファイルのコピー

import sys
while True:
    try:
        c = sys.stdin.read(1)
    except Exception:
        sys.exit("read error")
    if not c:
        break
    
    try:
        sys.stdout.write(c)
    except Exception:
        sys.exit("write error")

sys.exit(0)    

C言語によるサンプルではgetc()/putc()を使っているが、Pythonには相当する関数がないため、通常のファイル読み書きで1バイトずつ処理している。

1.4 プロセスのプロセスIDを出力する

import os
print "Hello world from process ID {0}".format(os.getpid())

1.5 標準出力からコマンドを読み込み、実行する

import sys, os

while True:
    try:
        buf = raw_input("% ")
    except EOFError:
        break
        
    try:
        pid = os.fork()
    except Exception:
        sys.exit("fork error")
        
    if pid == 0:  # child
        try:
            os.execlp(buf, buf)
        except Exception:
            print >>sys.stderr, "couldn't execute"
            sys.exit(127)
        
    # parent
    try:
        os.waitpid(pid, 0)
    except Exception:
        sys.exit("waitpid error")

このサンプルのように、対話的に一行ずつ入力を受け取りたい時には、sys.stdinオブジェクトを直接使用するよりも、raw_input()を使用する。raw_input()はプロンプトを出力してくれるだけでなく、ターミナル等からの読み込みなら readline を使った行編集なども行ってくれるのだ。

1.6 エラー処理

通常、Pythonでエラーが発生した場合には例外が発生し、エラーメッセージは例外オブジェクトに含まれている。

try:
    open("ThisFileNeverExisted")
except Exception, e:
    print str(e)

os.strerror()で、エラー番号からエラーメッセージを取得することもできる。

import os, errno
print os.strerror(errno.ENOENT)

1.7 ユーザ識別

import os
print "uid = {0}, gid = {1}".format(os.getuid(), os.getgid())

1.8 シグナル

先ほど作成した単純なシェルを、割り込みキーを検出するように修正してみよう。

import sys, os

import signal
def sig_int(signum, frame):
    print "interrupt"

signal.signal(signal.SIGINT, sig_int)

while True:
    try:
        buf = raw_input("% ")
    except EOFError:
        break
        
    try:
        pid = os.fork()
    except Exception:
        sys.exit("fork error")
        
    if pid == 0:  # child
        try:
            os.execlp(buf, buf)
        except Exception:
            print >>sys.stderr, "couldn't execute"
            sys.exit(127)
        
    # parent
    try:
        os.waitpid(pid, 0)
    except Exception:
        sys.exit("waitpid error")

APUEのサンプルをそのままPythonに置き換えると上のようになる。しかし、実行してみると、このスクリプトC言語によるサンプルとはちょっと違った動作になってしまう。

C言語版ではテキストの読み込みにfgets()を使用しており、シグナルハンドラが設定されている場合は、入力待ち状態でCtrl-Cを押しても読み込みが続行される。しかし、Python版ではEOFError例外が発生し、処理が終了してしまう。Pythonインタープリタは独自にシグナルハンドラの起動を管理しており、C言語版と全く同じ動作にすることは難しい。

単にキーボード割り込みを無視して処理を継続するのが目的なら、SIGINT割り込みで発生するKeyboardInterrupt例外を利用しても良い。

import sys, os

while True:
    try:
        buf = raw_input("% ")
    except KeyboardInterrupt:
        # ignore SIGINT
        continue
        
    try:
        pid = os.fork()
    except Exception:
        sys.exit("fork error")
        
    if pid == 0:  # child
        try:
            os.execlp(buf, buf)
        except Exception:
            print >>sys.stderr, "couldn't execute"
            sys.exit(127)
        
    # parent
    try:
        os.waitpid(pid, 0)
    except Exception:
        sys.exit("waitpid error")