Bashシェルスクリプトを作成する際、外部からデータを受け取り動的に動作を変更させる「引数」の扱いは非常に重要です。
引数を正しく活用することで、同じスクリプトで異なるファイルを処理したり、動作モードを切り替えたりすることが可能になります。
本記事では、引数処理の基本となる位置パラメータ「$1」や全引数を表す「$@」の具体的な使い方について解説します。
初心者の方が迷いやすい記号の意味や、実務で役立つ実践的なテクニックを整理してご紹介します。
位置パラメータの基本と$1の役割
Bashでは、スクリプト実行時に渡された引数は「位置パラメータ」と呼ばれる特殊な変数に格納されます。
実行コマンドの直後に置かれた最初の単語が$1、2番目が$2というように、数字に対応して割り振られます。
なお、$0には実行しているスクリプト自身のファイル名が格納されることを覚えておきましょう。
個別の引数を取得する方法
特定の順番にある引数を利用したい場合は、その番号を直接指定します。
例えば、入力ファイル名と出力ファイル名の2つを引数として受け取るスクリプトを想定してみましょう。
#!/bin/bash
# 引数の内容を表示するサンプルスクリプト
echo "実行されたスクリプト名: $0"
echo "第1引数(入力ファイル): $1"
echo "第2引数(出力先): $2"
このスクリプトに「input.txt」と「output.txt」を渡して実行した結果は以下の通りです。
実行されたスクリプト名: ./test.sh
第1引数(入力ファイル): input.txt
第2引数(出力先): output.txt
このように、引数の位置に応じた変数を呼び出すだけで簡単に値を操作できます。
ただし、引数が10個以上になる場合は注意が必要で、$10ではなく${10}のように波括弧で囲む必要があります。
全引数を一括で扱う「$@」と「$*」の違い
スクリプトに渡されたすべての引数をまとめて処理したい場合には、$@や$*を使用します。
これらは一見すると同じように見えますが、ダブルクォーテーションで囲んだ際の挙動に決定的な違いがあります。
「$@」の特性:各引数を個別に保持する
"$@"は、それぞれの引数を個別のダブルクォーテーションで囲んだ状態で展開します。
つまり、引数の中にスペースが含まれている場合でも、元の区切りを維持したままループ処理などに渡すことができます。
実務においては、予期せぬ分割を防ぐために「$@」を使用するのが一般的です。
「$*」の特性:すべての引数を1つの文字列にする
一方で"$*"は、すべての引数を1つの大きな文字列として連結して展開します。
各引数の間には、環境変数IFS(通常はスペース)の最初の文字が挿入されます。
引数リスト全体を単一のメッセージとして表示したい場合には便利ですが、個別のファイルパスなどを処理する際には不向きです。
具体的な挙動の比較
以下のコードで、それぞれの展開方法によるループ処理の違いを確認してみましょう。
#!/bin/bash
# $@ と $* の違いを確認するスクリプト
echo "--- \"\$@\" の場合 ---"
for arg in "$@"; do
echo "引数: $arg"
done
echo "--- \"\$*\" の場合 ---"
for arg in "$*"; do
echo "引数: $arg"
done
このスクリプトに「apple “orange juice” banana」という3つの引数を渡してみます。
--- "$@" の場合 ---
引数: apple
引数: orange juice
引数: banana
--- "$*" の case ---
引数: apple orange juice banana
「$@」はスペースを含む「orange juice」を1つの塊として保持していることがわかります。
引数の数をチェックする「$#」の活用
スクリプトの安定性を高めるためには、実行時に必要な数の引数が渡されているかを確認することが欠かせません。
引数の総数を取得するには、特殊変数$#を使用します。
引数不足によるエラーを防止する
例えば、必ず2つの引数が必要なスクリプトでは、以下のようなバリデーション(検証)を記述します。
#!/bin/bash
# 引数が2つでない場合にエラーを出力して終了する
if [ $# -ne 2 ]; then
echo "エラー: 引数が不足しています。"
echo "使い方: $0 [入力ファイル] [出力ファイル]"
exit 1
fi
echo "処理を開始します..."
このようにif文と組み合わせることで、ユーザーの入力ミスによる予期せぬ動作を防ぐことができます。
引数が空であるかどうかをチェックする際には、[ -z "$1" ]といった条件式もよく利用されます。
引数を順番に処理する「shift」コマンド
大量の引数を順番に処理したい場合や、最初の引数だけを特殊な設定として扱い、残りをファイルリストとして処理したい場合があります。
このようなケースでは、shiftコマンドが非常に役立ちます。
shiftによる位置パラメータの移動
shiftを実行すると、$2の内容が$1に、$3の内容が$2にというように、番号が1つずつ繰り上がります。
これを利用することで、whileループを用いた柔軟な引数処理が可能になります。
#!/bin/bash
# shiftを使って引数を1つずつ処理する
echo "全引数の数: $#"
while [ $# -gt 0 ]; do
echo "現在のターゲット: $1"
# ここに処理を記述
shift
done
この手法は、オプション解析を自作する場合などに頻繁に活用されるテクニックです。
実践的な引数処理のテクニック
基本を押さえたところで、より実務に近い形での引数処理のパターンを見ていきましょう。
スクリプトの柔軟性を高めるための工夫をいくつか紹介します。
デフォルト値の設定
引数が指定されなかった場合に、あらかじめ決めておいた初期値(デフォルト値)を使用する方法があります。
Bashの変数展開の機能を使うと、短く記述できます。
# 第1引数が空なら "default.log" を代入する
LOG_FILE=${1:-"default.log"}
echo "ログファイル: $LOG_FILE"
${変数名:-デフォルト値}という記法を用いることで、「値がなければこれを使う」というロジックを1行で完結させられます。
全ての引数に対して同じコマンドを実行する
バックアップ処理など、渡されたすべてのファイルに対して同一の操作を行いたい場合は、配列のように扱える$@が最適です。
#!/bin/bash
# 渡されたすべてのファイルをバックアップディレクトリにコピーする
BACKUP_DIR="/tmp/backup"
mkdir -p "$BACKUP_DIR"
for file in "$@"; do
if [ -f "$file" ]; then
cp "$file" "$BACKUP_DIR/"
echo "Backed up: $file"
else
echo "Warning: $file is not a file."
fi
done
このように記述すれば、backup.sh file1 file2 file3といった具合に、複数のファイルを一括で安全に処理できます。
引数処理における注意点とトラブルシューティング
シェルスクリプトで引数を扱う際、初心者が陥りやすい罠がいくつか存在します。
最も多いミスは、変数のダブルクォーテーション囲み忘れです。
変数のクォートを徹底する
$1や$@を使用する際は、常に"$1"や"$@"のようにダブルクォーテーションで囲む癖をつけましょう。
クォートを忘れると、引数の中にスペースやワイルドカード(*)が含まれていた場合に、シェルによって予期せぬ展開が行われてしまいます。
これはファイル消失などの重大なバグにつながる可能性があるため、「引数は常にクォートする」という原則を徹底してください。
特殊な文字の扱い
引数にハイフンから始まる文字列(例: -config.txt)を渡すと、コマンドがそれをオプションと誤認することがあります。
これを防ぐためには、コマンドの引数の終端を示す--を活用しましょう。
# ファイル名がハイフンで始まっていても安全に扱う
cat -- "$1"
こうした細かい配慮が、堅牢なスクリプトを作成するための第一歩となります。
まとめ
Bashシェルスクリプトにおける引数処理は、自動化の幅を広げるための基礎知識です。
個別の値を指す$1から、全引数を安全に扱う$@、そして引数の数を確認する$#まで、それぞれの役割を正しく理解することが重要です。
特に"$@"を用いたループ処理や、デフォルト値の設定、shiftによる連続処理は、実務の現場で頻繁に登場します。
今回解説したテクニックを組み合わせることで、再利用性が高く、エラーに強いシェルスクリプトを記述できるようになります。
まずは簡単なスクリプトから引数を取り入れ、その挙動を実際に手元で確認しながら学習を進めてみてください。
