シェルスクリプトの書き方入門 - Bash変数・条件分岐・ループ

シェルスクリプトの書き方入門 - 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 になる。

シェバン(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)。小数が必要な場合は bcawk を使う。

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"
done

C言語風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
done

whileループ

基本的な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 が見つかりません"
fi

untilループ

#!/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 には thenfifor / while には dodone が対で必要。

症状: 変数が空になる

原因: 変数名の直後に文字が続き、シェルが別の変数名として解釈している。

確認:

echo "DEBUG: var = [$var]"

[] で囲むと、空文字列なのか空白が入っているのかを判別できる。

対処: ${var} の形で変数名の範囲を明示する。

作業完了チェックリスト

  • [ ] 1 行目にシェバン(#!/bin/bash)を書いた
  • [ ] chmod +x で実行権限を付けた
  • [ ] 変数代入の = の前後にスペースを入れていない
  • [ ] 変数参照を "$var" の形で引用符で囲んだ
  • [ ] 練習用ディレクトリの中で実行し、sudo を付けずに動作を確認した
  • [ ] 動かない場合は bash -nset -x で原因を切り分けた

次に読む

この記事を共有

次の一手