技術

pyddの改良

以前ddコマンドのラッパー「pydd」をPythonで書いてみたという記事を書きましたが(うわ〜もう半年経つんですね〜)、幾つか小さい問題点がありました。

1. ddで転送された最終的なサイズが出ない
2. ddの出力を3行と決め打ちしてた

1については、ddの進捗を得るために0.1秒ごとにシグナルを送信してstderrを読む処理を行ってましたが、この方法だと一番最後に表示される転送サイズが実際の最終的なサイズではなく、完了するちょっと前(平均して0.1/2秒前)の途中の転送サイズしかとれません。
これは最近の一般的な性能のマシンだと数MB〜数十MBのずれが生じます。
ddコマンドは完了時にstdoutに結果を出力するので、処理の最後にそれ”も”読んで表示すべきです。

2については、非同期に実行されているddが出力したstderrを読み込む際に、読み込みすぎる(バッファに存在するデータより多く読み込もうとする)とそこで処理がブロックしてしまい、ddが完了するまで進捗の表示が更新されないという問題が起きるために、ddの進捗の出力を3行と決め打ちして読み込みすぎないようにしてました。
これを決め打ちしなくて済むならそうするに越したことはありません。

ということでこれらの問題点を解消したバージョンが以下となります。
結構前回と構造が変わってますが、簡単に言えば変更した部分は

1. ddのstdoutとstderrを混ぜて、さらに完了時の出力も処理するようにした
2. ノンブロッキングでddのstdoutとstderrを読み込むようにし読み過ぎてもブロックしないようにした

となります。

しかし問題点を解消するかわりに、前回のものよりソースが汚くなってしまいました。
制御文が増えたからでしょう。

美しく簡潔に表現する方法はないものか…

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
dd command wrapper
'''

import sys, os, time, fcntl
from subprocess import Popen, PIPE, STDOUT
from signal import SIGUSR1, SIGINFO
from select import select
import humansize

interval = 0.1

if 'linux' in os.uname()[0].lower():
    SIGPROGRESS = SIGUSR1
else:
    SIGPROGRESS = SIGINFO

def print_progress(lines):
    for line in lines:
        if 'bytes transferred' in line:
            b = int(line.split()[0])
            print '\t', humansize.approximate_size(b, False), '   \r',

def fh2lines(fh):
    lines = []
    while True:
        try:
            line = fh.readline()
            lines.append(line)
        except IOError, e:
            if e.errno == 35: # Resource temporarily unavailable
                break
            else:
                raise
    return lines

def set_nonblocking(fh):
    fd = fh.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

if __name__ == '__main__':
    dd = Popen(['dd'] + sys.argv[1:], stdout = PIPE, stderr = STDOUT)
    set_nonblocking(dd.stdout)
    while not dd.poll():
        lines = []
        try:
            dd.send_signal(SIGPROGRESS)
            info = select((dd.stdout, ), (), (), 0)[0]
            if info:
                lines = fh2lines(info[0])
            print_progress(lines)
            time.sleep(interval)
        except OSError, e:
            if e.errno == 3: # No such process
                out, _ = dd.communicate()
                print_progress(out.split('\n'))
                break
            else:
                raise
    print
    sys.exit(dd.returncode)

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください