Hatena::ブログ(Diary)

bonlife このページをアンテナに追加 RSSフィード

言及ISBN/ASIN
  • Ringo EXPO 08 [DVD]
  • 三文ゴシップ
  • my way
  • ビジネスパーソンのための話し方入門 (日経文庫)
  • ザ・グーグルウェイ グーグルを成功へ導いた型破りな戦略
  • 考え・書き・話す3つの魔法
  • 自分の答えのつくりかた―INDEPENDENT MIND

2007-09-08 ビールが美味い。

「ゲームで極める シェルスクリプトスーパーテクニック」を注文しました

なんだかよく分からないまま、脊髄反射で注文してしまったbonlifeです。

ゲームで極める シェルスクリプトスーパーテクニック

ゲームで極める シェルスクリプトスーパーテクニック

で、今日になってこのムービーですよ。あぁ、気になる気になるです。会社の売店で注文したので、届くまで少し時間がかかるかも。うぅ。

D

2007-04-18 ノドがイガイガします。

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

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ページに載ってました。)

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

> ./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 {} \;

2007-04-17 電話での問合せはやめてください。

csh系でのワイルドカード展開の謎(というより罠)

最近、香りがあるお茶(伊藤園ジャスミン茶、アサヒ飲料の香茶(シャンティー)プーアル茶)を好んで飲んでいるbonlifeです。csh使ってる時に、lsコマンドアスタリスク(*)使ってファイルを表示しようとしたら、なんだか予想と違う動きですごくビックリしたのでメモしておきます。どうしてこういう動きになるのかご存知の方、是非とも教えてくださいませ。

まず、空ファイル aaa.txt を作ります。

> touch aaa.txt

lsで表示すると当然普通に表示できます。

> ls -l aaa.txt
-rw-rw-r--   1 user     group             0 Apr 17 15:44 aaa.txt

続いて、引数存在しないファイル bbb.txt も引数で指定してみます。当然、aaa.txt は表示され、 bbb.txt については存在しないよ!というメッセージが表示されます。

> ls -l aaa.txt bbb.txt
ls: 0653-341 ファイル bbb.txt が存在しません。
-rw-rw-r--   1 user     group             0 Apr 17 15:44 aaa.txt

で、ここからです。あれ、bで始まるファイル確かあったよな、的なノリで aaa.txt と合わせて b* も指定してみます。ワイルドカードでの部分一致指定ですね。

> ls -l aaa.txt b*
一致していません.

オーマイコンブ!なんと、aaa.txt の情報も表示されず…。なんとまぁ…。これは場合によっては困っちゃいますです。

さらに不思議なのは…。

> ls -l a* b*
-rw-rw-r--   1 user     group             0 Apr 17 15:44 aaa.txt

aaa.txt ではなく a* と b* を同時に引数指定すると、ちゃんと aaa.txt が表示されるじゃないの!いや、この動作には特に問題はないんですが、なんなんだろう、このモヤモヤした感じは。これってUNIX(Linux)ユーザの間では常識だったりするのでしょうか。

ちなみに、気になって少し試したり試してもらったりしたところ、tcshzshではcshと同じようにワイルドカードなしのファイル名とワイルドカードありのファイル名が混在しているケースで、ワイルドカードありの結果が存在しない場合、「No match.」となりました。(zshは奥が深そうなので、何か回避方法があるかもしれませんが。) /bin/shでお馴染みのBourne Shellbashでは混在しているケースでも想定した通りの結果になりました。やっぱり漢は手堅くBourne Shell使っとけ、ってことかしら(違)。うぅ…。

2006-11-30 徹夜してでもWiiを買う覚悟です。

指定した日付のファイルを探すシェルスクリプト

いつにも増して集中力がないbonlifeです。最近ぼんやりと眺めさせていただいているid:parasporospaさんの日記にて「[unixtool]指定した日付のファイルを探す」という記事があり、こういうのをスクリプトにして自分用binに入れとくと便利かも!と思ってトライしてみました。findコマンドの-newerオプションとtouchコマンドでの指定日時のファイル生成を組み合わせてみました。(ワンライナーじゃない全く普通のシェルスクリプトです。)

  • find_by_date
#! /bin/sh

# シグナルをトラップして一時ファイル削除

trap 'rm -f /tmp/tmp_*.$$; exit 1' 1 2 3 15

SCRIPT_NAME=`basename $0`

# エラー終了関数定義

function error_exit {
    echo "$SCRIPT_NAME [path] [CCYYMMDD]" >&2
    rm -f /tmp/tmp_*.$$
    exit 1
}

# 引数の数をチェック

if [ $# -ne 2 ];
then
    error_exit
fi

SEARCH_PATH=$1
DATE=$2

# $DATEが8桁の数字であるかの簡易チェック

if [ -z "`echo $DATE | egrep '[0-9]{8}'`" ];
then
    error_exit
fi

# 指定日時のFROM、TOを取得
# 前日の23:59:59から当日の23:59:59まで

DATE_FROM=`date --date "${DATE} 1 second ago" +%Y%m%d%H%M.%S
# DATE_FROM=${DATE}0000.00
DATE_TO=${DATE}2359.59

# touchで指定日時の空ファイルを作成

touch -t $DATE_FROM /tmp/tmp_date_from.$$
touch -t $DATE_TO   /tmp/tmp_date_to.$$

# findの-newer(とその否定)を使い、一時ファイルと比較した結果を出力 (ls)

find $SEARCH_PATH -type f -newer /tmp/tmp_date_from.$$ ! -newer /tmp/tmp_date_to.$$ -ls;

# 一時ファイルの削除

rm -f /tmp/tmp_*.$$

これでそれっぽい動きをするシェルが出来ました。ただ、これAIXでは動かないのです。GNU版Dateじゃないと"1 second ago"なんて小粋なことは出来ないわけで。で、仕方なしに妥協してコメントアウトしてある行を使うと、タイムスタンプが対象日の00時00分00秒のファイルは検索に引っかからなくなってしまう罠。シェルで文字列をごにょごにょして日付計算するのはかなり面倒なので却下。PerlワンライナーでlocaltimeやTime::Localのtimelocalを使って無理矢理やろうかとも思ったのですが、それなら全部Perlで書いた方が良いじゃないの…ということで。後でPerlで書き直してみようっと。

本日の結論としては、findとtouchの連携は結構便利!

2006-09-04 UNIXシェルプログラミングをゼロから学んでみます。

「入門UNIXシェルプログラミング」を買いました

最近ちょっと気味が悪いくらいすんなり帰宅できてしまっているbonlifeです。(これはオカシイ…。そろそろヘヴィーな仕事が突然降ってくる予感…。)UNIX使ってるのにシェルもまともに書けないのは恥ずかしいなぁ…と思っていたところ、青木日記でこの本がオススメされていたので、勢いで買ってしまいました。読みたい(勉強したい)本が多過ぎて時間が足りません。(時間があっても読まなかったりするのですが…。)

まだ第1章しか読んでませんが、それでもなんだか良い感じです。第13章まであるので、1日1章ずつ会社勉強してみます。だいたい3週間ぐらいで読み終わるかな。読み終わったら、getoptsなんかを駆使して既存のシェルの拡張とかもしていきたいところです。

そういや、最近シェルスクリプトを書いていてつまづいたことがあったのでメモメモ。

#! /bin/bash

# 変数 $hoge は空文字列

hoge=""

if [ -z $hoge ]; then
    echo '$hoge は空ですよ。'
else
    echo '$hoge は空じゃないですよ。'
fi

出力結果は以下の通り。

$hoge は空ですよ。

なんてことはないシェルスクリプトですよね。変数hogeに空文字列を代入して、それがゼロバイト長かどうかを判定するだけ。ところが、Bourne Shellでは全く同じ内容が正しく動かないのです!

#! /bin/sh

# 変数 $hoge は空文字列

hoge=""

if [ -z $hoge ]; then
    echo '$hoge は空ですよ。'
else
    echo '$hoge は空じゃないですよ。'
fi

出力結果は以下の通り。

./sample_b.sh[7]: test: 0403-004 このコマンドにはパラメータを指定してください。

あれー、なんだかエラーになっちゃいます。ガビョーン…。とういのも、変数hogeは空文字列としているつもりですが、実際にはヌルとして扱われているみたいなのです。このあたりは上で紹介した本の第2章の内容っぽいので、明日勉強します。とりあえず、変数を呼び出す時にダブルクォーテーションで括ってあげることで、文字列として扱うことができるようなので、修正して試してみました。

#! /bin/sh

# 変数 $hoge は空文字列

hoge=""

if [ -z "$hoge" ]; then
    echo '$hoge は空ですよ。'
else
    echo '$hoge は空じゃないですよ。'
fi

出力結果は以下の通り。

$hoge は空ですよ。

問題なしですね。cshでも試してみたのですが、やはりダブルクォートで括っておかないとエラーになりました。変数を文字列として扱わせるためにはダブルクォート、という基本的なルールを今頃知ったbonlife、UNIXに触れて5年目(恥)。一歩ずつ着実に成長していきたいところです。細々と頑張ります。