僕は偉大なプログラマなんかじゃない。 偉大な習慣を身につけたプログラマなんだ -- Kent Beck
プログラミングにしても他のことにしても、何かを効果的に行うに は、よい習慣を身につけるのが大切とされている。プログラミング について言えば、面倒くさがらずにしっかりテストをしたり、新しい 技術を日々、覚えたりすることが大切なようである。しかしながら、 急いでプログラミングしているときは、テストをなおざりにしたり、 デバッガを使った方がよい場面でも「printf デバッグ」に固執し たりと、悪い方の習慣に従ってしまいがちである。
一方、原稿やレポートなどの締め切りが迫って急いでいるときにか ぎって、目下の仕事とは関係ない現実逃避に走ってしまうのも悪い 習慣のひとつである。原稿の内容そっちのけで LaTeX のマクロに 凝り始めてみたり、レポートの提出前日に部屋の片づけを始めて古 い本を引っ張り出してきたり、といった行動パターンはよく聞く話 である。私の場合も、原稿の締め切りが迫っているときにかぎって アイディアが浮かんで、こんなことやってる場合ではないのにと思 いつつプログラムを書き始めたりすることが多い。今回は、そうし て書き溜めてきた小粒なツールを Web関連、メール関連、画像関連 に絞ってとりあげ、あわせて小粒なツールのプログラミングのノウ ハウを紹介する。
Web関連のツール
私は 6年ほど前に自分の Webサイトを作りはじめた。以来、Emacs で HTML ファイルを編集するというスタイルで更新を続けている。 市販の Web 用オーサリングソフトを使わないのは、
- シンプルなページしか作らない
- Emacs + Unix の強力なテキスト処理から離れられない
という理由からである。ここでは Webサイトを保守するために作っ た小粒なツールを紹介する。
url-extract
本連載 Webページ *2 では記事中で紹介した URL の一覧をまとめている。
こうしたリストを手作業で作るのは面倒なだけでなく、見落としに よる間違いが起きやすい。そこで、URLの一覧を自動生成するため のツール url-extract を作った。
url-extract のソースコード
#! /usr/bin/env ruby content = readlines.join regex = %r!href\s*=\s*(['"])?((?:http://|https://|ftp://|mailto:)\S+?)\1!i urls = [] content.scan(regex) { urls.push($2) } puts "<ul>" urls.uniq.each {|url| puts %Q(<li><a href="#{url}">#{url}</a>) } puts "</ul>"
url-extract では、正規表現を用いて HTML の中からリンク部分 を抽出している。foo.html というファイルから URL を抽出す るにはコマンドラインから次のように実行する。
% url-extract foo.html <ul> <li><a href="http://namazu.org/">http://namazu.org/</a> <li><a href="http://quickml.com/">http://quickml.com/</a> <li><a href="http://mobiquitous.com/">http://mobiquitous.com/</a> </ul>
数分で作った安直なツールだが、毎月、Webページを更新するのに 重宝している。ファイルの先頭の #! /usr/bin/env ruby の部分は、 Ruby スクリプトのであることを示している。#! /usr/bin/ruby の ように絶対パスで指定した場合と違って、環境変数 PATH の中から 最初に見つかった ruby コマンドが用いられる。このように指定し ておけば、別の計算機にコピーしたときに、 ruby コマンドが /usr/bin/ruby ではなく /usr/local/bin/ruby にインストールさ れていても、スクリプトを修正する必要がなく便利である。後から 紹介する Perl のスクリプトでも同様に #! /usr/bin/env perl と 指定している。
webpngize
1999年の 8月末、米国の Unisys 社が、特許を侵害しているソフト ウェアで作成された GIF画像を使用している Web サイトから特許 使用料を徴収する方針を発表する、という出来事があった*3 。個人のサイトまで特許使用料を求められるとは思えなかったが、 安心して使えない画像フォーマットを使い続けるのは避けたい。そ こで、私の Web サイトでは、GIF画像をすべて PNGフォーマットに 変換することに決めた。
そうは言っても、画像ファイルをひとつひとつ GIMP *4などの画像編集ソフトで読み込んで GIF から PNG に変換するのは面倒である。また、画像ファイルのフォー マットを変換するだけでなく、 HTML の中の
<img src="foo.gif" alt="foo">
といったタグを
<img src="foo.png" alt="foo">
に書き直さなければならない。こちらもHTMLファイルをエディタで ひとつづつ開いて修正するのは骨が折れる。
そこで、これらの作業をコマンドラインから一発で行うためのツー ル webpngize を作った。HTMLファイルと画像ファイルが置かれて いるディレクトリを指定して次のように実行すると、 ~/public_html*5 以下の画像ファイルの変換と HTMLファイルの書き換えを一発で行える。
% webpngize ~/public_html
webpngize のソースコード
#! /bin/sh test -z "$1" && echo "Usage: webpngize <directory>" && exit cd "$1" # GIFファイルを PNGファイルに変換する find . -type f -name '*.gif' | while read gif; do echo "$gif" # 進行状況を表示する # GIFファイルを PNGファイルに変換する。例: foo.gif -> foo.png convert "$gif" `basename "$gif" .gif`.png rm "$gif" done # HTMLファイルの内容を書き換える find . -type f -name '*.html' | while read html; do # HTMLファイルの内容を perl で置換して書き換える perl -i -p0777e "s/(<img\s.*?src=)(['\"]?)(.*?)\.gif\2/\$1\$2\$3.png\$2/gi" "$html" done
webpngize は各種のツールを組合せてシェルスクリプトで処理を自 動化する Unix 流プログラミングの典型的な例といえる。$gif や $html などの変数名を "..." で囲っているのは、ファイル名に空 白が含まれる場合でも正しく処理を行うためである。
webpngize の処理は大きく 2つに分かれている。GIFファイルを PNG ファイルに変換する処理の部分では、
- find コマンドで GIF ファイルのリストを作り、
- 個々の GIFファイルを ImageMagick*6 の convert コマンドで PNG に変換し、
- 元のGIFファイルを削除する
という処理を while ループを用いて行っている。
HTML の内容を書き換える処理の部分では、
- find でHTMLファイルのリストを作り、
- 個々の HTMLファイルを Perl で書き換える
という処理を while ループを用いて行っている。perl -i -p0777e 's/.../.../gi' のコマンドライオプションは、次のような意 味を持っている。
- -i : ファイルの内容を変更し、元のファイルに上書きする。
- -p0777e "s/.../.../gi" : ファイルの内容を読み込んで、"s/.../.../gi" で指定された文字列置換を行う。0777 はファイル全体をひとつの文字列として読み込むためのおまじないである。
この結果、HTMLファイルの IMG タグで GIFファイルを埋め込んで いる部分が、すべて PNGファイルへと置き換えられる。 webpngize では、正規表現を用いた文字列置換を行う "s/.../.../gi" の部分 が複雑だが、単純な文字列置換の場合は、
# *.html の中の文字列 foo をすべて bar に置き換える % perl -i -pe "s/foo/bar/g" *.html
のように、コマンドラインから手軽に行えるので覚えておくと便利 である。
html-downcase
私は自分の Webサイトを作りはじめてからしばらくの間、HTML の タグをすべて大文字で記述していた。ところが、ある日、なんとな く「小文字の方がかっこいいのでは?」と思い立ち、すべての HTML ファイルのタグを小文字にしたくなった。しかし、何十個もある HTML ファイルをひとつづつエディタで開いて、タグを小文字に置 き換えていくのは気が遠くなる作業である。そこで、HTMLタグの大 文字から小文字への変換を自動で行うツール html-downcase を作るこ とにした。
html-downcase のソースコード
#! /usr/bin/env perl use strict; my $content = join '', <>; $content =~ s/(<\/?)([A-Z]\w*)((?:\s+[A-Z]\w*(?:\s*=\s*(?:(["']).*?\4|[\w\-.]+))?)*)(\s*>)/ $1 . lc($2) . lc_attr($3) . $5/gsixe; print $content; sub lc_attr { my ($attrs) = @_; $attrs =~ s/\n/ /g; $attrs =~ s!(\s+)([A-Z]\w*)((?:\s*=\s*(?:(["']).*?\4|[\w\-.]+)))?! $1 . lc($2) . $3!gies; return $attrs; }
html-downcase では正規表現を使って HTML タグを抽出し、小文字へ の変換処理を行っている。この方法では HTML の中のコメントなど を正しく扱うことはできないが、大抵の場合は問題ないので、厳密 な処理にはこだわっていない。
HTMLファイル sample.html のタグを小文字に変換するにはコマン ドラインから次のように実行する。<!DOCTYPE ...> の部分をのぞ き、すべての HTMLタグが小文字に変換される。
% cat sample.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <HTML> <HEAD><TITLE>今月の新刊</TITLE></HEAD> <BODY> <H1>UNIX懐古主義 ―ザ・バッドノウハウ―</H1> <P>termcapを極めよう。stty徹底攻略。roffで楽々文書作成...</P> <ADDRESS><A HREF="http://example.org/">編集部</A></ADDRESS> </BODY> </HTML> % html-downcase sample.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head><title>今月の新刊</title></head> <body> <h1>UNIX懐古主義 ―ザ・バッドノウハウ―</h1> <p>termcapを極めよう。stty徹底攻略。roffで楽々文書作成...</p> <address><a href="http://example.org/">編集部</a></address> </body> </html>
先に紹介した webpngize では、ディレクトリ以下のファイルをま とめて変換するようにプログラムを書いたが、html-downcase では読 み込んだ HTML ファイルをのタグを小文字に変換して標準出力に出 力するというフィルタ処理に専念している。これは汎用性の高いコ マンドにしたかったからである。ディレクトリ以下のファイルをま とめて処理したい場合は、コマンドラインから次のように実行すれ ばいい。sh系シェルスクリプトの while 構文はやや複雑であるが、 たくさんのファイルをひとづつ処理するときに役立つので覚えてお くと便利である。
% find ~/public_html -type f -name "*.html" | while read html; do html-downcase "$html" > tmp; mv tmp "$html"; done
このワンライナーは sh 系の while 構文を利用しているので、csh や tcsh を使っている場合は、
% sh -c 'find ~/public_html -type f -name "*.html" | while read html; do html-downcase "$html" > tmp; mv tmp "$html"; done'
のように実行する必要がある。
Web関連のツールのまとめ
Web 関連の小粒なツールとして url-extract, webpngize, html-downcase を紹介した。HTML ファイルはただのテキストファ イルであるため、Perl や Ruby を用いてテキスト処理を簡単に行 える。厳密に HTML を処理するには構造の解析が必要であるが、 ちょっとした文字列置換などは正規表現による処理でも十分に役立 つ。たくさんの HTML ファイルを持つ Web サイトの保守に苦労し ている方はぜひ正規表現によるテキスト処理を覚えて楽をしてほし い。
と、正規表現を持ち上げておいてから言うのも変かもしれないが、 高度に構造化された XML 文書が広まるにつれて、正規表現による テキスト処理という手法が適用できる場面は減っていくような気も している。
メール関連のツール
インターネットと携帯電話の普及により、メールによるコミュニケー ションは、日常生活に欠かせないものとなった。私などはインター ネットを知った瞬間にメール中毒になり、友人からは「高林 == い つでもメールをチェックしているだけの奴」というレッテル を貼られてしまった (今では多少の節度はあるつもりでいるが…)。 ここではメールを扱うために作った小粒なツールを紹介する。
mailrank
Unix を使いはじめて間もない頃、何かのメーリングリストで、投 稿者ランキングを自動集計するためのワンライナー*7が紹介されていて、ずいぶん感心した記憶がある。 たしかこういうものだったと思う。
% grep -h '^From:' * | sort | uniq -c | sort -nr 27 From: Satoru Takabayashi <satoru@...> 18 From: Toshiyuki Masui <masui@...> 8 From: Koji Tsukada <tsuka@...>
メーリングリストに投稿されたすべてのメールから grep コマンド で From: から始まる行を抽出し (grep -h '^From:' *)、それらを ソートして (sort)、数を数えた (uniq -c) のちに、数の多い順に ソート (sort -nr) するという一連のパイプラインに、Unix のテ キスト処理の神髄をみたような気がしたのである。
このワンライナーはシンプルで優れているが、本文中に From: か ら始まる行があるとその行も数えてしまうのが難点である。そこで、 本文中の From: を数えないように改良したツール mailrank を作 ることにした。ついでに、
- nkf*8コマンドを用いて、MIME で符号化された日本語の From: を復号する
- 結果の出力から行頭の "From: " を取り除く
という改良も施した。
mailrank のソースコード
#! /bin/sh test -z "$1" && echo "Usage: mailrank <directory>" && exit find "$1" -maxdepth 1 -type f | while read mail; do sed -n '1,/^$/p' "$mail" # へッダ部分だけ抽出 done | nkf -m | sed -n 's/^From: //p' |sort |uniq -c |sort -nr
mailrank コマンドを使って投稿者ランキングを調べるにはコマン ドラインから次のように実行する。
% mailrank ~/Mail/ml/foo 27 Satoru Takabayashi <satoru@...> 18 Toshiyuki Masui <masui@...> 8 Koji Tsukada <tsuka@...>
この例では ~/Mail/ml/foo ディレクトリに、1メール = 1ファイル で保存されているメールを対象に投稿者ランキングを集計している。
xface-gallery
xface-gallery を紹介する前に、X-Face について説明する。 X-Face は、メールのへッダに付加できる 48×48 ピクセルの白黒 2値のビットマップ画像である。メールに付加されたX-Face 画像は X-Face 対応のメーラーで読むことができる。私は、3年ほど前に X-Face の存在を知り、以来、自筆の「なまず君」のイラストを X-Face としてメールにつけている。
上のなまず君の X-Face は次のような文字列としてメールのへッダ に付加されている。
X-Face: ']LG0eU\ZOAZuj!aa4#GU(:U#7sS-@y6b(({~0z8.UUiu\F#dE\8(8@4sVnc68WE>OxquYD}Q"jfR8{@&6@dgW&!DGQ>!YOOd@JZ0>Ik}=%IY`Pc=jv+Y0ZL(DQ)r/J*i
X-Face を作るには、48×48 ピクセルの白黒 2値のビットマッ プ画像を用意し、Web上の Online X-Face Converter *9 というサービスを使って 変換するとよい。compface というツールと Emacs 用の X-Face ユーティリティ *10 を組合せて作成する方法もあるが、 Web上のサービスを利用した方が楽である。
Online X-Face Converter にファイルをアップロードすると
X-Face が生成される
自分の出すメールに X-Face をつけるには、このページで生成され た X-Face: ... という文字列をメールのへッダに付加すればよい。 X-Face 対応のメーラーに関する情報は津邑公暁氏によるページ *11 に詳しい。
前置きが長くなったが、xface-gallery は、指定したディレクトリ 以下のすべてのメールから X-Face を抽出して、一枚の大きなタイ ル状の画像ファイルを作るためのツールである。ある日、手元にど のくらい X-Face が集まっているかを知りたくなって作成した。 ~/Mail 以下のメールを対象に xface-gallery を実行したところ、 191個の X-Face が見つかり、次のような画像が生成された。
% xface-gallery ~/Mail 191 x-faces found.
xface-gallery のソースコード
#! /bin/zsh test -z "$1" && echo "Usage: xface-gallery <directory>" && exit i=0 tmpdir=xface-gallery.$$ mkdir $tmpdir find "$1" -type f |xargs xface-extract |sort |uniq |\ while read -r line; do echo -E "$line" | xface2png > $tmpdir/`printf "%04d" $i`.png; i=`expr $i + 1` done echo "$i x-faces found." x=`perl -e "print int(sqrt($i))"` rm -f xface-gallery.png montage -geometry 48x48 -tile $x $tmpdir/*.png xface-gallery.png rm -rf $tmpdir
xface-gallery は、zsh*12 を用いたシェ ルスクリプトとして作成した。次のような流れで処理を行っている。
- 指定されたディレクトリ以下のメールから find と xface-extract を用いて X-Face を抽出し
- それぞれの X-Face を xface2png を用いて PNG画像に変換し
- 最後に ImageMagick の montage コマンドで一枚の大きなタイル状の画像ファイル (xface-gallery.png) を生成する
xface-gallery の中で使われている xface-extract と xface2png は私が作成したシェルスクリプトである。xface-extract はメール のへッダから X-Face の抽出を行う。コマンドライン引数に複数の メールのファイルが指定されたときは、1 行につき1つの X-Face を連続的に出力する。sed と perl を組合せた複雑な処理を行って いるのは、複数行にまたがってメールのヘッダに含まれている X-Face を正しく抽出するためである。perl -n00le の 00 の部分 はパラグラフモードで入力を読み込む (空行単位で読み込む) ため のおまじないである。
xface-extract のソースコード
#! /bin/sh sed -n -e '/^X-Face:/,/^[^ \t]/ p' "$@" \ | perl -pe 's/^X-Face:\s*/\n /' \ | perl -ne 'print if /^$/ or /^[ \t]/' \ | perl -n00le 's/\s//g; print'
xface2png は X-Face を PNG画像に変換する処理を行う。標準入力 から読み込んだ X-Face を uncompface で復号し、その頭に "/* Width=48, Height=48 */" という情報を付加したものを netpbm *13 の icontopbm と ImageMagick の convert コマンドを用いて PNG 画像に変換してい る。なぜこのような処理が必要なのか実はよくわかっていないが、 Web 上で X-Face の復号方法を調べているうちに見つけたテクニッ クをそのまま流用している。uncompface は compface*14 のパッ ケージに含まれているツールである。
xface2png のソースコード
#!/bin/sh { echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | convert - png:-
X-Face は Emacs のマニアを中心として広まったという経緯があり、 一般にはまだあまり普及していないようである。メールをちょっと 楽しくする小道具として、X-Face をぜひ試してみてほしい。
メール関連のツールまとめ
メールを処理する小粒なツールとして mailrank と xface-gallery を紹介した。メールは基本的にへッダと本文が空行で分離されただ けのシンプルなフォーマットであるため、Unix 流のテキスト処理 との相性がいい。ここでは扱わなかったが、マルチパート形式の添 付ファイルを扱うには、シェルスクリプトから metamail コマンド *15 を使ったり、Ruby で TMail ライブラリ *16 を使うと 楽である。ちょっとした工夫で便利なツールを作れるでみなさんも 試してみてほしい。
画像関連のツール
長い間、私は Unix はテキスト処理には適しているが、画像処理に 関してはそうでもない、と考えていた。しかし、あるとき ImageMagick を知ってからこの考えは変わった。ImageMagick はコ マンドラインから画像処理を行うためのツールである。たとえば、 室内でデジタルカメラで撮影した写真の色合いが全体的に暗い場合、 コマンドラインから
% mogrify -gamma 1.4 *.jpg
のように実行すれば、カレントディレクトリのすべての JPEG ファ イルに対してガンマ補正*17をかけて色合いを明るくすることができ る。
ガンマ補正 1.4 をかける前とかけた後の画像
色合いの微妙な調整は GIMP などの画像編集ソフトのトーンカーブ の機能を使わないと難しいが、mogrify を使った大ざっぱな調整で も役に立つことは多い。ここでは画像ファイルを扱うために作った 小粒なツールを紹介する。
thumbnail
Web ページを作っているときに、画像のサムネイル (縮小画像) が ほしくなるときがある。大きすぎる画像をそのまま Webページに貼 ると不格好であるため、小さい画像をクリックすると元の大きな画 像が現われる、という仕組みにしたいからだ。サムネイルを作成す るには GIMP などの画像編集ソフトを使う方法もあるが、 ImageMagick の convert コマンドを使った方が楽である。たとえ ば foo.png を 50% のサイズに縮小した画像 foo-mini.png を作る にはコマンドラインから次のように実行すればいい。
% convert -geometry '50%' foo.png foo-mini.png
このように、convert コマンドは大変重宝するが、たくさんの画像 ファイルを扱う場合に、ファイルひとつづつに対してコマンドを打 つのは面倒である。そこで、複数の画像ファイルのサムネイルをま とめて作成するためのツール thumbnail を作成した。
thumbnail のソースコード
#! /bin/zsh geometry="50%" # コマンドラインオションを処理 while [ -n "$1" ]; do case $1 in # 横幅のサイズを指定するオプション -geometry ) shift test -z "$1" && echo "-geometry needs an argument" && exit geometry=$1 shift ;; * ) break ;; esac done test -z "$1" && echo "Usage: thumbnail <file>..." && exit for filename in "$@"; do test -f "$filename" || continue # ファイルが存在しなければスキップ suffix="${filename##*.}" # suffixの部分だけ取り出す (例: jpg) name="${filename%.*}" # suffixより前の部分だけ取り出す # サムネイルのサムネイルを作らないように除外する echo "$filename" | grep -- "-mini.$suffix" >/dev/null && continue thumbname="$name-mini.$suffix" convert -geometry $geometry "$filename" "$thumbname" origsize=`identify "$filename" | awk '{print $3}'` # 元のサイズ thumbsize=`identify "$thumbname" | awk '{print $3}'` # サムネイルのサイズ echo "$filename ($origsize) -> $thumbname ($thumbsize)" done
カレントディレクトリ以下のすべての PNG 画像に対して 50% のサ イズのサムネイルを作成するにはコマンドラインから次のように実 行すればいい。
% thumbnail -geometry '50%' *.png lazy-web.png (648x646) -> lazy-web-mini.png (324x323) xface-web-1.png (646x841) -> xface-web-1-mini.png (323x421) xface-web-2.png (637x469) -> xface-web-2-mini.png (319x235) xface.png (356x209) -> xface-mini.png (178x105) xface-gallery.png (624x746) -> xface-gallery-mini.png (312x373)
thumbnail も xface-gallery と同様に zsh を用いたシェルスクリ プトとして作成した。Unix では伝統的にシェルスクリプトは /bin/sh を用いる場合が多いが、私はちょっと複雑なシェルスクリ プトを書く場合には /bin/zsh を使うようにしている。
これは zsh の拡張機能を使いたいためということもあるが、 「bashism」なシェルスクリプトを書くのを避けるためという意味 も大きい。bashism なシェルスクリプトとは、#! /bin/sh で始まっ ているにも関わらず、 bash*18 の拡 張機能を使っているもののことである。
実際、私が使っている Red Hat Linux 7.3 では /bin/sh がbash であるため、/bin/sh のつもりでシェルスクリプト書いていても、 知らず知らずに bash の拡張機能を使ってしまい、他のプラット フォームの /bin/sh で動かないものができあがってしまうことが 多い。何が bash 拡張なのか調べるのは面倒であるし、自分が使う Unix ホストには必ず zsh が入っているので、迷ったときは zsh のシェルスクリプトとして書くようにしている。
dropshadow
Webページに画像を貼るときに、縁取りの線や影をつけたくなると きがある。たとえば下の画像は、端末のスクリーンショットをとっ たものだが、縁取りの線すらないので、そのまま白地のWebページ に貼りつけると、背景と一体化してしまう。
GIMPなどの画像編集ソフトを使えば縁取りの線や影をつけることは できるが、横着な私としては、やはりコマンド一発で行いたい。そ こで、画像に縁取りの線や影をつけるためのツールdropshadow を 作成した。
先ほどの端末のスクリーンショットに対して縁取りの線と影をつけるに はコマンドラインから次のように実行する。
% dropshadow -b terminal-1.png > terminal-2.png
また、-f オプションを指定すると写真のような枠をつけることも できる。
% dropshadow -f imoimo-1.jpg > imoimo-2.jpg
枠と影をつける前の画像とつけた後の画像
dropshadow のソースコード
#! /usr/bin/env perl use strict; use Image::Magick; use Getopt::Long; my $shadow_width = 10; my $with_border = undef; my $with_frame = undef; my $without_shadow = undef; my $border_margin = 0; my $border_color = "black"; my $shadow_color = "gray75"; main(); sub main { parse_options(); my $image = read_image(); draw($image); write_image($image); } # Getopt::Long モジュールを用いてコマンドラインオプションを解析する sub parse_options { Getopt::Long::Configure('bundling'); GetOptions('w|width=i' => \$shadow_width, 'b|border' => \$with_border, 'f|frame' => \$with_frame, 'n|no-shadow' => \$without_shadow, 'c|shadow-color=s' => \$shadow_color, 'C|border-color=s' => \$border_color); } sub read_image { my $image = new Image::Magick; if (@ARGV >= 1) { $image->ReadImage($ARGV[0]); # ファイルから画像を読み込む } else { $image->ReadImage('-'); # 標準入力から画像を読み込む } return $image; } sub draw { my ($image) = @_; draw_frame($image) if $with_frame; draw_border($image) if $with_border || $with_frame; draw_shadow($image) unless $without_shadow; process_transparent_color($image); } sub draw_frame { my ($image) = @_; $image->Set(bordercolor => 'gray'); $image->Border(geometry => "1x1"); $image->Set(bordercolor => 'white'); my $framewidth = $shadow_width / 2; $image->Border(geometry => "${framewidth}x${framewidth}"); } sub draw_border { my ($image) = @_; $image->Set(bordercolor => $border_color); $image->Border(geometry => "1x1"); } sub draw_shadow { my ($image) = @_; my ($width, $height, $magick) = $image->Get('width', 'height', 'magick'); if ($magick eq "GIF" || $magick eq "PNG") { # 透過色を設定する。 #facade は使われていないことが前提... $image->Set(bordercolor => '#facade'); } else { $image->Set(bordercolor => 'white'); } $image->Border(geometry => "${shadow_width}x${shadow_width}"); $image->Crop(x => $shadow_width, y => $shadow_width); my $width2 = $width + $shadow_width; my $height2 = $height + $shadow_width; $image->Draw(fill => $shadow_color, primitive=> 'rectangle', stroke => $shadow_color, points => "$width,$shadow_width $width2,$height2"); $image->Draw(fill => $shadow_color, primitive=> 'rectangle', stroke => $shadow_color, points => "$shadow_width,$height $width2,$height2"); } sub process_transparent_color { my ($image) = @_; my ($magick) = $image->Get('magick'); if ($magick eq "GIF" || $magick eq "PNG") { $image->Transparent(color => '#facade'); # 透過色を設定 } } sub write_image { my ($image) = @_; my ($magick) = $image->Get('magick'); binmode STDOUT; $image->Write("$magick:-"); }
dropshadow は ImageMagick に付属する PerlMagick という Perl 用のライブラリを用いて作成した。PerlMagick はバージョンによ る非互換があるらしく、この dropshadow コマンドを別の計算機に 持っていったら動かないことがあった。最新の ImageMagick 5.5.1 では動作することを確認している。
sync-day-by-day
デジタルカメラを手に入れてから写真をよく撮るようになった。フィ ルムのカメラではほとんど写真を撮らなかったが、デジタルカメラ ならゴミのような写真を撮りまくってもコストを気にする必要がな く、気楽に撮れる。そんなふうにして、毎日のように写真を撮って いると、写真の整理をどうするか考えなければいけなくなった。1 枚1 枚の写真にファイル名をつけていたら気が遠くなってしまうし、 残しておきたい写真を厳選するのも面倒である。そこで、 2002/11/15 のような日付ごとのディレクトリに写真を分類すると いう方針を採ることに決めた。
私が使っているデジタルカメラは写真に dcp_0001.jpg, dcp_0002.jpg のようなファイル名をつけて、コンパクトフラッシュ カードに保存する。ファイルのタイムスタンプには写真を撮った日 時が記録されているので PC へファイルをコピーするときはこのタ イムスタンプの情報を元にすればよい。そこで、タイムスタンプを 元にして日付ごとのディレクトリに分類しながらファイルのコピー を行うツール sync-day-by-day を作成した。sync-day-by-day は 次のような特徴を持っている。
- 日付のディレクトリを自動的に作成する
- 新しいファイルのみをコピーする (すでにあるファイルはコピーしない)
- コピーした際にタイムスタンプを保持する
/mnt/camera にマウントしたコンパクトフラッシュから pictures というディレクトリに、ファイルをコピーするにはコマンドライン から次のように実行する。
% sync-day-by-day /mnt/camera pictures /mnt/camera/dcp_0001.jpg -> pictures/2002/11/13/dcp_0001.jpg /mnt/camera/dcp_0002.jpg -> pictures/2002/11/13/dcp_0002.jpg /mnt/camera/dcp_0003.jpg -> pictures/2002/11/13/dcp_0003.jpg /mnt/camera/dcp_0004.jpg -> pictures/2002/11/13/dcp_0004.jpg /mnt/camera/dcp_0005.jpg -> pictures/2002/11/13/dcp_0005.jpg /mnt/camera/dcp_0006.jpg -> pictures/2002/11/14/dcp_0006.jpg /mnt/camera/dcp_0007.jpg -> pictures/2002/11/14/dcp_0007.jpg /mnt/camera/dcp_0008.jpg -> pictures/2002/11/15/dcp_0008.jpg /mnt/camera/dcp_0009.jpg -> pictures/2002/11/15/dcp_0009.jpg /mnt/camera/dcp_0010.jpg -> pictures/2002/11/15/dcp_0010.jpg /mnt/camera/dcp_0011.jpg -> pictures/2002/11/15/dcp_0011.jpg
sync-day-by-day のソースコード
#! /usr/bin/env ruby require 'date' require 'ftools' def usage puts "Usage: sync-day-by-day <source directory> <destination directory>" exit end def nodir(dir) puts "No such directory: " + dir exit 1 end def same_file? (f1, f2) File.symlink?(f1) == false && File.symlink?(f2) == false && File.file?(f1) && File.file?(f2) && File.size(f1) == File.size(f2) && File.mtime(f1) == File.mtime(f2) end def parse_options usage if ARGV.length != 2 nodir ARGV[0] unless File.directory?(ARGV[0]) nodir ARGV[1] unless File.directory?(ARGV[1]) return ARGV end def datedir(date) sprintf("%02d/%02d/%02d", date.year, date.month, date.day) end def copy(src, dest) atime = File.atime(src) mtime = File.mtime(src) File.copy src, dest File.utime(atime, mtime, dest) end def sync_day_by_day(src, dest) Dir.glob("#{src}/*") {|src_filename| next unless File.file?(src_filename) dest_dir = File.join(dest, datedir(File.mtime(src_filename))) dest_filename = File.join(dest_dir, File.basename(src_filename)) unless File.file?(dest_filename) && same_file?(src_filename, dest_filename) printf("%s -> %s\n", src_filename, dest_filename) File.mkpath(dest_dir) unless File.directory?(dest_dir) copy(src_filename, dest_filename) end } end def main src, dest = parse_options sync_day_by_day(src, dest) end main
sync-day-by-day は Ruby に標準で付属している ftools.rb とい うライブラリを使ってファイルのコピー (File.copy) や 2002/11/13 のような多階層のディレクトリの作成 (File.mkpath) を行っている。
画像関連のツールのまとめ
画像を処理する小粒なツールとして thumbnail, dropshadow, sync-day-by-day を紹介した。Web ページやレポートなどで大量の 画像ファイルを扱う場合に、こうしたツールを用意しておくと作業 がずいぶん楽になる。画像編集ソフトでひとつづつ画像ファイルを 開いて処理するのが面倒だと感じたことがある方はぜひ試してほし い。
おわりに
今回は、私が開発した小粒なツールをWeb関連、メール関連、画像 関連に絞ってとりあげ、あわせて小粒なツールのプログラミングの ノウハウを紹介した。こうした小粒なツールを作ることは日常の作 業を快適にするだけでなく、プログラミングに上達するいい方法だ と思っている。おそらく皆さんの ~/bin ディレクトリにも、小粒 なツールがそろっているのではないかと思う。便利なツールやノウ ハウなどがあったらぜひ教えてもらえるとうれしい。
なお、本連載で作成したプログラムのソースコードは筆者のページ *19 から入手可能である。
おまけ: 超小粒なツール
あるとき、PC のスピーカが正常に動作しているかを調べるために、 音を鳴らす実験をしたくなった。しかし、音を鳴らす方法がとっさ に思いつかずに、一瞬考えたのちに端末でビープ音を鳴らせばいい と気づいた。そのときに作ったツールが beep である。
beep のソースコード
#! /usr/bin/env perl print "\x07";
beep は \x07 という制御記号を使って端末からビープ音を鳴らす という、たった 2行の超小粒なツールである。コマンドラインから echo ^G *20 と実行してビープ音を鳴 らすこともできるが、コマンドを作っておいた方がわかりやすい。 コマンドラインから次のよう実行するとピッと音が鳴る。
% beep
ただこれだけのツールだが sleep コマンドと組合せると
% sleep 180 && beep # 3分後にピッと鳴る
といった応用や、zsh を使っているなら
% repeat 100 beep # 100回ビープ音を鳴らす
といったこともできる。今の速い計算機で repeat 100 beep を実 行したときと、昔の遅い遠い計算機で実行したときではビープ音の 間隔が変わって違う音色に聞こえる。perl のプロセス呼び出しに かかる時間が影響するようである。こんな些細なツールでもなかな か遊べるところがおもしろい。