ファイルサイズをチェックするスクリプトのサンプル

FFTT : はじめましてPython」にすごく簡潔にPythonの特徴がまとまっててビックリしたbonlifeです。Pythonに興味がある人はまずここを読んでみると良いかも。
それはさておき、ファイルサイズをチェックするスクリプトPythonで書いてる人がいたので、ちょっと真似してみました。

まず、こういう小さなスクリプトの定番のシェルスクリプト。bonlifeっぽい書き方で勝手に書き直してみました。

  • file_size_check.sh
#!/bin/sh

SCRIPT_NAME=`basename $0`

usage(){
    echo $SCRIPT_NAME file [file...] >&2
}

if [ $# -lt 1 ]
then
    usage
    exit 1
fi

for f in $*
do
    if [ -f $f ]
    then
        fsize=`wc -c $f | awk '{print $1}'`
        if [ $fsize -gt 12288 ]
        then
            echo "File size 12 KB over OK. ( $f : $fsize B )"
        else
            echo "File size NG. ( $f : $fsize B )"
        fi
    fi
done

exit 0

一番大きな違いはファイルサイズの取得ですね。元々のサンプルでは以下のようになっていました。

fsize=`ls -s $1 | cut -c 1-4 | tr [:blank:] 0`

ls -s でキロバイト単位のサイズを出力させたのをごにょごにょしてます。これは、wc と awk を使う方が一般的ですし、バイト単位の情報を取得できるのでベターだと思います。もちろん用途にもよるので、ls -s の結果を使っても問題ないと思いますが。 ( wc と awk を使ったファイルサイズの取得は、下の本の229ページに載ってました。)

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

ファイルサイズ取得以外にも引数で複数のファイルを与えられるようにしてみました。実行すると以下のような感じになります。

> ./file_size_check.sh *
File size NG. ( a.dat : 100 B )
File size NG. ( b.dat : 200 B )
File size 12 KB over OK. ( c.dat : 1300 B )
File size 12 KB over OK. ( sample.sh : 2000 B )
File size NG.  ( file_size_check.sh : 427 B )

で、ここまでが長い前フリで、次からがPythonで書き直したものです。

  • file_size_check.py
#!/usr/bin/env python

import sys, os

limit = 12 * 1024

files = sys.argv[1:]

for file in files:
    fsize = os.path.getsize(file)
    if fsize > limit:
        print '%-20s : File size OK (%s B)' % (file, fsize)
    else:
        print '%-20s : File size NG (%s B)' % (file, fsize)

元のスクリプトとの違いは、複数ファイルを扱えるように sys.argv[1:] をファイルリストとして取得して処理している点と、出力部分でPythonらしくモジュロ演算子を使っている点ぐらい。

> python file_size_check.py a.dat b.dat c.dat d.dat
a.dat                : File size NG. (100 B)
b.dat                : File size NG. (200 B)
c.dat                : File size OK. (1300 B)
d.dat                : File size OK. (2000 B)

ただ、このままだとワイルドカードに対応していなかったり、チェックするファイルサイズがプログラム内に組み込まれてしまっていたりしてちょっとアレな感じ…。ということで、色々と修正した結果、以下のようになりました。

  • file_size_check.py (改訂版)
#!/usr/bin/env python

import sys, os, getopt, glob

script_name = os.path.basename(sys.argv[0])

def usage():
    print "usage : %s [ -h | -s size ] file [file...]" % script_name

def file_set_generator(args):
    file_set = set()
    for i in args:
        file_set.update([f for f in glob.glob(i) if os.path.isfile(f)])
    return file_set

def file_size_check(files,size=12*1024):
    for file in files:
        fsize = os.path.getsize(file)
        if fsize > size:
            print '%-20s : File size OK. (%s B)' % (file, fsize)
        else:
            print '%-20s : File size NG. (%s B)' % (file, fsize)

try:
    optlist, args = getopt.getopt(sys.argv[1:], 'hs:', longopts=["help","size="])
except getopt.GetoptError:
    usage()
    sys.exit(1)

if __name__ == '__main__':
    if optlist:
        for opt, arg in optlist:
            if opt in ("-h", "--help"):
                usage()
                sys.exit(0)
            if opt in ("-s", "--size"):
                file_size_check(files=file_set_generator(args),size=int(arg))
                sys.exit(0)
    else:
        file_size_check(file_set_generator(args))
        sys.exit(0)

変更点は、見ての通りですが、細々と関数化して、引数が指定されていない場合に usage を表示させるようにして、コマンドライン引数を扱えるようにして、チェックするファイルサイズを指定できるようにして…。もうちょっとキレイに書けそうな気がしますが、とりあえずこれくらいにしときます。
そうそう、glob で複数のファイルを指定できるようにしてしまったため、* を含んだ複数の引数を指定した場合に、同じファイルが何度も含まれてしまう可能性があるため、set にファイルリストを格納してみました。初めての set です。本当は yeild して generator な感じに仕上げたかったのですが、同じファイルが何度も表示されるのはダサ過ぎるので却下。こんな感じで細々とPythonista目指します!
追記です。結局のところ、何をしたいのか、という一番大事な部分をほとんど理解しないままスクリプトを書いてみたわけですが、うっかりすると find だけでもなんとかなりそうな、そうでもなさそうな。(最後の rm の部分のみ行いたい処理に変えれば良かったりして。)

find . -name '*.jpg' -a -size -12288c -exec rm {} \;