改訂版 シェルスクリプトについて ( ports 編 )

id:otsune 様が test(1) なんて不思議な man を言っていたので見てみました。
まさかこんな罠があるなんて・・・勉強不足なだけとか言わない。


先日のエントリーで関連のある項目を抜き出してみました。

     -x file       True if file exists and is executable.  True indicates only
                   that the execute flag is on.  If file is a directory, true
                   indicates that file can be searched.

     ! expression  True if expression is false.


以下は日本語マニュアルからです。

     -x file       file が存在し、実行可能であれば真になります。真ということ
                   は、実行可能フラグが立っていることを表すに過ぎません。 file
                   がディレクトリの場合、真は file が検索可能であることを表し
                   ます。

     ! expression  expression が偽ならば真になります。


以下の構文は先日のエントリーの問題だった箇所から。

[ ! -x /usr/local/sbin/portsnap ] && exit 0


&& は 倫理積 論理積 ( 2006/07/09 追記:倫理積ではなく、論理積です。すいませんでした。コメントをくださった kogule 様、ありがとうございました。 ) らしいので条件に当てはまる場合には exit に 0 を返す、つまりシェルスクリプトをその場で終了って事ですね。
この 倫理積 論理積とかの言い回しがややこしくて脳内フィルターかけていないと、理解するどころか何を言ってるのがわかりません。
そんなヘタレだからなかなか進歩しないんでしょうけど。 :-)


次の問題の箇所は下記の通り。 ( 抜粋 )

echo
echo "Ports/packages update check:"
(portsnap cron && portsnap update) >/dev/null 2>&1

if [ $? -eq 0 ]; then
        portsdb -u >/dev/null 2>&1
        portversion -vL=
        rc=0
else
        echo "Failure of portsnap(*ERROR*)"
        rc=1
fi
echo


まず上記の条件分岐ですが、これは $? を読み間違えていたことからおかしくなったんだと思います。
$? という変数は最後に実行したコマンドの exit 値を返します。
例えば下記のようなシェルスクリプトを組んだ場合、カレントディレクトリが表示されます。

#!/bin/sh
cd ~/
if [ $? -eq 0 ]; then
        pwd
        rc=0
else
        echo "hoge"
        rc=1
fi

exit $rc


-eq
は条件の評価のひとつで、上記の例だと $? が 0 と等しい場合はそのまま実行する、ということになります。
上記でも解説しましたが、 $? は最後に実行した exit 値を持っています。
このシェルスクリプトでは cd ~/ となっいて、正常終了したので $? の値は 0 です。
まとめると、 $? は 0 と等しいのでそのまま構文を続ける、ということにます。


では上記のことを踏まえた上で id:hiro-ueda 様のシェルスクリプトに戻ってみましょう。
今度は全文掲載です。
( id:hiro-ueda 様からは教材として使用する許可をもらいました。 id:hiro-ueda 様、ありがとうございます。 )

#!/bin/sh
#
# $Id: 499.status-pkgupdate,v 1.2 2005/08/01 02:15:07 ueda Exp $
#

# If there is a global system configuration file, suck it in.
#
if [ -r /etc/defaults/periodic.conf ]
then
    . /etc/defaults/periodic.conf
    source_periodic_confs
fi

export PATH=$PATH:/usr/local/sbin:/usr/local/bin
export LANG=C

[ ! -x /usr/local/sbin/portsnap ] && exit 0

echo
echo "Ports/packages update check:"
(portsnap cron && portsnap update) >/dev/null 2>&1

if [ $? -eq 0 ]; then
        portsdb -u >/dev/null 2>&1
        portversion -vL=
        rc=0
else
        echo "Failure of portsnap(*ERROR*)"
        rc=1
fi
echo

exit $rc


まずは上から順に読み解いていきます。
なお、コメント行には触れません。

#!/bin/sh

どのシェルを使うのか指定する。

if [ -r /etc/defaults/periodic.conf ]
then
    . /etc/defaults/periodic.conf
    source_periodic_confs
fi

test(1) を見てみる。

     -r file       file が存在し、それが読み込み可能であれば真になります。

なので、先日のエントリーも微妙に間違っていたかも。
とりあえず『おまじない』と考えておけばいいと思います。 ( ダメ? )

export PATH=$PATH:/usr/local/sbin:/usr/local/bin
export LANG=C

これはそのまま。
環境変数ロケールを通しています。
( /bin/tcsh の場合は export が setenv になる。 ( はず ) )

[ ! -x /usr/local/sbin/portsnap ] && exit 0

上記で解説した通り。

if [ $? -eq 0 ]; then
        portsdb -u >/dev/null 2>&1
        portversion -vL=
        rc=0
else
        echo "Failure of portsnap(*ERROR*)"
        rc=1
fi
echo

exit $rc

これでも上記で解説した通り。
$rc については昨日の解説でほぼ間違いないと思います。


とりあえずこんな感じでしょうか。
昨日の敗因をまとめると、
シェルスクリプトの構文をひとつひとつ読み解いていった事。
・exit 値を何かと勘違いしていた事。
こんな感じだと思います。


そして今後の方針のまとめとして、
シェルスクリプトに限らず、まずは上から下まで構文を読み取った上で、解説する時には関連項目 ( 参考文献や man など ) を一緒に提示する。
・諦めない心。 ( ぉ
意外と他の言語に共通するものがあるのでこういった所から入っていけば案外 C も独学でいけそうな気がしてきました。


余談ですが、『 man [ 』なんて使い方があるなんて思ってもいませんでした。
多分純正のゲイツ様に教育された Windows 使いが何故 Unix を敬遠するのか少しわかった気がします。
・・・違う?