Linux: Bashスクリプト基礎 — 変数 / 条件 / ループ
一連のLinuxコマンドをファイルに保存し、論理制御(条件、ループ)を追加すれば、それがShellスクリプトです。すべての反復作業を自動化する出発点です。
📋 前提条件:まず以下を完了していること
- レッスン19:アーカイブと圧縮
1. このレッスンで学ぶこと
- Shebangと実行権限
- 変数定義とクォートの違い
- 位置パラメータ
- if/test条件論理
- for/whileループ
2. 手動デプロイからワンクリックデプロイへのストーリー
(1) 悩み:毎日同じ5コマンドを打っている
小明はデプロイのために毎日これらのコマンドを実行していました:git pull、npm install、npm run build、pm2 restart app、echo "done"。5つのコマンド、3分、毎日繰り返し。
(2) 8行のスクリプト
彼はdeploy.shを書きました:
#!/bin/bash
set -e
echo "Starting deployment..."
cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app
echo "✅ Deployment complete!"
set -eはスクリプト安全性の最初の防衛線 — いずれかのコマンドが非ゼロの終了コードを返すとスクリプトは即座に停止します。set -eがないと、コマンドが失敗してもスクリプトは実行を継続し、深刻な連鎖エラー(壊れたコードでサービスを再起動するなど)を引き起こす可能性があります。
(3) 得られるもの:ワンクリックデプロイ
それ以来、小明は./deploy.shを実行するだけ — 全ステップが自動完了、時間節約と漏れ防止。
3. 知識ポイント
(1) Shebang
スクリプトの1行目はShebangでなければなりません — システムにどのインタプリタを使用するかを伝えます:
#!/bin/bash # Execute with bash
#!/bin/sh # Execute with sh (compatibility mode)
#!/usr/bin/env python3 # Execute with Python 3
#!/bin/bashはShebang — OSにスクリプトのインタプリタを指定します。この行がないと、システムは現在のシェルを使用し、互換性の問題が発生する可能性があります(例:shは[[ ]]構文をサポートしない)。すべてのbashスクリプトは#!/bin/bashで始める必要があります。
(2) 変数
# Define variables (no spaces around the equals sign)
name="Alice"
age=25
# Use variables (curly braces recommended)
echo $name # Alice
echo ${name} # Alice (braces are safer)
echo "Hello, ${name}"
# Variable naming rules
# 1. Letters, digits, underscores
# 2. Cannot start with a digit
# 3. Case-sensitive (NAME != name)
3種類のクォート:
| クォート | 例 | 動作 |
|---|---|---|
| ダブルクォート | "Hello $name" |
変数とコマンドを展開 |
| シングルクォート | 'Hello $name' |
リテラル文字列、展開なし |
バッククォート/$() |
`date` or $(date) |
コマンドを実行し出力を取得 |
ℹ️ 注意:シングルクォートとダブルクォートの核心的な違い — ダブルクォート内では
$variableと$(command)が展開・置換されます。シングルクォート内はすべてリテラルです。変数展開が必要な時はダブルクォート、$などの特殊文字を保持したい時(正規表現パターンや変数展開したくないヒアドキュメントなど)はシングルクォートを使用してください。
name="Alice"
echo "Hello $name" # Hello Alice
echo 'Hello $name' # Hello $name
echo "Today is $(date)" # Today is Tue Jul 7 10:23:45 CST 2026
(3) 位置パラメータ
./script.sh arg1 arg2 arg3
# $1 $2 $3
$0 # Script name itself
$1, $2 # 1st, 2nd argument
$# # Number of arguments
$@ # All arguments (as separate words)
$* # All arguments (as a single string)
$? # Exit code of previous command (0=success, non-0=failure)
$$ # PID of current script
(4) 条件論理(if / test / [[ ]])
# test command or [ ]
if [ "$name" = "Alice" ]; then
echo "Hello Alice"
fi
# More powerful [[ ]] (bash-specific)
if [[ "$name" == "Alice" ]]; then
echo "Hello Alice"
fi
# if-elif-else
if [ "$1" -gt 10 ]; then
echo "Greater than 10"
elif [ "$1" -gt 5 ]; then
echo "Greater than 5"
else
echo "5 or less"
fi
ファイルテスト:
| 条件 | 意味 |
|---|---|
-f file |
通常ファイルか |
-d dir |
ディレクトリか |
-e path |
存在するか |
-s file |
ファイルサイズ > 0 |
-r file |
読み取り可能か |
-w file |
書き込み可能か |
-x file |
実行可能か |
文字列比較:
| 条件 | 意味 |
|---|---|
$a = $b |
等しい |
$a != $b |
等しくない |
-z $a |
文字列が空(長さ = 0) |
-n $a |
文字列が空ではない |
数値比較:
| 条件 | 意味 |
|---|---|
$a -eq $b |
等しい |
$a -ne $b |
等しくない |
$a -gt $b |
より大きい |
$a -ge $b |
以上 |
$a -lt $b |
より小さい |
$a -le $b |
以下 |
(5) ループ
# for loop
for i in 1 2 3 4 5; do
echo "Number: $i"
done
for file in *.txt; do
echo "Found file: $file"
done
for i in {1..10}; do
echo "$i"
done
# while loop
count=1
while [ $count -le 5 ]; do
echo "Attempt $count"
count=$((count + 1))
done
# Read file line by line
while IFS= read -r line; do
echo "$line"
done < /etc/hostname
(6) ▶ サンプル:基本スクリプト
#!/bin/bash
# hello.sh - First bash script
echo "Hello, World!"
echo "Current user: $(whoami)"
echo "Current directory: $(pwd)"
echo "File count: $(ls | wc -l)"
出力:
TEXTHello, World! Current user: alice Current directory: /home/alice File count: 12
(7) ▶ サンプル:位置パラメータの使用
#!/bin/bash
# greet.sh - Greet a specified user
if [ $# -eq 0 ]; then
echo "Usage: $0 <username>"
exit 1
fi
echo "Hello, $1!"
echo "Welcome to Linux scripting."
echo "Script name: $0"
echo "Argument count: $#"
./greet.sh Alice
# Hello, Alice!
(8) ▶ サンプル:if条件分岐
#!/bin/bash
# check-file.sh - Check file status
FILE="${1:-test.txt}"
if [ ! -e "$FILE" ]; then
echo "File '$FILE' does not exist"
exit 1
fi
if [ -f "$FILE" ]; then
echo "Type: Regular file"
elif [ -d "$FILE" ]; then
echo "Type: Directory"
fi
[ -r "$FILE" ] && echo "Readable: ✅" || echo "Readable: ❌"
[ -w "$FILE" ] && echo "Writable: ✅" || echo "Writable: ❌"
[ -x "$FILE" ] && echo "Executable: ✅" || echo "Executable: ❌"
(9) ▶ サンプル:forループ一括処理
#!/bin/bash
# rename.sh - Batch rename .txt to .md
count=0
for file in *.txt; do
if [ -f "$file" ]; then
mv "$file" "${file%.txt}.md"
echo "Renamed: $file → ${file%.txt}.md"
count=$((count + 1))
fi
done
echo "Total files renamed: $count"
(10) ▶ サンプル:whileループと入力読み取り
#!/bin/bash
# countdown.sh - Countdown timer
if [ $# -ne 1 ]; then
echo "Usage: $0 <seconds>"
exit 1
fi
count=$1
while [ $count -gt 0 ]; do
echo "Countdown: $count"
count=$((count - 1))
sleep 1
done
echo "Time's up!"
(11) ▶ 総合例:一括ファイル処理スクリプト
#!/bin/bash
# batch-process.sh - Batch process files in a directory
DIR="${1:-.}"
ACTION="${2:-info}"
if [ ! -d "$DIR" ]; then
echo "Error: Directory '$DIR' does not exist"
exit 1
fi
echo "Processing directory: $DIR"
echo "Action: $ACTION"
echo "---"
case "$ACTION" in
info)
echo "File statistics:"
echo " Total files: $(find "$DIR" -type f | wc -l)"
echo " Total directories: $(find "$DIR" -type d | wc -l)"
echo " Total size: $(du -sh "$DIR" | cut -f1)"
;;
backup)
BACKUP="/tmp/backup-$(date +%Y%m%d)"
mkdir -p "$BACKUP"
cp -r "$DIR" "$BACKUP/"
echo "Backup complete: $BACKUP"
;;
clean)
echo "Cleaning .log files..."
find "$DIR" -name "*.log" -delete
echo "Cleaning .tmp files..."
find "$DIR" -name "*.tmp" -delete
echo "Cleaning complete"
;;
*)
echo "Actions: info / backup / clean"
;;
esac
❓ よくある質問
Q: 1行目の
#!/bin/bashは必須ですか? A: はい。カーネルにどのインタプリタを使用するかを伝えます。これがないとデフォルトで現在のシェルが使用され、互換性の問題が発生する可能性があります。
Q: スクリプトが
command not foundと言うのはなぜ? A: 2つの理由:1) 実行権限がない —chmod +x script.shを実行。2) カレントディレクトリがPATHにない —script.shではなく./script.shを使用。
Q:
$@と$*の違いは? A:$@は各引数を独立した「単語」として扱い、$*は全引数を1つの文字列に結合します。ほとんどのシナリオで$@が推奨されます。for arg in "$@"; doでの反復が標準プラクティス。
Q:
exit 0とexit 1の違いは? A:exit 0は成功、exit 非ゼロは失敗(異なる番号は異なるエラータイプを示す)。他のプログラムは$?でスクリプトが成功したかを判定できます。
Q: スクリプト内でユーザー入力を取得するには? A:
readコマンドを使用:read -p "Enter your name: " name、その後$nameにユーザーの入力が格納されます。
📖 まとめ
- Shebang
#!/bin/bash、chmod +xで実行権限を付与 - 変数:
name="value"、$nameまたは${name}で参照 - ダブルクォートは変数を展開、シングルクォートはリテラル、
$(cmd)でコマンド実行 if [ condition ]で条件分岐、for i in listでループexit 0で成功、exit 1でエラー
📝 練習問題
- 基本(難易度 ⭐):現在のユーザー名、日付、カレントディレクトリ、ファイル数を出力するスクリプト
info.shを書く - 中級(難易度 ⭐⭐):ファイル名の引数を受け取り、ファイルが存在するか確認してタイプを出力する
check-file.shを書く。次に、現在のディレクトリの全.txtファイルを/tmp/txt-backup/にバックアップするbackup-txt.shを書く - 上級(難易度 ⭐⭐⭐):秒数の引数を受け取り毎秒カウントダウンするスクリプトを書き、
set -eエラーハンドリングを追加。次にforループでtest-1からtest-10という10個のディレクトリを作成し、最後にその10個のディレクトリのサイズを計算してソートするスクリプトを書く