コマンドライン引数の基本 ($1 ~ $9)
bash シェルスクリプト実行時にコマンドラインで渡した引数を参照するには、次のようなパラメーター参照用の特殊変数を使用します。
$1… 第 1 パラメーター$2… 第 2 パラメーター$3… 第 3 パラメーター
パラメーターの参照には、$1 〜 $9 が使用できます。
使用することは余りないと思いますが、10 番目以降のパラメータを参照したいときは、${10} のように数値を {} で囲めば参照できます。
#!/bin/bash
echo '1 番目: ' "$1"
echo '2 番目: ' "$2"
echo '3 番目: ' "$3"$ ./sample.sh AAA BBB "CCC DDD"
1 番目: AAA
2 番目: BBB
3 番目: CCC DDD
コマンドライン引数を指定しなかった場合は、対応する変数は空になります。
$ ./sample.sh AAA
1 番目: AAA
2 番目:
3 番目:
echo の引数として変数の値を渡すときは、"$1" のようにダブルクォートで囲む癖をつけましょう。
$1 のようにそのまま記述してしまうと、変数の値として連続するスペースが含まれていたときに 1 つのスペースにまとめられてしまいます。コマンドライン引数にデフォルト値を指定する (${1:-XXX})
コマンドライン引数が指定されなかったときに使用する値として、デフォルト値を指定しておくこともできます。
デフォルト値は、パラメーター参照時に ${1:-デフォルト値} という形で指定します。
下記の例では、1 番目、2 番目、3 番目のコマンドラインパラメーターのデフォルト値を、それぞれ AAA、BBB、CCC に設定しています。
#!/bin/bash
echo '1 番目: ' "${1:-AAA}"
echo '2 番目: ' "${2:-BBB}"
echo '3 番目: ' "${3:-CCC}"$ ./sample.sh 100
1 番目: 100
2 番目: BBB
3 番目: CCC
指定されたコマンドライン引数の数を取得する ($#)
シェルスクリプト実行時に渡されたパラメータの数は、$# という特殊変数で取得することができます。
#!/bin/bash
echo 引数の数: $#$ ./sample.sh
引数の数: 0
$ ./sample.sh AAA
引数の数: 1
$ ./sample.sh AAA "BBB CCC DDD"
引数の数: 2
コマンドライン引数をループで順番に処理する ($@, $*)
for ループを使う方法
シェルスクリプト実行時に渡されたコマンドライン引数は、特殊変数 $@ を使って参照することができます。
以下の例では、for in ループを使って、$@ の要素を 1 つずつ取り出して処理しています(おまけでカウンター変数 $count をインクリメントしながらループしてます)。
#!/bin/bash
count=0
for arg in "$@"; do
let count=$count+1
echo "$count: $arg"
done$ ./sample.sh AAA BBB "CCC DDD"
1: AAA
2: BBB
3: CCC DDD
実は、特殊変数の指定部分 in "$@" は省略して記述することができます。
#!/bin/bash
count=0
for arg; do
let count=$count+1
echo "$count: $arg"
done明示的に in "$@" を記述する場合は、$@ の部分をダブルクォートで囲むことを忘れないようにしてください(詳しくは後述)。
while ループ + shift を使う方法
shift コマンドを実行することで、$1〜$9 に格納されたパラメータを 1 つずつ前にシフトすることができます。
shift コマンドを実行するたびに $1 に格納されていたパラメータは破棄され、パラメータ数を表す $# の値が 1 つずつ減っていきます。
下記の例では、パラメータ数 ($#) が 1 以上の間、処理を続ける while ループを定義しています。
$1 はコマンドラインパラメータの最初の要素を参照する変数ですが、直後の shift によってパラメータを 1 つずつシフトしているので、結果としてすべてのパラメータを順番に参照することができます。
#!/bin/bash
count=0
while [ "$#" -ge "1" ]; do
let count=$count+1
echo "$count: $1"
shift
done$ ./sample.sh AAA BBB CCC
1: AAA
2: BBB
3: CCC
引数全体を 1 つの文字列として取得する ($*)
$@ と似た特殊変数に $* があります。
どちらもパラメータ全体を表す特殊変数ですが、$@ が各パラメータを個別に保持しているのに対し、$* はすべてのパラメータを結合した 1 つの文字列 になっています。
それぞれ、ダブルクォートで囲んで参照した場合と、囲まずに参照した場合の展開方法も含めて理解しておきましょう。
下記のようなサンプルコードと、その振る舞いを対応付けて覚えてしまうのが手っ取り早いです。
#!/bin/bash
echo -e '\n=== "$@" の場合 ==='
for arg in "$@"; do
echo "$arg"
done
echo -e '\n=== $@ の場合 ==='
for arg in $@; do
echo "$arg"
done
echo -e '\n=== "$*" の場合 ==='
for arg in "$*"; do
echo "$arg"
done
echo -e '\n=== $* の場合 ==='
for arg in $*; do
echo "$arg"
done$ ./sample.sh "100 200" "CCC DDD"
=== "$@" の場合 ===
100 200
CCC DDD
=== $@ の場合 ===
100
200
CCC
DDD
=== "$*" の場合 ===
100 200 CCC DDD
=== $* の場合 ===
100
200
CCC
DDD
1 番目の方法が、おそらく想定通りの振る舞いに近いと思います。
"$@" と指定することで、内部的には下記のようにそれぞれの要素をダブルクォート (") で囲んで指定されたものとみなされるため、正しく 2 つのパラメータとしてハンドルされます。
for arg in "100 200" "AAA BBB"; ...
2 番目の方法のように $@ をダブルクォートで囲まずに渡すと、下記のように各要素が展開して指定されたものとみなされます。
for ループはスペース区切りで各要素が渡されていると判断するため、結果的に 4 つの要素として処理されてしまいます。
for arg in 100 200 AAA BBB; ...
3 番目の方法のように "$*" と指定すると、下記のように全てのパラメータをスペースでつなげ、さらに全体をダブルクォート (") で囲んで指定されたものとして処理されます。
つまり、ループは "100 200 AAA BBB" という文字列 1 回分しか回りません。
for arg in "100 200 AAA BBB"; do
4 番目の方法のように $* と指定した場合は、2 番目の方法と同様に、すべての要素がスペース区切りで渡されたとみなされます(4 つの要素として処理されます)。
for arg in 100 200 AAA BBB; ...
(コラム)$* が使用する IFS 変数について
前述のとおり、全てのパラメータは $* あるいは $@ で参照できます。
$* は環境変数 IFS に設定された最初のセパレータで区切られた単一の文字列として評価され(デフォルトはスペース)、$@ はスペースで区切られた複数の文字列として評価されます。
例えば、次のように実行した場合、
$ ./sample.sh aaa bbb ccc ddd
それぞれの値は以下のようになります。
$*→"aaa bbb ccc ddd"$@→"aaa""bbb""ccc""ddd"
$ ./sample.sh "aaa bbb" "ccc ddd"
とした場合は、
$*→"aaa bbb ccc ddd"$@→"aaa bbb""ccc ddd"
となります。
$* はこのような性質を持つため、2 つ目の例のように、スペースを含むパラメータが渡されたときに for ループでうまく扱うことができません。
#!/bin/bash
for x in $*; do
echo "$x"
done$ ./sample.sh "aaa bbb" "ccc ddd"
aaa
bbb
ccc
ddd
実は、IFS 変数の値は変更できる ため、次のようにして改行 1 つ分に変更してやることで、for ループがうまく回るようになります。
#!/bin/bash
IFS='
'
for x in $*; do
echo "$x"
done
unset IFS # デフォルトに戻す(スペースで結合)$ ./sample.sh "aaa bbb" "ccc ddd"
aaa bbb
ccc ddd
うまく動作するとはいえ、これはとてもトリッキーな方法なので、パラメータをループ処理するときは素直に "$@" を使って次のように書きましょう。
for arg in "$@"; do
echo "$arg"
done