ひがきの日記

2009-05-09

シェルスクリプトの真髄

部屋の掃除をしていたら、昔の UNIX MAGAZINE のコピーが出てきた。*1

職場で Unix シェルスクリプトの講師をしていたときに、みんなに紹介したもの。

…… 私が初めて UNIX マシンや C 言語を触りだしたころのことです。 プログラミングの練習ということで、 「テキストファイルの行を逆順に出力するプログラムを作れ」という 課題を出されました。…… ある人はこれを UNIX のコマンドのみで実現しました。……
#!/bin/sh
cat -n "$@" | sort -nr | cut -c 8-
この方法の優れたところは、最初に出力を得るまでが高速だという点です。…… さらに、UNIX のコマンドを流用しているため安定度が高いという点も見逃せません。…… また、巨大な入力が与えられた場合を考えてみてください。……

*1UNIX MAGAZINE 2003.5, ASCII, 特集 プログラミング入門 6, 今泉貴史

2009-02-14

プロンプトを出す。

ユーザに何か入力してもらうためにプロンプトを出したい。

プロンプトは出力に混じらないように標準エラー出力に出す。

$ cat sample
    ...
echo "Are you sure? " >&2
read ask

case $ask in
[Yy]) ... ;;
    ...

$ ./sample
Are you sure?
_

echo にそのままプロンプト文字列を渡すと、プロンプトなのに改行されてしまう。


改行の抑止

BSD
-n オプションを使う。
echo -n "Are you sure? "
SystemV 系
\c を表示する。
echo "Are you sure? \c"

どっちでも行けるようにするには、echo 自身にどっちの方法が使えるのか聞いてみればいい。

$ cat sample
    ...
if [ x`echo -n` = x ]; then
  ECHO_N='-n'
else
  ECHO_C='\c'
fi

echo $ECHO_N "Are you sure? $ECHO_C" >&2
read ask

case $ask in
[Yy]) ... ;;
    ...

$ ./sample
Are you sure? _

-n オプションの使えない echo なら、echo -n すると -n と出力される。


相手の確認

入力してくれる相手が人でない場合に、いちいちプロンプトを出しても仕方ない。*1

相手が端末でなければプロンプトを出さない。

    ...
[ -t 0 ] && echo $ECHO_N "Are you sure? $ECHO_C" >&2
read ask
    ...

まとめ

  • プロンプトは標準エラー出力に出す。
  • echo で改行しない方法には方言がある。
  • 相手は端末でなければプロンプトを出さない。

*1:そうとも言いきれないが ……

2009-02-11

ワイルドカード

普通はファイル名の指定に使うけど、case 文のパターンにも使える。

$ ls
bar     foo     foobar  food    fool    foot

$ ls foo?           # 1 文字にマッチ
food    fool    foot

$ ls foo*           # 0 文字以上にマッチ
foo     foobar  food    fool    foot

$ ls foo[a-n]       # 範囲内の 1 文字にマッチ
food    fool

$ ls foo[!a-n]      # 範囲外の 1 文字にマッチ
foot

$ ls ?oo[a-n]*      # 上記の組み合わせ
foobar  food    fool

いつ誰が評価するのか

Unix ではシェルが評価し*1DOS では各コマンドが評価する。

シェルワイルドカードをいつ評価するのか?

$ ls
bar     foo     foobar  food    fool    foot

$ wildcard=foo?

$ echo $wildcard
food fool foot

$ cd /
$ echo $wildcard    # ここで評価すると
foo?                # マッチしなかった

コマンドに引数として渡す際に評価している。

ちなみに変数に入れたワイルドカードそのものを見たいときは、どうするか?

$ ls
bar     foo     foobar  food    fool    foot

$ wildcard=foo?

$ echo $wildcard
food fool foot      # 展開されてしまった

$ echo "$wildcard"
foo?

文字列にしてやればいい。


マッチしなかったとき

上の例でも見たように、ワイルドカードにマッチするファイルがなかった場合は、その文字列がそのまま使用される。

$ for f in foo??; do rm $f; done
rm: foo??: No such file or directory

マッチしなくても for の中は 1 回 実行される。


大量にマッチしたとき

ワイルドカードを展開するバッファは有限で、そのサイズはシェル自身をコンパイルしたときに静的に決まる。(みたい)

すなわち、あまりたくさんのファイルがマッチしてしまうと、展開に失敗する。

$ ls many/files/* || echo NG: $?
-bash: /bin/ls: Argument list too long
NG: 126

*1:find(1) など、ワイルドカードを処理するコマンドも存在する。