Bash 入門

Bashスクリプトの引数処理ガイド

スクリプト引数(Script arguments)とは、コマンドラインからBashスクリプトを実行する際に渡される値のことです。これらの引数を使用することで、ユーザーはスクリプトのコードを直接書き換えることなく、その挙動をカスタマイズできるようになり、より柔軟で再利用性の高いスクリプトを作成することが可能になります。

引数へのアクセス方法と活用術を理解することは、効率的なBashスクリプトを記述するための必須基礎です。本章では、これらの引数にアクセスするための特殊変数($1、$2、$@、および $*)について詳しく紹介します。

1. スクリプト引数へのアクセス:$1 と $2

Bashでは、特殊変数 $1$2$3 から $9 までを使用して、スクリプトに渡された1番目、2番目、3番目から9番目までの引数にアクセスできます。9個以上の引数が必要な場合は、${10}${11} といった中括弧(ブレース)を用いた構文を使用して、10番目以降の引数にアクセスします。

1.1 基本的な使い方

greet.sh という名前のシンプルなスクリプトを作成してみましょう。このスクリプトは、1番目の引数として「名前」を、2番目の引数として「挨拶メッセージ」を受け取り、パーソナライズされた挨拶を表示します。

#!/bin/bash

# 最初の引数(名前)にアクセス
name="$1"

# 2番目の引数(挨拶メッセージ)にアクセス
greeting="$2"

# 両方の引数が提供されているかチェック
if [ -z "$name" ] || [ -z "$greeting" ]; then
  echo "Usage: $0 <名前> <挨拶メッセージ>"
  exit 1
fi

# パーソナライズされた挨拶メッセージを表示
echo "$greeting, $name!"

このスクリプトを実行するには、ターミナルで以下のコマンドを入力します。

./greet.sh John "Hello"

出力結果は以下の通りです:

Hello, John!

この例では、$1 には「John」という値が、$2 には「Hello」という値が格納されます。スクリプトはこれらの引数にアクセスし、最終的なメッセージを構築するために利用します。

1.2 複数の引数を処理する例

名(first name)、姓(last name)、年齢(age)を引数として受け取る process_info.sh というスクリプトを考えてみましょう。

#!/bin/bash

# 最初の引数(名)にアクセス
first_name="$1"

# 2番目の引数(姓)にアクセス
last_name="$2"

# 3番目の引数(年齢)にアクセス
age="$3"

# すべての引数が提供されているかチェック
if [ -z "$first_name" ] || [ -z "$last_name" ] || [ -z "$age" ]; then
  echo "Usage: $0 <名> <姓> <年齢>"
  exit 1
fi

# 情報を表示
echo "名: $first_name"
echo "姓: $last_name"
echo "年齢: $age"

実行コマンド:

./process_info.sh Alice Smith 30

出力:

名: Alice
姓: Smith
年齢: 30

1.3 欠落している引数の処理

引数を使用する前に、ユーザーが実際にそれらを提供したかどうかを確認することは非常に重要です。引数が不足している場合、対応する特殊変数は「空(empty)」になります。if 文において、-z オプションは文字列が空であるかどうかをチェックするために使用されます。引数が不足している場合、スクリプトは「Usage(使用法)」を表示して終了(exit)します。

ヒント: 変数 $0 は特殊変数であり、常に実行されているスクリプト自身のファイル名を保持しています。

2. 9番目以降の引数へのアクセス

9番目より後の引数にアクセスする場合、変数の数字を中括弧で囲む必要があります。例えば、10番目の引数にアクセスするには ${10} を使用します。中括弧なしで $10 と記述すると、Bashは $1 の値の直後に文字 0 が続いているとパースしてしまいます。

以下のスクリプト process_args.sh を見てみましょう:

#!/bin/bash

# 10番目の引数にアクセス
tenth_arg="${10}"

# 10番目の引数が提供されているかチェック
if [ -z "$tenth_arg" ]; then
  echo "10番目の引数が不足しています。"
else
  echo "10番目の引数は: $tenth_arg"
fi

このスクリプトを実行するには、少なくとも10個の引数を渡す必要があります。

./process_args.sh a b c d e f g h i j

出力:

10番目の引数は: j

3. $@ を使用した全引数へのアクセス

特殊変数 $@ はスクリプトに渡されたすべての引数を表し、それらを「独立した単語(文字列)」として扱います。すべての引数をループ処理したり、別のコマンドにそのまま渡したりする場合に特に有用です。

3.1 $@ を使用して引数をループ処理する

for ループを使用して、$@ が保持するすべての引数をイテレート(巡回)できます。スクリプト print_args.sh を考えてみましょう:

#!/bin/bash

# すべての引数をループ処理
for arg in "$@"; do
  echo "引数: $arg"
done

いくつかの引数を指定して実行します:

./print_args.sh one "two words" three

出力:

引数: one
引数: two words
引数: three

重要なポイント: "two words" は引用符(クォート)で囲まれているため、単一の引数として扱われます。$@ はクォート内のスペースを完璧に保持し、1つのユニットとして処理します。

3.2 将引数传递给另一个命令

$@ は、スクリプトが受け取ったすべての引数をそのまま別のコマンドに引き渡す際にもよく使われます。これは、既存のコマンドの挙動を修正・拡張する「ラッパー(wrapper)スクリプト」を作成する際に非常に便利です。

ls コマンドをラップした wrapper.sh というスクリプトを考えてみましょう:

#!/bin/bash

# 受け取ったすべての引数を ls コマンドに渡す
ls -l "$@"

このラッパースクリプトを実行します:

./wrapper.sh -a -h /tmp

これは、ターミナルで直接 ls -l -a -h /tmp を実行するのと全く同じであり、/tmp ディレクトリ内の詳細(隠しファイルを含む)をヒューマンリーダブルな形式でリスト表示します。

4. $@ と $* の違い

$@$* はどちらもすべての引数を表しますが、スペースを含む引数の扱いにおいて本質的な違いがあります。

  • $@: 各引数を独立した単語として扱います。引数内部にスペースが含まれていても、独立性が保たれます。
  • $*: すべての引数を1つの長い文字列に結合します。引数間は IFS(Internal Field Separator:内部フィールド区切り文字)変数の最初の文字で区切られます。デフォルトでは、IFS はスペース、タブ、改行を含みます。

4.1 $* を使用して引数を単一の文字列としてアクセスする

スクリプト print_star.sh を見てみましょう:

#!/bin/bash

# すべての引数を単一の文字列として表示
echo "全引数: $*"

実行:

./print_star.sh one "two words" three

出力:

全引数: one two words three

見ての通り、すべての引数がスペースで区切られた1つの文字列に結合されました。$@ とは異なり、クォートによる変数の独立性は(適切にクォートして扱わない限り)失われてしまいます。

4.2 IFS 変数の変更

IFS 変数を変更することで、$* の結合挙動をカスタマイズできます。例えば、IFS をカンマに設定すると、すべての引数をカンマで連結できます。

#!/bin/bash

# IFS をカンマに設定
IFS=','

# 全引数を単一の文字列として表示(カンマ区切り)
echo "全引数: $*"

再度実行:

./print_star.sh one "two words" three

出力:

全引数: one,two words,three

4.3 いつ $* を使い、いつ $@ を使うべきか?

  • "$@" の使用を強く推奨: 各引数の独立性を維持する必要がある場合(特に引数にスペースが含まれる可能性がある場合)。ほとんどのケースにおいて、これが最も安全で好ましい選択です。
  • "$*" を時折使用: すべての引数を1つの長い文字列として出力したり処理したりする必要がある場合(例:単一の文字列入力を受け付けるコマンドへ渡す場合や、単にログとしてまとめて出力したい場合)。

5. 実践的なケーススタディ

5.1 自動バックアップスクリプトの作成

システム管理の実戦ケースに戻りましょう。複数のディレクトリをバックアップするスクリプトを作成します。このスクリプトは、バックアップ対象のディレクトリリストを引数として受け取り、tarアーカイブを作成します。

#!/bin/bash

# 少なくとも1つの引数が提供されているかチェック ($# は引数の総数)
if [ $# -eq 0 ]; then
  echo "Usage: $0 <ディレクトリ1> <ディレクトリ2> ..."
  exit 1
fi

# バックアップファイル名を定義 (タイムスタンプを含む)
backup_file="backup_$(date +%Y%m%d%H%M%S).tar.gz"

# tar アーカイブを作成 ("$@" ですべてのディレクトリ引数を tar に渡す)
tar -czvf "$backup_file" "$@"

# tar コマンドが成功したかチェック ($? は直前のコマンドの終了ステータス)
if [ $? -eq 0 ]; then
  echo "バックアップ成功: $backup_file"
else
  echo "バックアップ失敗。"
  exit 1
fi

このスクリプトでは:

  • $# で渡された引数の数を確認しています。
  • $@ を使用して、すべてのディレクトリ引数を完璧に tar コマンドへ引き渡しています。
  • $?tar コマンドの終了コードをチェックしています(0は成功)。

実行コマンド:

./backup_directories.sh /home/user/documents /var/log /etc

これにより、指定したディレクトリを含む tar.gz アーカイブが作成されます。

5.2 ログのバッチ処理スクリプトの作成

複数のログファイルを一度に引数として渡し、それらを一括処理するスクリプトを想定します。

#!/bin/bash

# 少なくとも1つの引数が提供されているかチェック
if [ $# -eq 0 ]; then
  echo "Usage: $0 <ログファイル1> <ログファイル2> ..."
  exit 1
fi

# 各ログファイルを巡回して処理
for log_file in "$@"; do
  if [ -f "$log_file" ]; then
    echo "ログファイルを処理中: $log_file"
    # ここに具体的なログ処理コマンドを追加
    grep "ERROR" "$log_file"
  else
    echo "エラー: ファイルが見つかりません: $log_file"
  fi
done

このスクリプトでは、for ループが引数として渡された各ログファイルを走査します。ループ内部では、grep を使用して特定のエラーパターンを検索するなどの具体的な処理を実行できます。

実行コマンド:

./process_logs.sh /var/log/syslog /var/log/auth.log

これにより、syslogauth.log が順番に処理され、「ERROR」を含むレコードが検索・表示されます。