はじめに
Bash スクリプトにおける未定義の変数は、予期せぬ動作やエラーを引き起こす可能性があります。このチュートリアルでは、シェル変数の基本を掘り下げ、変数が未定義(設定されていない)状態を特定する方法を学び、それらを効果的に処理するための戦略を実装します。この実験(Lab)の終わりには、変数の問題を適切に処理する、より信頼性の高い Bash スクリプトを書けるようになります。
シェル変数を使った最初のスクリプトの作成
シェル変数は、Bash スクリプト内で値を保持する名前付きのストレージ場所です。このステップでは、変数がどのように機能し、どのように使用されるかを理解するために、シンプルなスクリプトを作成します。
シェル変数の理解
Bash の変数は、スクリプト全体で参照および操作できるデータを格納できます。これらは、柔軟で再利用可能なスクリプトを作成するために不可欠です。
まず、プロジェクトディレクトリに新しいスクリプトファイルを作成しましょう。
WebIDE を開き、
/home/labex/projectディレクトリにvariables.shという名前の新しいファイルを作成します。ファイルに次の内容を追加します。
#!/bin/bash
## 変数の宣言
name="LabEx User"
age=25
current_date=$(date)
## 変数へのアクセス
echo "Hello, my name is $name"
echo "I am $age years old"
echo "Today is: $current_date"
## スペースを含む変数は引用符で囲む必要があります
greeting="Welcome to Bash scripting"
echo "$greeting"
ファイルを保存します。
ターミナルで次のコマンドを実行して、スクリプトを実行可能にします。
chmod +x variables.sh
- スクリプトを実行します。
./variables.sh
次のような出力が表示されるはずです。
Hello, my name is LabEx User
I am 25 years old
Today is: Mon Jan 1 12:34:56 UTC 2023
Welcome to Bash scripting
変数命名規則
Bash で変数に名前を付ける際には、次の規則を念頭に置いてください。
- 変数名には、文字、数字、およびアンダースコアを含めることができます。
- 変数名は数字で始めることはできません。
- 値を割り当てるとき、等号の周りにスペースは許可されていません。
- 変数名は大文字と小文字を区別します(NAME と name は異なる変数です)。
スクリプトにさらにいくつかの例を追加しましょう。もう一度variables.shを開き、最後に以下を追加します。
## 有効な変数名の例
user_1="John"
HOME_DIR="/home/user"
count=42
## 変数の出力
echo "User: $user_1"
echo "Home directory: $HOME_DIR"
echo "Count: $count"
ファイルを保存して、もう一度実行します。
./variables.sh
追加の出力は次のようになります。
User: John
Home directory: /home/user
Count: 42
これで、変数を正しく使用する最初の Bash スクリプトが作成されました!
未定義変数への遭遇
変数の基本を理解したところで、値が割り当てられていない変数、つまり未定義変数を使用しようとするとどうなるかを探ってみましょう。
未定義変数とは?
未定義変数(unset variable)とは、スクリプト内で値が割り当てられていない変数のことです。未定義変数を使用しようとすると、Bash は通常、それを空の文字列として扱い、これがスクリプトに微妙なバグを引き起こす可能性があります。
これを実際に確認するために、新しいスクリプトを作成しましょう。
- プロジェクトディレクトリに、次の内容で
unbound.shという名前の新しいファイルを作成します。
#!/bin/bash
## 正しく設定された変数
username="labex"
## 設定されていない変数を使用してみる
echo "Hello, $username! Your home directory is $home_dir"
## 未定義変数で計算を試みる
total=$((count + 5))
echo "Total: $total"
- スクリプトを実行可能にします。
chmod +x unbound.sh
- スクリプトを実行します。
./unbound.sh
次のような出力が表示されるはずです。
Hello, labex! Your home directory is
Total: 5
$home_dirが空白として表示され、$countが計算で 0 として扱われていることに注意してください。この静かな動作は、見つけにくいバグにつながる可能性があります。
未定義変数の検出
これらの未定義変数を検出するために、スクリプトを変更してみましょう。これを行う 1 つの方法は、変数を使用する前に条件付きチェックを使用することです。
unbound.shを開き、その内容を以下に置き換えます。
#!/bin/bash
## 正しく設定された変数
username="labex"
## home_dirを使用する前に設定されているか確認する
if [ -z "$home_dir" ]; then
echo "Warning: home_dir is not set!"
home_dir="/home/default"
echo "Using default: $home_dir"
else
echo "Home directory is: $home_dir"
fi
## 計算を行う前にcountが設定されているか確認する
if [ -z "$count" ]; then
echo "Warning: count is not set!"
count=10
echo "Using default count: $count"
fi
total=$((count + 5))
echo "Total: $total"
- ファイルを保存して、もう一度実行します。
./unbound.sh
出力は次のようになるはずです。
Warning: home_dir is not set!
Using default: /home/default
Warning: count is not set!
Using default count: 10
Total: 15
このアプローチは、未定義変数を検出し、それらが静かなエラーを引き起こすのを防ぐのではなく、明示的に処理するのに役立ちます。
set -uオプションによるテスト
未定義変数を検出するもう 1 つの方法は、set -uオプションを使用することです。これにより、Bash は未定義変数に遭遇するとすぐに終了します。
- 次の内容で
strict.shという名前の新しいファイルを作成します。
#!/bin/bash
set -u ## 未定義変数が使用された場合に終了
echo "My username is $username"
echo "My favorite color is $favorite_color"
echo "This line won't be reached if favorite_color isn't set"
- 実行可能にします。
chmod +x strict.sh
- スクリプトを実行します。
./strict.sh
次のようなエラーメッセージが表示されるはずです。
./strict.sh: line 4: favorite_color: unbound variable
スクリプトは、未定義変数$favorite_colorを使用しようとすると、4 行目で直ちに終了します。これは、未定義変数を早期に検出し、スクリプトの後半で発生する可能性のある問題を回避するのに役立ちます。
パラメータ展開による未定義変数の処理
Bash で未定義変数を処理するための最も強力な機能の 1 つは、パラメータ展開です。これにより、デフォルト値を指定し、変数に対してさまざまな操作を実行できます。
パラメータ展開によるデフォルト値の使用
パラメータ展開は、未定義変数をエレガントに処理するためのいくつかの方法を提供します。これらのテクニックを実演するために、新しいスクリプトを作成しましょう。
- 次の内容で
parameter_expansion.shという名前の新しいファイルを作成します。
#!/bin/bash
## ${parameter:-default}によるデフォルト値
## parameterが未設定またはnullの場合、'default'が使用されます
echo "1. Username: ${username:-anonymous}"
## usernameを定義して、もう一度試す
username="labex"
echo "2. Username: ${username:-anonymous}"
## ${parameter-default}によるデフォルト値
## parameterが未設定の場合のみ、'default'が使用されます
unset username
empty_var=""
echo "3. Username (unset): ${username-anonymous}"
echo "4. Empty variable: ${empty_var-not empty}"
## ${parameter:=default}によるデフォルトの割り当て
## parameterが未設定またはnullの場合、'default'に設定します
echo "5. Language: ${language:=bash}"
echo "6. Language is now set to: $language"
## ${parameter:?message}によるエラーの表示
## parameterが未設定またはnullの場合、エラーメッセージを表示して終了します
echo "7. About to check required parameter..."
## エラーを確認するには、以下の行のコメントを外してください
## echo "Config file: ${config_file:?not specified}"
echo "8. Script continues..."
- スクリプトを実行可能にします。
chmod +x parameter_expansion.sh
- スクリプトを実行します。
./parameter_expansion.sh
次のような出力が表示されるはずです。
1. Username: anonymous
2. Username: labex
3. Username (unset): anonymous
4. Empty variable:
5. Language: bash
6. Language is now set to: bash
7. About to check required parameter...
8. Script continues...
一般的なパラメータ展開パターン
ここで、使用したパラメータ展開パターンの概要を示します。
| 構文 | 説明 |
|---|---|
${var:-default} |
var が未設定または null の場合、デフォルトを使用 |
${var-default} |
var が未設定の場合のみ、デフォルトを使用 |
${var:=default} |
var が未設定または null の場合、var をデフォルトに設定 |
${var:?message} |
var が未設定または null の場合、エラーを表示 |
実用的な例の作成
設定設定を処理するためにパラメータ展開を使用する、より実用的なスクリプトを作成しましょう。
- 次の内容で
config_script.shという名前の新しいファイルを作成します。
#!/bin/bash
## 設定可能な設定でファイルを処理するスクリプト
## パラメータ展開を使用して、すべての設定オプションのデフォルトを設定します
BACKUP_DIR=${BACKUP_DIR:-"/tmp/backups"}
MAX_FILES=${MAX_FILES:-5}
FILE_TYPE=${FILE_TYPE:-"txt"}
VERBOSE=${VERBOSE:-"no"}
echo "Configuration settings:"
echo "----------------------"
echo "Backup directory: $BACKUP_DIR"
echo "Maximum files: $MAX_FILES"
echo "File type: $FILE_TYPE"
echo "Verbose mode: $VERBOSE"
## バックアップディレクトリが存在しない場合は作成します
if [ ! -d "$BACKUP_DIR" ]; then
echo "Creating backup directory: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
fi
## 指定されたタイプのファイルを検索します(デモのために現在のディレクトリ内)
echo "Searching for .$FILE_TYPE files..."
file_count=$(find . -maxdepth 1 -name "*.$FILE_TYPE" | wc -l)
echo "Found $file_count .$FILE_TYPE files"
## 要求された場合は詳細出力を有効にします
if [ "$VERBOSE" = "yes" ]; then
echo "Files found:"
find . -maxdepth 1 -name "*.$FILE_TYPE" -type f
fi
echo "Script completed successfully!"
- スクリプトを実行可能にします。
chmod +x config_script.sh
- デフォルト値でスクリプトを実行します。
./config_script.sh
- 次に、いくつかのカスタム設定で再度実行します。
FILE_TYPE="sh" VERBOSE="yes" ./config_script.sh
出力には、現在のディレクトリ内の bash スクリプト(.shファイル)が表示されるはずです。
このスクリプトは、パラメータ展開を使用して設定変数のデフォルト値を設定し、スクリプトを柔軟でユーザーフレンドリーにする方法を示しています。
堅牢なスクリプトのための厳格モードの使用
より信頼性が高く、エラー耐性の高い Bash スクリプトを作成するために、多くの開発者は「厳格モード」と呼ばれるものを使用しています。これには、未定義変数を含む一般的なエラーを捕捉するのに役立つ一連の Bash オプションが含まれています。
厳格モードとは?
厳格モードには、通常、スクリプトの冒頭でこれらのオプションを有効にすることが含まれます。
set -e: コマンドがゼロ以外のステータスで終了した場合、直ちに終了しますset -u: 未定義変数が参照された場合、終了しますset -o pipefail: パイプライン内のいずれかのコマンドが失敗した場合、パイプラインを失敗させます
スクリプトでこれを実装する方法を見てみましょう。
- 次の内容で
strict_mode.shという名前の新しいファイルを作成します。
#!/bin/bash
## 厳格モード
set -euo pipefail
echo "Starting strict mode script..."
## いくつかの変数を定義する
username="labex"
project_dir="/home/labex/project"
## この関数は2つのパラメータを必要とします
process_data() {
local input="$1"
local output="${2:-output.txt}"
echo "Processing $input and saving to $output"
## デモ目的のため、出力ファイルにエコーするだけです
echo "Processed data from $input" > "$output"
}
## 必要な引数で関数を呼び出す
process_data "input.txt"
## 存在しないファイルにアクセスしようとする
## 厳格モードがエラーをどのように処理するかを確認するには、次の行のコメントを外してください
## cat non_existent_file.txt
echo "Script completed successfully"
- スクリプトを実行可能にします。
chmod +x strict_mode.sh
- スクリプトを実行します。
./strict_mode.sh
次のように表示されるはずです。
Starting strict mode script...
Processing input.txt and saving to output.txt
Script completed successfully
- 出力ファイルが作成されたことを確認します。
cat output.txt
次のように表示されるはずです。
Processed data from input.txt
厳格モードでのエラー処理のテスト
次に、厳格モードがエラーをどのように処理するかを確認するために、スクリプトを変更しましょう。
- 次の内容で
strict_mode_errors.shという名前の新しいファイルを作成します。
#!/bin/bash
## 厳格モード
set -euo pipefail
echo "Starting script with error demonstrations..."
## 例1:未定義変数
echo "Example 1: About to use an unbound variable"
## エラーを確認するには、次の行のコメントを外してください
## echo "Home directory: $HOME_DIR"
echo "This line will not be reached if you uncomment the above line"
## 例2:コマンドの失敗
echo "Example 2: About to run a failing command"
## エラーを確認するには、次の行のコメントを外してください
## grep "pattern" non_existent_file.txt
echo "This line will not be reached if you uncomment the above line"
## 例3:パイプラインの失敗
echo "Example 3: About to create a failing pipeline"
## エラーを確認するには、次の行のコメントを外してください
## cat non_existent_file.txt | sort
echo "This line will not be reached if you uncomment the above line"
echo "Script completed without errors"
- スクリプトを実行可能にします。
chmod +x strict_mode_errors.sh
- スクリプトを実行します。
./strict_mode_errors.sh
- 次に、スクリプトを編集して、エラー行の 1 つ(たとえば、
$HOME_DIRを含む行)のコメントを外し、保存して、もう一度実行します。
./strict_mode_errors.sh
未定義変数に遭遇すると、スクリプトがエラーメッセージとともに直ちに終了することがわかります。
Starting script with error demonstrations...
Example 1: About to use an unbound variable
./strict_mode_errors.sh: line 9: HOME_DIR: unbound variable
これは、厳格モードがスクリプトの早期に問題を特定するのに役立つことを示しています。
堅牢なスクリプトテンプレートの作成
将来のプロジェクトで使用できる、堅牢な Bash スクリプトのテンプレートを作成しましょう。
- 次の内容で
robust_script_template.shという名前の新しいファイルを作成します。
#!/bin/bash
## ====================================
## 堅牢なBashスクリプトテンプレート
## ====================================
## --- 厳格モード ---
set -euo pipefail
## --- スクリプトメタデータ ---
SCRIPT_NAME="$(basename "$0")"
SCRIPT_VERSION="1.0.0"
## --- デフォルト設定 ---
DEBUG=${DEBUG:-false}
LOG_FILE=${LOG_FILE:-"/tmp/${SCRIPT_NAME}.log"}
## --- ヘルパー関数 ---
log() {
local message="$1"
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}
debug() {
if [[ "$DEBUG" == "true" ]]; then
log "DEBUG: $1"
fi
}
error() {
log "ERROR: $1"
exit 1
}
usage() {
cat << EOF
Usage: $SCRIPT_NAME [options]
エラー処理とロギングを備えた堅牢なbashスクリプトテンプレート。
オプション:
--help このヘルプメッセージを表示して終了
--version スクリプトのバージョンを表示して終了
環境変数:
DEBUG デバッグ出力を有効にするには'true'に設定します
LOG_FILE ログファイルへのパス(デフォルト:/tmp/${SCRIPT_NAME}.log)
EOF
}
## --- コマンドライン引数の処理 ---
for arg in "$@"; do
case $arg in
--help)
usage
exit 0
;;
--version)
echo "$SCRIPT_NAME version $SCRIPT_VERSION"
exit 0
;;
*)
## 不明なオプション
;;
esac
done
## --- メインスクリプトロジック ---
main() {
log "Starting $SCRIPT_NAME version $SCRIPT_VERSION"
## スクリプトロジックはここに記述します
debug "Script executed with DEBUG=$DEBUG"
log "Script executed by user: $(whoami)"
log "Current directory: $(pwd)"
## デフォルト値を持つ変数の使用例
TARGET_DIR=${TARGET_DIR:-$(pwd)}
log "Target directory: $TARGET_DIR"
log "$SCRIPT_NAME completed successfully"
}
## --- メイン関数の実行 ---
main
- スクリプトを実行可能にします。
chmod +x robust_script_template.sh
- スクリプトを実行します。
./robust_script_template.sh
- デバッグを有効にして実行してみます。
DEBUG=true ./robust_script_template.sh
このテンプレートには以下が含まれています。
- エラーを捕捉するための厳格モード設定
- パラメータ展開によるデフォルト設定
- ロギングとデバッグ関数
- コマンドライン引数の処理
- main 関数を使用したクリーンな構造
このテンプレートを、独自の Bash スクリプトの開始点として使用して、より堅牢で保守性の高いものにすることができます。
実用的なアプリケーションの作成
変数、未定義変数、パラメータ展開、および厳格モードについて学習したので、これらのすべての概念を実用的なスクリプトに組み合わせましょう。Bash での変数の処理に関するベストプラクティスを示す、シンプルなファイルバックアップユーティリティを作成します。
バックアップスクリプトの計画
バックアップスクリプトは次のことを行います。
- ソースディレクトリとバックアップの宛先を入力として受け取ります
- 環境変数またはコマンドライン引数による設定を許可します
- 厳格モードを使用してエラーを適切に処理します
- ユーザーに役立つフィードバックを提供します
バックアップスクリプトの作成
- 次の内容で
backup.shという名前の新しいファイルを作成します。
#!/bin/bash
## ====================================
## ファイルバックアップユーティリティ
## ====================================
## --- 厳格モード ---
set -euo pipefail
## --- スクリプトメタデータ ---
SCRIPT_NAME="$(basename "$0")"
SCRIPT_VERSION="1.0.0"
## --- デフォルト設定 ---
SOURCE_DIR=${SOURCE_DIR:-"$(pwd)"}
BACKUP_DIR=${BACKUP_DIR:-"/tmp/backups"}
BACKUP_NAME=${BACKUP_NAME:-"backup_$(date +%Y%m%d_%H%M%S)"}
EXCLUDE_PATTERN=${EXCLUDE_PATTERN:-"*.tmp"}
VERBOSE=${VERBOSE:-"false"}
## --- ヘルパー関数 ---
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "$(date "+%Y-%m-%d %H:%M:%S") - $1"
fi
}
error() {
echo "ERROR: $1" >&2
exit 1
}
usage() {
cat << EOF
Usage: $SCRIPT_NAME [options]
シンプルなファイルバックアップユーティリティ。
オプション:
-s, --source DIR バックアップするソースディレクトリ(デフォルト:現在のディレクトリ)
-d, --destination DIR バックアップの宛先ディレクトリ(デフォルト:/tmp/backups)
-n, --name NAME バックアップアーカイブの名前(デフォルト:backup_YYYYMMDD_HHMMSS)
-e, --exclude PATTERN 除外するファイル(デフォルト:*.tmp)
-v, --verbose 詳細出力を有効にする
-h, --help このヘルプメッセージを表示して終了
環境変数:
SOURCE_DIR --sourceと同じ
BACKUP_DIR --destinationと同じ
BACKUP_NAME --nameと同じ
EXCLUDE_PATTERN --excludeと同じ
VERBOSE 詳細出力を有効にするには'true'に設定します
EOF
}
## --- コマンドライン引数の処理 ---
while [[ $## -gt 0 ]]; do
case $1 in
-s | --source)
SOURCE_DIR="$2"
shift 2
;;
-d | --destination)
BACKUP_DIR="$2"
shift 2
;;
-n | --name)
BACKUP_NAME="$2"
shift 2
;;
-e | --exclude)
EXCLUDE_PATTERN="$2"
shift 2
;;
-v | --verbose)
VERBOSE="true"
shift
;;
-h | --help)
usage
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
## --- メイン関数 ---
main() {
## ソースディレクトリの検証
if [[ ! -d "$SOURCE_DIR" ]]; then
error "Source directory does not exist: $SOURCE_DIR"
fi
## バックアップディレクトリが存在しない場合は作成します
if [[ ! -d "$BACKUP_DIR" ]]; then
log "Creating backup directory: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR" || error "Failed to create backup directory"
fi
## バックアップファイルのフルパス
BACKUP_FILE="$BACKUP_DIR/$BACKUP_NAME.tar.gz"
log "Starting backup from $SOURCE_DIR to $BACKUP_FILE"
log "Excluding files matching: $EXCLUDE_PATTERN"
## バックアップを作成します
tar -czf "$BACKUP_FILE" \
--exclude="$EXCLUDE_PATTERN" \
-C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" \
|| error "Backup failed"
## バックアップが作成されたかどうかを確認します
if [[ -f "$BACKUP_FILE" ]]; then
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo "Backup completed successfully: $BACKUP_FILE ($BACKUP_SIZE)"
else
error "Backup file was not created"
fi
}
## --- メイン関数の実行 ---
main
- スクリプトを実行可能にします。
chmod +x backup.sh
- デフォルト設定でスクリプトを実行します。
./backup.sh
バックアップが正常に作成されたことを確認するメッセージが表示されるはずです。
- バックアップファイルを確認します。
ls -lh /tmp/backups/
- さまざまなオプションでスクリプトを実行してみます。
./backup.sh --source ~/project --destination ~/backups --name my_project_backup --verbose
~/backupsディレクトリが存在しない場合、スクリプトはそれを生成します。そのディレクトリを作成するための書き込み権限がない場合は、エラーが表示される可能性があります。
バックアップスクリプトでのエラー処理のテスト
スクリプトがエラーをどのように処理するかをテストしましょう。
- 存在しないディレクトリをバックアップしようとします。
./backup.sh --source /path/that/does/not/exist
次のようなエラーメッセージが表示されるはずです。
ERROR: Source directory does not exist: /path/that/does/not/exist
- 無効なバックアップの宛先(書き込み権限がない場所)を設定しようとします。
./backup.sh --destination /root/backups
スクリプトがバックアップディレクトリの作成に失敗したことを示すエラーメッセージが表示されるはずです。
テスト環境の作成
バックアップスクリプトを実演するためのテスト環境を作成しましょう。
- テストディレクトリ構造を作成します。
mkdir -p ~/project/test_backup/{docs,images,code}
touch ~/project/test_backup/docs/{readme.md,manual.pdf}
touch ~/project/test_backup/images/{logo.png,banner.jpg}
touch ~/project/test_backup/code/{script.sh,data.tmp,config.json}
- このテストディレクトリでバックアップスクリプトを実行します。
./backup.sh --source ~/project/test_backup --exclude "*.tmp" --verbose
- バックアップ内のファイルを一覧表示します。
tar -tvf /tmp/backups/backup_*.tar.gz | sort
除外パターン(*.tmp)に一致するファイルを除くすべてのファイルが表示されるはずです。
このバックアップスクリプトは、これまで説明したすべての概念を示しています。
- パラメータ展開を使用した変数のデフォルト値の設定
- エラーを捕捉するための厳格モードの使用
- コマンドライン引数と環境変数の処理
- ユーザーフィードバックとエラーメッセージの提供
- 入力の検証とエッジケースの処理
これらのテクニックを使用すると、未定義変数を適切に処理し、より優れたユーザーエクスペリエンスを提供する堅牢な Bash スクリプトを作成できます。
まとめ
この実験(Lab)では、信頼性の高いシェルスクリプトを作成するための重要なスキルである、Bash スクリプトでの未定義変数の処理方法を学びました。以下は、達成したことの概要です。
- 適切な変数の宣言と使用法を備えたスクリプトを作成しました
- 未定義変数が Bash スクリプトで引き起こす可能性のある問題を特定しました
- 条件チェックを使用して、未定義変数を検出しました
- デフォルト値を提供するために、パラメータ展開技術を適用しました
- 早期にエラーを捕捉するために、厳格モード(
set -euo pipefail)を実装しました - 適切なエラー処理を備えた堅牢なスクリプトテンプレートを作成しました
- ベストプラクティスを示す実用的なバックアップユーティリティを構築しました
これらのテクニックを適用することで、エッジケースを適切に処理し、ユーザーにより良いエクスペリエンスを提供する、より信頼性の高い Bash スクリプトを作成できます。これらの重要なポイントを覚えておいてください。
- 変数が未設定の場合に何が起こるかを常に考慮する
- デフォルト値にはパラメータ展開を使用する
- 重要なスクリプトには厳格モードを有効にする
- 入力を検証し、役立つエラーメッセージを提供する
- スクリプトの動作と要件を文書化する
これらのプラクティスは、より堅牢で、保守性が高く、ユーザーフレンドリーな Bash スクリプトを作成するのに役立ちます。



