まくまくLinux/Shellノート
シェルスクリプト: コマンドライン引数を取得する
2008-10-29

パラメータ取得の基本

シェルスクリプト実行時にコマンドラインで渡したパラメータは、以下のような特殊変数で参照できます。

  • $1 – 第1パラメータ
  • $2 – 第2パラメータ
  • $3 – 第3パラメータ

パラメータの取得には、$1$9 が使用できます。 使用することは余りないと思いますが、10番目以降のパラメータを参照したいときは、${10} のように数値を {} で囲めば参照できます。

sample.sh

#!/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 BBB
$1 = AAA
$2 = BBB
$3 =
echo の引数として変数を渡すときは、"$1" のようにダブルクォートで囲む癖をつけましょう。 $1 のようにそのまま記述してしまうと、値として連続するスペースが含まれていたときに1つのスペースにまとめられてしまいます。

デフォルト値を指定する

コマンドラインパラメータが指定されなかったときのために、デフォルト値を指定しておくこともできます。 下記の例では、1番目、2番目、3番目のコマンドラインパラメータのデフォルト値を、それぞれ 100、200、300 に設定しています。

sample.sh

#!/bin/bash

echo '$1 =' "${1:-AAA}"
echo '$2 =' "${2:-BBB}"
echo '$3 =' "${3:-CCC}"

実行結果(1番目のパラメータのみ指定してみる)

$ ./sample.sh 100
$1 = 100
$2 = BBB
$3 = CCC

パラメータの数を取得する

シェルスクリプト実行時に渡されたパラメータの数は、$# という特殊変数で取得することができます。

sample.sh

#!/bin/bash
echo $#

実行結果

$ ./sample.sh
0

$ ./sample.sh AAA
1

$ ./sample.sh AAA BBB
2

パラメータをループで順番に処理する ($@, $*)

for ループを使う方法

シェルスクリプト実行時に渡されたコマンドラインパラメータは、特殊変数 $@ を使って参照することができます。 以下の例では、for in ループを使って、$@ の要素を 1 つずつ取り出して処理しています(おまけでカウンター変数 $count をインクリメントしながらループしてます)。

sample.sh

#!/bin/bash

count=1
for arg in "$@"; do
  echo "$count: $arg"
  let count=$count+1
done

実行結果

$ ./sample.sh AAA BBB CCC
1: AAA
2: BBB
3: CCC

特殊変数の指定部分 in "$@" は省略して記述することができます。

#!/bin/bash

count=1
for arg; do
  echo "$count: $arg"
  let count=$count+1
done

明示的に in "$@" を記述する場合は、$@ の部分をダブルクォートで囲むことを忘れないようにしてください。

while ループを使う方法

shift コマンドを実行することで、$1$9 に格納されたパラメータを 1 つずつ前にシフトすることができます。 shift コマンドを実行するたびに $1 に格納されていたパラメータは破棄され、パラメータ数を表す $# の値が 1 つずつ減っていきます。

下記の例では、パラメータ数 ($#) が 1 以上の間、処理を続ける while ループを定義しています。 $1 はコマンドラインパラメータの最初の要素を参照する変数ですが、直後の shift によってパラメータを 1 つずつシフトしているので、結果としてすべてのパラメータを順番に参照することができます。

sample.sh

#!/bin/bash
count=1
while [ "$#" -ge "1" ]; do
  echo "$count: $1"
  shift
  let count=$count+1
done

実行結果

$ ./sample.sh AAA BBB CCC
1: AAA
2: BBB
3: CCC

$@ と $* の違い

$@ と似た特殊変数に $* があります。 どちらもパラメータ全体を表す特殊変数ですが、$@ が各パラメータを個別に保持しているのに対し、$* はすべてのパラメータを結合した 1 つの文字列です。 それぞれ、ダブルクォートで囲んで参照した場合と、囲まずに参照した場合の展開方法も含めて理解しておきましょう。 下記のようなサンプルコードと、その振る舞いを対応付けて覚えてしまうのが手っ取り早いです。

sample.sh

#!/bin/bash

echo '=== Pattern 1 ==='
for arg in "$@"; do
  echo "$arg"
done

echo -e '\n=== Pattern 2 ==='
for arg in $@; do
  echo "$arg"
done

echo -e '\n=== Pattern 3 ==='
for arg in "$*"; do
  echo "$arg"
done

echo -e '\n=== Pattern 4 ==='
for arg in $*; do
  echo "$arg"
done

実行結果

$ ./sample.sh "100 200" "CCC DDD"
=== Pattern 1 ===
100 200
CCC DDD

=== Pattern 2 ===
100
200
CCC
DDD

=== Pattern 3 ===
100 200 CCC DDD

=== Pattern 4 ===
100
200
CCC
DDD

Pattern 1 の方法が、おそらく一番想定通りの振る舞いをします"$@" と指定することで、内部的には下記のようにそれぞれの要素をダブルクォート (") で囲んで指定されたものとみなされるため、正しく2つのパラメータとしてハンドルされます。

for arg in "100 200" "AAA BBB"; ...

Pattern 2 の方法のように $@ をダブルクォートで囲まずに渡すと、下記のように各要素が展開して指定されたものとみなされます。 for ループはスペース区切りで各要素が渡されていると判断するため、結果的に4つの要素として処理されてしまいます。

for arg in 100 200 AAA BBB; ...

Pattern 3 の方法のように "$*" と指定すると、下記のように全てのパラメータをスペースでつなげ、さらに全体をダブルクォート (") で囲んで指定されたものとして処理されます。 つまり、ループは "100 200 AAA BBB" という文字列の1回分しか回りません。

for arg in "100 200 AAA BBB"; do

Pattern 4 の方法のように $* と指定した場合は、Pattern 2 と同様に、すべての要素がスペース区切りで渡されたとみなされます(4つの要素として処理されます)。

for arg in 100 200 AAA BBB; ...

(コラム) $* が使用する IFS 変数について

全てのパラメータは $* あるいは $@ で参照できます。 $* は環境変数 IFS に設定された最初のセパレータで区切られた単一の文字列として評価され(デフォルトはスペース)、$@ はスペースで区切られた複数の文字列として評価されます。

例えば、次のように実行した場合、

$ ./sample.sh aaa bbb ccc

それぞれの値は以下のようになります。

  • $*"aaa bbb ccc"
  • $@"aaa" "bbb" "ccc"
$ ./sample.sh "aaa bbb" "ccc ddd"

とした場合は、

  • $*"aaa bbb ccc ddd"
  • $@"aaa bbb" "ccc ddd"

となります。 $* はこのような性質を持つため、2つ目の例のように、スペースを含むパラメータが渡されたときに for ループでうまく扱うことができません。

sample.sh(間違った例)

#!/bin/bash

for x in $*; do
  echo "$x"
done

実行結果(4つのパラメータとして扱われてしまう)

$ ./sample.sh "aaa bbb" "ccc ddd"
aaa
bbb
ccc
ddd

実は、IFS 変数の値は変更できるため、次のようにして改行1つ分に変更してやることで、for ループがうまく回るようになります。

sample.sh(オススメはしないがうまく動作する)

#!/bin/bash

IFS='
'

for x in $*; do
  echo "$x"
done

unset IFS  # デフォルトに戻す(スペースで結合)

実行結果(ちゃんと2つのパラメータとして扱われる)

$ ./sample.sh "aaa bbb" "ccc ddd"
aaa bbb
ccc ddd

うまく動作するとはいえ、これはとてもトリッキーな方法なので、パラメータをループ処理するときは素直に "$@" を使って次のように書きましょう。

for arg in "$@"; do
  echo "$arg"
done
2008-10-29