低レベルインターフェースと高レベルインターフェース

  • ref:J

結局インターフェースの問題なのでは。
単機能のコマンドを組み合わせて、複雑な処理を組み合わせるってのは組み合わせ方次第で、いろんなことが出来るようになるという点で優れてはいるんだけど、そりゃ毎回おんなじことやっていりゃ面倒だし、1つのコマンドで実行できたほうが簡単ではある。gzip/bzip2 と tar だって、みんな GNU tar の場合 -z オプションとか -j オプションとか使うよね(最近は GNU tar はファイルの中身をみて自動認識するからつけなくても展開できるけど)。いちいち明示的にパイプしない。まぁ、そもそもあれが GNU tar の独自拡張オプションだということ自体知らない人が多いけど(そんでもって GNU tar じゃない環境にいくとたちまちアーカイブを展開できなくなる)。
でだ。実は、悪いのは組み合わせないことじゃなくて、組み合わせにくいことなんじゃないだろうか。美しさとかそういう問題じゃなくて。
たとえば、grep が「指定ディレクトリ以下のファイルから特定のパターンを検索する」コマンドなら検索ファイルの条件を指定したい場合は grep を拡張するしかないわけだけど、そうじゃないから find と組み合わせて複雑なことが出来るわけだ。要するに再利用性の問題。
同じことはプログラミングにもいえて、いきなりモノリシックに機能を1つの関数に作りこむんじゃなくて、単純な機能を複数作って組み合わせて高レベルインターフェースを構築したほうが再利用性は高いし、柔軟性も高い。
で、grep と find の問題は適当に Facade 的なものを用意すれば使いやすくなっていいんじゃね、とか思ったので、ちょっと作ってみた。

#!/bin/bash

function usage {
    help_message="Usage: $0 path [grep_args] [-- [find_args]]"
    if [ "$1" -eq 0 ]; then
        echo "$help_message"
    else
        echo "$help_message" 1>&2
    fi

    exit "$1"
}

if [ $# -eq 0 ]; then
    usage 2
elif [ "$1" = "--help" -o "$1" = "-h" ]; then
    usage 0
fi

declare -a grep_args
declare -a find_args

path="$1"; shift

while [ $# -gt 0 ]; do
    arg="$1"; shift
    if [ "$arg" = "--" ]; then
        break
    else
        grep_args[${#grep_args[@]}]="$arg"
    fi
done

if [ ${#grep_args[@]} -eq 0 ]; then
    # grep args not found
    usage 2
fi

while [ $# -gt 0 ]; do
    arg="$1"; shift
    find_args[${#find_args[@]}]="$arg"
done

find "$path" -type d \( -name .svn -o -name CVS -o -name RCS \
    -o -name _darcs -o -name blib \) -prune \
    -o -type f "${find_args[@]}" -print | perl -ne 'chomp; print $_, "\0" if -T' | \
    xargs -0 -e grep "${grep_args[@]}" -- /dev/null

こんな感じで使う。

grep-find . background-image -- -name "*.css"