シェルスクリプトの書き方入門 - Bash変数・条件分岐・ループ
この記事でできるようになること
- シェバンと実行権限を理解してスクリプトを自分で作成・実行できる
- 変数・条件分岐・ループを組み合わせて処理を自動化できる
- よくある構文エラーの原因を切り分けて直せる
前提知識(先に読むと理解しやすい記事)
シェルスクリプトとは、ターミナルで打つコマンドをファイルにまとめて、まとめて実行できるようにしたものです。「シェルスクリプト」「bash スクリプト」「シェルプログラム」はいずれも同じものを指します。
毎回手で打っていた作業を 1 ファイルにしておけば、次回は 1 回の実行で済みます。この基礎編では、Bash スクリプトの基本構文から条件分岐、ループ処理までを扱います。
この記事で身につくこと
- シェバンと実行権限を理解して、スクリプトを自分で作成・実行できるようになります。
- 変数・条件分岐・ループを組み合わせて、繰り返し作業を自動化できるようになります。
- よくある構文エラーの原因を切り分けて、自分で直せるようになります。
想定読者:ターミナルでコマンドを打った経験があり、同じ作業の繰り返しを自動化したい人。
前提:$HOME のような変数の読み方を扱います。未習なら 環境変数の基礎 を先に読んでください。
先に用語を整理する
初めて出てくる言葉を、ここで一度だけ定義します。
- シェルとは、打ったコマンドを受け取って実行してくれるプログラムです。Linux では Bash(バッシュ)が標準です。
- シェバンとは、スクリプトの 1 行目に書く
#!/bin/bashのことです。「シバン」「hashbang」「ハッシュバン」とも呼びます。この行でどのシェルに実行させるかを指定します。 - 実行権限とは、そのファイルをプログラムとして起動してよいという許可です。付いていないスクリプトは
Permission deniedで止まります。 - 変数とは、値に名前を付けて置いておく箱です。
name="値"で入れて、$nameで取り出します。 - 引用符(クォート)とは、
"や'のことです。値に空白が入る場合は引用符で囲まないと、シェルが別々の単語として解釈します。 - 終了ステータスとは、コマンドが終わったときに返す 0〜255 の数値です。0 が成功、0 以外が失敗を意味します。「終了コード」「exit status」「リターンコード」とも呼びます。
- コマンド置換とは、
$(date)のようにコマンドの実行結果をその場に埋め込む書き方です。 - 算術展開とは、
$((1 + 2))のように計算結果を埋め込む書き方です。シェルは何もしないと数値も文字列として扱うため、計算にはこの書き方が必要です。扱えるのは整数のみで、小数は切り捨てられます。
はじめてのシェルスクリプト
結論: #!/bin/bashのシェバンで始まり、chmod +xで実行権限を付けてから./script.shで実行する。
基本構造
#!/bin/bash # これはコメントです echo "Hello, Shell Script!" echo "現在の日時: $(date)" echo "ユーザー名: $USER"
スクリプトの作成と実行
ステップ1: ファイル作成
$ nano hello.sh
上記のコードを入力して保存する。
ステップ2: 実行権限の付与
$ chmod +x hello.sh
ステップ3: スクリプト実行
$ ./hello.sh
Hello, Shell Script! 現在の日時: Sat Jan 11 14:30:00 JST 2025 ユーザー名: user
date の表示形式はロケール設定で変わる。日本語ロケール(ja_JP.UTF-8)の環境では 2025年 1月11日 土曜日 14時30分00秒 JST のように出る。
./hello.sh の ./ は「今いるディレクトリの」という意味。これを省くとシェルは PATH の中だけを探すため、command not found になる。
スクリプトは書いた内容を無条件に実行する
スクリプトは確認画面を出さない。中に rm があれば、そのまま削除が走る。失敗するとどうなるかは次の 2 点。
- 中身を読まずに実行したスクリプトが、削除や上書きを含んでいた場合、取り消せない。
sudo ./script.shで実行すると、スクリプト内の全操作が root 権限で走る。システムファイルまで壊せる状態になる。
安全に試す方法は次の 3 つ。
- 練習は
mkdir ~/script-practice && cd ~/script-practiceで作った専用ディレクトリの中だけで行う。 - 他人が書いたスクリプトは
cat script.shで中身を読んでから実行する。 - 最初は
sudoを付けずに実行する。権限エラーが出てから、なぜ必要なのかを考える。
シェバン(Shebang)について
#!/bin/bash はシェバン(shebang)と呼ばれ、このファイルを実行するインタープリター(実行を担当するプログラム)を指定する。
#!/bin/bash— Bashを使用#!/bin/sh— POSIXシェルを使用#!/usr/bin/env bash— 環境から自動検出
シェバンを書き忘れると、実行するシェルによって挙動が変わる。特に #!/bin/sh を指定した場合、Bash 固有の書き方([[ ]] や配列)は使えない。迷ったら #!/bin/bash と書いておけばよい。
変数と入力
結論: 変数はname="値"で定義して$nameで参照し、readコマンドで対話的なユーザー入力を受け付けられる。
変数の基本
#!/bin/bash
# 変数の定義(=の前後にスペースを入れない)
name="Linux User"
age=25
today=$(date +%Y-%m-%d)
# 変数の使用
echo "名前: $name"
echo "年齢: ${age}歳"
echo "今日の日付: $today"計算の例
#!/bin/bash num1=10 num2=3 sum=$((num1 + num2)) diff=$((num1 - num2)) product=$((num1 * num2)) quotient=$((num1 / num2)) echo "足し算: $num1 + $num2 = $sum" echo "引き算: $num1 - $num2 = $diff" echo "掛け算: $num1 × $num2 = $product" echo "割り算: $num1 ÷ $num2 = $quotient"
足し算: 10 + 3 = 13 引き算: 10 - 3 = 7 掛け算: 10 × 3 = 30 割り算: 10 ÷ 3 = 3
割り算が 3 になるのは誤りではない。シェルの算術展開は整数演算で、小数は切り捨てられる($((10 / 3)) は 3)。小数が必要な場合は bc か awk を使う。
echo "scale=2; 10/3" | bc # 3.33
awk 'BEGIN {print 10/3}' # 3.33333特殊変数
| 変数 | 説明 | 例 |
|---|---|---|
$0 |
スクリプト名 | ./script.sh |
$1, $2, ... |
コマンドライン引数 | 第1引数、第2引数 |
$# |
引数の個数 | 3個の引数なら3 |
$@ |
すべての引数 | "arg1" "arg2" "arg3" |
$? |
直前のコマンドの終了ステータス | 成功なら0、失敗なら非0 |
$$ |
現在のプロセスID | 12345 |
$USER |
現在のユーザー名 | user |
$HOME |
ホームディレクトリ | /home/user |
$PWD |
現在のディレクトリ | /home/user/scripts |
ユーザー入力
基本的な入力
#!/bin/bash
echo "あなたの名前を入力してください:"
read name
echo "こんにちは、${name}さん!"プロンプト付き入力
#!/bin/bash read -p "年齢を入力してください: " age read -s -p "パスワードを入力してください: " password echo echo "年齢: $age" echo "パスワードは非表示で入力されました"
複数の値を一度に入力
#!/bin/bash echo "名前と年齢をスペース区切りで入力:" read name age echo "名前: $name, 年齢: $age"
条件分岐
結論: if [ 条件 ]; then〜fiの形でファイル存在・数値比較・文字列比較の条件分岐をcase文も含めて記述できる。
if文
基本的なif文
#!/bin/bash
read -p "数値を入力してください: " num
if [ "$num" -gt 0 ]; then
echo "$num は正の数です"
elif [ "$num" -lt 0 ]; then
echo "$num は負の数です"
else
echo "$num はゼロです"
fiファイル存在チェック
#!/bin/bash
filename="test.txt"
if [ -f "$filename" ]; then
echo "$filename は存在します"
echo "ファイルサイズ: $(wc -c < "$filename") bytes"
else
echo "$filename は存在しません"
echo "ファイルを作成します..."
touch "$filename"
fi条件演算子
数値比較
| 演算子 | 意味 | 例 |
|---|---|---|
-eq |
等しい | [ $a -eq $b ] |
-ne |
等しくない | [ $a -ne $b ] |
-gt |
より大きい | [ $a -gt $b ] |
-ge |
以上 | [ $a -ge $b ] |
-lt |
より小さい | [ $a -lt $b ] |
-le |
以下 | [ $a -le $b ] |
文字列比較
| 演算子 | 意味 | 例 |
|---|---|---|
= |
等しい | [ "$a" = "$b" ] |
!= |
等しくない | [ "$a" != "$b" ] |
-z |
空文字列 | [ -z "$str" ] |
-n |
空でない | [ -n "$str" ] |
ファイルテスト
| 演算子 | 意味 | 例 |
|---|---|---|
-f |
通常ファイル | [ -f file.txt ] |
-d |
ディレクトリ | [ -d /home ] |
-e |
存在する | [ -e path ] |
-r |
読み取り可能 | [ -r file ] |
-w |
書き込み可能 | [ -w file ] |
-x |
実行可能 | [ -x script ] |
case文
#!/bin/bash
echo "操作を選択してください:"
echo "1) ファイル一覧表示"
echo "2) 現在時刻表示"
echo "3) システム情報表示"
echo "4) 終了"
read -p "選択 (1-4): " choice
case $choice in
1)
echo "=== ファイル一覧 ==="
ls -la
;;
2)
echo "=== 現在時刻 ==="
date
;;
3)
echo "=== システム情報 ==="
uname -a
;;
4)
echo "終了します。"
exit 0
;;
*)
echo "無効な選択です。"
;;
esacループ処理
結論: for i in {1..5}やwhile [ 条件 ]でループし、breakで脱出・continueで次の反復へ移行できる。
forループ
基本的なforループ
#!/bin/bash
# 数値の範囲
for i in {1..5}
do
echo "カウント: $i"
done
echo "---"
# ファイル処理
for file in *.txt
do
echo "処理中: $file"
wc -l "$file"
done配列を使ったループ
#!/bin/bash
fruits=("apple" "banana" "orange" "grape")
echo "果物リスト:"
for fruit in "${fruits[@]}"
do
echo "- $fruit"
doneC言語風forループ
#!/bin/bash
echo "九九表の一部:"
for ((i=1; i<=5; i++))
do
for ((j=1; j<=5; j++))
do
result=$((i * j))
printf "%2d " $result
done
echo
donewhileループ
基本的なwhileループ
#!/bin/bash
count=1
while [ $count -le 5 ]
do
echo "ループ $count 回目"
count=$((count + 1))
doneファイル読み込み
#!/bin/bash
filename="data.txt"
if [ -f "$filename" ]; then
while IFS= read -r line
do
echo "読み込み: $line"
done < "$filename"
else
echo "ファイル $filename が見つかりません"
fiuntilループ
#!/bin/bash
count=1
until [ $count -gt 5 ]
do
echo "カウント: $count"
count=$((count + 1))
doneループ制御
- break — ループを抜ける
- continue — 次の反復へ
#!/bin/bash
for i in {1..10}
do
if [ $i -eq 3 ]; then
echo "3はスキップ"
continue
fi
if [ $i -eq 8 ]; then
echo "8で終了"
break
fi
echo "数値: $i"
doneよくある間違いと落とし穴
結論: 変数代入時の=前後スペース禁止・条件式の[ ]内スペース必須・文字列比較での引用符が主な落とし穴。
間違い1: 変数定義時のスペース
NG(エラーになる)
name = "太郎" # = の前後にスペース age= 25 # = の後にスペース city ="東京" # = の前にスペース
「command not found」エラーになる。
OK(正しい書き方)
name="太郎" # = の前後にスペースなし age=25 city="東京"
変数の代入では = の前後にスペースを入れない。
間違い2: 変数参照時の{}忘れ
NG(予期しない結果)
filename="test" echo "$filenameback.txt" # ".txt" だけが出力される($filenameback は未定義) echo "$filename_backup" # 何も出力されない($filename_backup は未定義)
シェルは $filenameback までを 1 つの変数名として読む。$filename + back とは解釈されない。
OK(正しい書き方)
filename="test"
echo "${filename}back.txt" # testback.txt
echo "${filename}_backup" # test_backup{} で変数名の範囲を明確にする。
間違い3: if文の条件式でのスペース不足
NG(構文エラー)
if [$num -gt 5]; then # [の後ろにスペースなし if [ $num -gt 5]; then # ]の前にスペースなし
OK(正しい書き方)
if [ $num -gt 5 ]; then # [ ] の内側にスペース必須 if [[ $num -gt 5 ]]; then # [[ ]] を使う場合も同様 if (( num > 5 )); then # 算術式の場合
間違い4: 文字列比較での引用符忘れ
NG(危険な例)
if [ $name = John Doe ]; then # スペースが含まれると問題 if [ $empty_var = "" ]; then # 空の場合エラー
OK(安全な書き方)
if [ "$name" = "John Doe" ]; then # 両辺を引用符で囲む if [ "$empty_var" = "" ]; then # 空でもエラーにならない if [ -z "$var" ]; then # 空文字チェックの専用オプション
間違い5: コマンド置換での古い記法
NG(非推奨)
date=`date` # バッククォート result=`cat `which ls`` # ネストでエラー
OK(現代的な記法)
date=$(date) # $() を使用 result=$(cat $(which ls)) # ネストが容易
間違い6: 算術演算での間違い
NG(計算されない)
result = $num1 + $num2 # "result" コマンドとして解釈され command not found sum="$a + $b" # 計算されない
OK(正しい計算方法)
result=$((num1 + num2)) # 算術展開 let "result = num1 + num2" # let コマンド使用
間違いを防ぐための基本ルール
記述の基本
- 変数代入時:
=の前後にスペースを入れない - 変数使用時: 常に引用符で囲む
"$var" - 複雑な変数:
{}で囲む"${var}"
条件文の基本
[ ]の使用: 内側に必ずスペースを入れる- 文字列比較: 両辺を引用符で囲む
- 数値比較:
-eq,-gt,-ltなどを使用
デバッグとトラブルシューティング
スクリプトが期待どおり動かないときは、推測せずに set -x で実行過程を表示させる。変数がどう展開されたかが 1 行ずつ見えるため、原因が特定しやすい。
set -x:実行過程を表示する
#!/bin/bash
set -x # ここから実行するコマンドを表示
filename="data.txt"
echo "DEBUG: filename = [$filename]"
if [ ! -f "$filename" ]; then
echo "ERROR: ファイル $filename が見つかりません" >&2
exit 1
fi+ filename=data.txt + echo 'DEBUG: filename = [data.txt]' DEBUG: filename = [data.txt] + '[' '!' -f data.txt ']' + echo 'ERROR: ファイル data.txt が見つかりません' ERROR: ファイル data.txt が見つかりません + exit 1
+ で始まる行が、シェルが実際に実行したコマンド。変数が展開されたあとの姿が見えるため、「変数が空だった」「引用符が効いていなかった」といった原因をその場で判別できる。値を [ ] で囲んで出力しているのは、空文字列と空白 1 文字を区別するため。
set -e / set -u:異常を早い段階で止める
#!/bin/bash set -e # コマンドが失敗した時点で停止 set -u # 未定義変数を参照した時点で停止
この 2 つは「気づかないまま処理が進む」ことを防ぐための設定。特に set -u は変数名のタイポ検出に効く。
set -u を有効にしたスクリプトで未定義の変数を参照すると、その行で unbound variable エラーになって停止する。これは意図した挙動であり、set -x の不具合ではない。
script.sh: line 5: var: unbound variable
停止させたくない箇所では、${var:-}(未定義なら空文字列として扱う)のように既定値を書く。
よくあるエラーメッセージと対処法
症状: command not found
原因: 変数代入時に = の前後へスペースを入れている。または ./ を付けずに実行している。
確認:
bash -n script.sh # 構文だけをチェック(実行しない)
対処: = の前後のスペースを削る。実行時は ./script.sh の形にする。
症状: Permission denied
原因: スクリプトに実行権限が付いていない。
確認:
ls -l script.sh
-rw-r--r-- のように x がなければ実行権限なし。
対処:
chmod +x script.sh
症状: syntax error near unexpected token
原因: [ ] の内側のスペース不足、引用符の閉じ忘れ、then / fi / done の記述漏れ。
確認:
bash -n script.sh
対処: エラーが指す行番号の前後を見直す。if には then と fi、for / while には do と done が対で必要。
症状: 変数が空になる
原因: 変数名の直後に文字が続き、シェルが別の変数名として解釈している。
確認:
echo "DEBUG: var = [$var]"
[] で囲むと、空文字列なのか空白が入っているのかを判別できる。
対処: ${var} の形で変数名の範囲を明示する。
作業完了チェックリスト
- [ ] 1 行目にシェバン(
#!/bin/bash)を書いた - [ ]
chmod +xで実行権限を付けた - [ ] 変数代入の
=の前後にスペースを入れていない - [ ] 変数参照を
"$var"の形で引用符で囲んだ - [ ] 練習用ディレクトリの中で実行し、
sudoを付けずに動作を確認した - [ ] 動かない場合は
bash -nとset -xで原因を切り分けた