bashによるシェルスクリプトの小技(2)

前回(bashによるシェルスクリプトの小技(1))に引き続き、シェルによって自動的に値が設定される特殊な変数について紹介する。特殊な変数を参照することにより、様々な情報を取得することができる(ただし、これらの変数には自分で任意の値を設定することはできない)。

さて、まずは特殊変数を一覧でまとめてみる。お馴染みのものが多いが、最後の2つ(特に最後のPIPESTATUS)についてはきっと今まで知らなかった人もいるんじゃないだろうか。シェルの中でパイプすると途中のコマンドのリターンコード、拾えないとか思っていませんか?今回のポイントとしては、「1. PIPESTATUS変数について」と「2. 特殊変数 $@と$*の違いについて」の2点を主に説明する。

特殊変数一覧表

変数名 自動的に設定される値
$? 直前に実行されたコマンドの終了ステータスが設定される変数。正常終了の場合は「0」、異常終了の場合は「0 以外」がセットされる。シェルスクリプトでは exit コマンドに与えた引数がそのシェルスクリプトの終了ステータスとなる。 例えば「exit 2」と書いたならば、そのシェルスクリプトの終了ステータスは「2」。※ 関数の return も同様に、指定した引数がその関数の終了ステータスとなる。
$! バックグラウンドで実行されたコマンドのプロセスID が設定される変数。「 sleep 100 & 」のように & を付けて実行すると、そのコマンドはバックグラウンドで実行される。 上記の場合、「 $! 」には sleep コマンドの PID (プロセスID)がセットされている。
$- set コマンドで設定されたフラグ、もしくはシェルの起動時に指定されたフラグの一覧が設定される変数。変数 $- の値が「abc」ならばフラグは「-abc」ということになる。
$$ コマンド自身の PID (プロセスID)が設定される変数。シェルスクリプト内で「 echo $$ 」のように記述すると、そのシェルスクリプトが実行されたときの PID 取得できる ( PID は OS 上で一意になるよう割り当てられる)。この変数を使用したテクニックで、シェルスクリプト内で作成するファイル名の拡張子に変数 $$ を指定するというものがある ( ex. tempfile.$$ )。変数 $$ を拡張子に指定することで、シェルスクリプト実行毎に作成するファイル名を変えることができるので、 同一シェルスクリプトを同時実行した場合に、同時実行された 各スクリプトが同時に同一ファイルに出力してしまうことを防止できる。
$# 実行時に指定された引数の数が設定される変数。「 $ ./command.sh AAA BBB CCC 」のように実行された場合、シェルスクリプト command.sh 内で変数 $# を参照するとその値は「 3 」となる。
$@ シェルスクリプト実行時、もしくは set コマンド実行時に指定された全パラメータが設定される変数。変数 $* と基本的に同じだが「""」で囲んだときの動作が異なる。
$* シェルスクリプト実行時、もしくは set コマンド実行時に指定された全パラメータが設定される変数。変数 $@ と基本的に同じだが「""」で囲んだときの動作が異なる。
$LINENO この変数を使用している行の行番号が設定される変数。デバッグに便利な行番号が設定されている変数。例えば、シェルスクリプト内の5行目でこの変数を使用したとすると、そこでの変数 LINENO の値は"5"となる。
${PIPESTATUS[@]} パイプで連結した各コマンドの終了ステータスが設定される変数(配列)。パイプ(「|」)により連結された各コマンドの終了ステータスが設定設定されている変数(配列)。 変数 $? ではパイプの一番最後にあるコマンドの終了ステータスしか参照することができないが、この変数を使用することでパイプの先頭、 もしくは途中にあるコマンドの終了ステータスを参照することができる (残念ながら bash のみで ksh では使用できない)。

1.PIPESTATUS変数について

  • true/false/exitコマンドを対話実行後、パイプで連結された各コマンドの終了ステータスを取得してみる。


$ true | false | (exit 3) | (exit 8)
$ echo ${PIPESTATUS[@]}
0 1 3 8
  • シェル内で使ってみる
    • 先行で実行された処理のリターンコードを拾ってエラーメッセージを表示する例(以下の例ではwcコマンドに存在しないオプション「-k」をつけてエラーにしている)
    • こんなサンプルスクリプトで検証


$ cat test02.sh
#!/bin/bash
COMMAND1='wc -k'
COMMAND2='wc -l'
$COMMAND1 | $COMMAND2
test ${PIPESTATUS[0]} -ne 0 && echo "[$COMMAND1] error!"
    • 実行結果は以下の通り。ちゃんと拾えていることがわかる


$ bash test02.sh
wc: オプションが違います -- k
詳しくは `wc --help' を実行して下さい.
0
[wc -k] error!

2.特殊変数 $@と$* の違いについて

  • 変数 $@
    • 「""」で囲んだときは、格納されている個々の値をそれぞれ「""」で囲んだ状態で展開される。例えば、格納されている値が、「aaa」「bbb」「ccc」 (※ $ set aaa bbb ccc)ならば、「"$@"」は、「"aaa" "bbb" "ccc"」のように展開される。
  • 変数 $*
    • 変数 $@ とは異なり、「""」で囲んだときは、格納されている全ての値を一つの「""」で囲んだ状態で展開される。例えば、格納されている値が、「aaa」「bbb」「ccc」 (※ $ set aaa bbb ccc)ならば、「"$*"」は、「"aaa bbb ccc"」のように展開される。
  • 実際に結果を比べてみる
    • 配列を使ってVAR変数に3つの値をいれ、test03関数で認識した引数を拾ってみる。
    • こんなサンプルスクリプトで検証


$ cat test03.sh
#!/bin/bash
VAR=(aaa bbb ccc)
test03(){
n=1; while test $n -le $#
do
v=`eval echo '$'$n`
echo "ARG${n}: $v"
n=`expr $n + 1`
done
}
echo '===== [$@] ====='; test03 "${VAR[@]}"
echo '===== [$*] ====='; test03 "${VAR[*]}"
    • 実行結果は以下の通り。「$*」を使った場合には、「"aaa bbb ccc"」という1つの引数として解釈されていることがわかる。


$ bash test03.sh
===== [$@] =====
ARG1: aaa
ARG2: bbb
ARG3: ccc
===== [$*] =====
ARG1: aaa bbb ccc