シェルスクリプト補完計画

先日のエントリーで test(1) について教えてもらい、調子にのって command1 && [ $? -eq 0 ] command2 || command3 なんてもの凄いコメントをしてしまったので、その間違いを正すのと先日のシェルスクリプトを改めて解説、さらに test(1) についてもう一度、勉強し直します。
そもそもこういうことになったのは && ( || ) をちゃんと理解していなかった ( man page をみていなかった ) からでした。
kogule 様、ありがとうございました。


さて、この && をまず man ページで調べてみようと思います。
今回は sh に限った話なので、 sh(1) を引いてみました。
以下が該当個所を引用したものです。

短絡リスト演算子 (Short-Circuit List Operators)
   ``&&'' と ``||'' は AND-OR リスト演算子です。 ``&&'' は最初のコマンドを実
    行し、もし最初のコマンドの終了ステータスが 0 ならば次のコマンドを実行しま
    す。 ``||'' も同様ですが、最初のコマンドの終了ステータスが 0 でない場合
    に、次のコマンドを実行します。 ``&&'' と ``||'' の優先順位は同じです。

http://www.jp.freebsd.org/cgi/mroff.cgi?subdir=man&lc=1&cmd=&man=sh&dir=jpman-5.4.0%2Fman§=0


意味がわかりません。
毎度のことなので、読み解いてみます。
まず && の例題を見てみます。

#!/bin/sh
cd /bin && echo $?
exit


これは /bin に移動してその直前のコマンド、この場合は cd の終了ステータスを表示するというものです。
このシェルスクリプトでは /bin に移動出来ている、つまり終了ステータスが 0 なので画面には 0 と表示されています。
では次に || を見てみます。

#!/bin/sh
cd /bin || echo $?
exit


これは /bin に移動できている、つまり終了ステータスに 0 が返されているので画面には何も表示されません。
では次の例題はどうでしょうか。

#!/bin/sh
cd /hoge || $?
exit


これは /hoge というディレクトリは存在しないので「cd: can't cd to /hoge」とその exit 値が表示されていると思います。
さて、ここからが本題なんですが、上記の && に test(1) を加えてみます。
以下は test(1) の man page です。

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

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

http://www.jp.freebsd.org/cgi/mroff.cgi?subdir=man&lc=1&cmd=&man=test&dir=jpman-5.4.0%2Fman§=0


上記を踏まえた上で下記のシェルスクリプトをご覧ください。

#!/bin/sh

[ -x /usr/sbin/portsnap ] && echo test 1

[ ! -x /usr/sbin/portsnap ] && echo test 2

[ -x /usr/local/sbin/portsnap ] && echo test 3

[ ! -x /usr/local/sbin/portsnap ] && echo test 4

exit


環境によって出力が違うかもしれませんが、私の環境では下記の出力でした。

test 1
test 4


まず三行目の記述ですが、これは /usr/sbin/portsnap が存在し、実行可能なので画面に「 test 1 」と表示されました。
この時の test(1) の終了ステータスは 0 なので && 以降の echo は実行されます。


次に五行目の記述ですが、これは /usr/sbin/portsnap が存在し、実行可能ですが、 "!" によって偽 (1) になるので echo は実行されません。
この時の test(1) の終了ステータスは 1 なので && 以降の echo は実行されません。


次に七行目の記述ですが、これは /usr/local/sbin/portsnap が存在しない ( 実行可能でない ) ので echo は実行されません。
この時の test(1) の終了ステータスは 1 なので && 以降の echo は実行されません。


最後に九行目の記述ですが、これは /usr/local/sbin/portsnap が存在しない ( 実行可能でない ) ですが、 "!" によって真 (0) になるので「 test 4 」と表示されました。
この時の test(1) の終了ステータスは 0 なので && 以降の echo は実行されます。


別に当たり前の事と言えば当たり前なのですが、ここを私はどうも理解出来ていませんでした。
これまでのことを踏まえた上で 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


上記のシェルスクリプトの中から抜粋したものが下記の記述になります。

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


何故こんなややこしい記述なんだろうと今まで思っていましたが、今日初めてその意図が理解出来たと思います。
これでやっとこのシェルスクリプトを使っていろいろと遊べる。と思いたい。 ( 間違いがなければ )


関係ないですが、この夏場の暑さはどうにかならないですかね。