はじめに
Bash スクリプトにおける未定義の変数は、予期せぬ動作やエラーを引き起こす可能性があります。このチュートリアルでは、シェル変数の基本を掘り下げ、変数が未定義(設定されていない)状態を特定する方法を学び、それらを効果的に処理するための戦略を実装します。この実験(Lab)の終わりには、変数の問題を適切に処理する、より信頼性の高い Bash スクリプトを書けるようになります。
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 で変数に名前を付ける際には、次の規則を念頭に置いてください。
スクリプトにさらにいくつかの例を追加しましょう。もう一度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
$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
このテンプレートには以下が含まれています。
このテンプレートを、独自の 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 スクリプトでの未定義変数の処理方法を学びました。以下は、達成したことの概要です。
set -euo pipefail)を実装しましたこれらのテクニックを適用することで、エッジケースを適切に処理し、ユーザーにより良いエクスペリエンスを提供する、より信頼性の高い Bash スクリプトを作成できます。これらの重要なポイントを覚えておいてください。
これらのプラクティスは、より堅牢で、保守性が高く、ユーザーフレンドリーな Bash スクリプトを作成するのに役立ちます。