シェルスクリプト入門

beginner | 60分 で読める | 2025.12.02

このチュートリアルで学ぶこと

  • シェルスクリプトの基本構造
  • 変数の使い方
  • 条件分岐(if文)
  • ループ処理(for, while)
  • 実践的な自動化スクリプト

シェルスクリプトとは?なぜ学ぶのか

シェルスクリプトの誕生

シェルスクリプトの歴史は1971年のThompson shell(sh)に遡ります。1979年にはStephen BourneがBourne Shell(sh)を開発し、これが現代のシェルスクリプトの基礎となりました。

1989年にはBrian FoxがGNUプロジェクトの一環としてBash(Bourne Again Shell)を開発。Bashは現在最も広く使われているシェルです。

「シェルスクリプトは、繰り返し行う作業を自動化するための最も手軽な方法である」— Unix環境の古くからの知恵

なぜシェルスクリプトを学ぶのか

  1. 自動化: 繰り返しタスクを自動化し、時間を節約
  2. システム管理: サーバー管理、デプロイ、バックアップに必須
  3. CI/CD: GitHub Actions、Jenkinsなどで多用される
  4. 移植性: ほぼすべてのUnix系システムで動作

Bashとその他のシェル

シェル特徴主な用途
bash最も普及。POSIX互換 + 拡張機能一般的なスクリプト
shPOSIX標準。最小限の機能移植性重視のスクリプト
zshbash互換 + 強力な補完機能対話シェル
fishユーザーフレンドリー対話シェル
dash軽量・高速システムスクリプト

ベストプラクティス: 移植性を重視する場合は#!/bin/shを、Bash固有の機能を使う場合は#!/bin/bashを使用します。

最初のシェルスクリプト

まずは「Hello World」から始めましょう。

hello.sh

#!/bin/bash

# これは最初のシェルスクリプトです
echo "Hello, World!"

実行方法

# 実行権限を付与
chmod +x hello.sh

# 実行
./hello.sh

# または、bashを明示的に指定
bash hello.sh

シバン(Shebang)について

1行目の#!/bin/bashシバン(shebang)と呼ばれ、このスクリプトをどのインタプリタで実行するかを指定します。

#!/bin/bash     # bashで実行
#!/bin/sh       # POSIX shで実行(移植性が高い)
#!/usr/bin/env bash  # PATHからbashを探して実行(推奨)

公式ドキュメント: GNU Bash Manual

変数の使い方

基本的な変数

#!/bin/bash

# 変数の定義(=の前後にスペースを入れない!)
name="太郎"
age=25

# 変数の参照
echo "名前: $name"
echo "年齢: ${age}歳"

# コマンドの結果を変数に代入
current_date=$(date +%Y-%m-%d)
echo "今日の日付: $current_date"

# 旧式の書き方(バッククォート)- 非推奨
old_style=`date +%Y-%m-%d`

よくある間違い: スペース

# 間違い(スペースがあるとエラー)
name = "太郎"    # command not found: name
name= "太郎"     # 空の変数としてコマンド "太郎" を実行

# 正解
name="太郎"

変数のスコープ

#!/bin/bash

# グローバル変数
global_var="I am global"

function example() {
    # ローカル変数(関数内のみ有効)
    local local_var="I am local"
    echo "$local_var"
    echo "$global_var"
}

example
echo "$local_var"   # 空(関数外では見えない)

特殊変数

#!/bin/bash

echo "スクリプト名: $0"
echo "第1引数: $1"
echo "第2引数: $2"
echo "引数の数: $#"
echo "すべての引数: $@"
echo "すべての引数(文字列): $*"
echo "直前のコマンドの終了ステータス: $?"
echo "現在のプロセスID: $$"
echo "バックグラウンドプロセスのPID: $!"

実行例:

$ ./script.sh arg1 arg2 arg3
スクリプト名: ./script.sh
第1引数: arg1
第2引数: arg2
引数の数: 3
すべての引数: arg1 arg2 arg3

環境変数

# 環境変数の参照
echo "ホームディレクトリ: $HOME"
echo "ユーザー名: $USER"
echo "現在のディレクトリ: $PWD"
echo "シェル: $SHELL"
echo "パス: $PATH"

# 環境変数の設定(サブプロセスに継承される)
export MY_VAR="some value"

# 一時的に環境変数を設定してコマンドを実行
DEBUG=true ./my_script.sh

クォートの違い

name="World"

# ダブルクォート: 変数展開される
echo "Hello, $name"    # Hello, World

# シングルクォート: そのまま出力
echo 'Hello, $name'    # Hello, $name

# バックスラッシュでエスケープ
echo "Hello, \$name"   # Hello, $name

条件分岐(if文)

基本構文

#!/bin/bash

age=20

if [ $age -ge 20 ]; then
    echo "成人です"
elif [ $age -ge 13 ]; then
    echo "ティーンエイジャーです"
else
    echo "子供です"
fi

テスト構文の種類

# [ ] - 古典的なtest構文(POSIX互換)
if [ $a -eq $b ]; then

# [[ ]] - Bash拡張構文(推奨)
if [[ $a == $b ]]; then

# (( )) - 算術評価
if (( a > b )); then

ベストプラクティス: Bashを使う場合は[[ ]]を推奨。パターンマッチングや正規表現が使えます。

数値比較演算子

演算子意味
-eq等しい (equal)[ $a -eq $b ]
-ne等しくない (not equal)[ $a -ne $b ]
-ltより小さい (less than)[ $a -lt $b ]
-le以下 (less or equal)[ $a -le $b ]
-gtより大きい (greater than)[ $a -gt $b ]
-ge以上 (greater or equal)[ $a -ge $b ]

文字列比較

#!/bin/bash

str="hello"

# 文字列の比較(クォートで囲むことが重要!)
if [ "$str" = "hello" ]; then
    echo "文字列が一致"
fi

# 空文字列のチェック
if [ -z "$str" ]; then
    echo "文字列は空です"
fi

# 空でないかチェック
if [ -n "$str" ]; then
    echo "文字列は空ではありません"
fi

# [[ ]]を使ったパターンマッチング
if [[ "$str" == h* ]]; then
    echo "hで始まります"
fi

# 正規表現マッチング(Bash 3.0以降)
if [[ "$str" =~ ^[a-z]+$ ]]; then
    echo "小文字のみです"
fi

ファイル・ディレクトリの判定

#!/bin/bash

# ファイルの存在確認
if [ -f "config.txt" ]; then
    echo "config.txtが存在します"
fi

# ディレクトリの存在確認
if [ -d "logs" ]; then
    echo "logsディレクトリが存在します"
fi

# ファイルまたはディレクトリが存在
if [ -e "path" ]; then
    echo "pathが存在します"
fi

# 読み取り可能
if [ -r "file.txt" ]; then
    echo "読み取り可能です"
fi

# 書き込み可能
if [ -w "file.txt" ]; then
    echo "書き込み可能です"
fi

# 実行可能
if [ -x "script.sh" ]; then
    echo "実行可能です"
fi

# ファイルが空でない
if [ -s "file.txt" ]; then
    echo "ファイルは空ではありません"
fi

論理演算子

# AND
if [ $a -gt 0 ] && [ $a -lt 10 ]; then
    echo "0 < a < 10"
fi

# OR
if [ $a -eq 0 ] || [ $a -eq 1 ]; then
    echo "a is 0 or 1"
fi

# NOT
if [ ! -f "file.txt" ]; then
    echo "file.txt does not exist"
fi

# [[ ]] では && と || が使える
if [[ $a -gt 0 && $a -lt 10 ]]; then
    echo "0 < a < 10"
fi

case文

複数の条件分岐にはcase文が便利です:

#!/bin/bash

fruit="apple"

case $fruit in
    apple)
        echo "りんごです"
        ;;
    banana|orange)
        echo "バナナかオレンジです"
        ;;
    *)
        echo "不明な果物です"
        ;;
esac

ループ処理

forループ

#!/bin/bash

# リストを順に処理
for fruit in apple banana orange; do
    echo "フルーツ: $fruit"
done

# 数値の範囲(Bash拡張)
for i in {1..5}; do
    echo "カウント: $i"
done

# ステップを指定
for i in {0..10..2}; do
    echo "偶数: $i"
done

# C言語スタイルのfor
for ((i=0; i<5; i++)); do
    echo "Index: $i"
done

# ファイルを処理
for file in *.txt; do
    echo "ファイル: $file"
done

# コマンドの出力を処理
for user in $(cat users.txt); do
    echo "ユーザー: $user"
done

whileループ

#!/bin/bash

count=1
while [ $count -le 5 ]; do
    echo "カウント: $count"
    count=$((count + 1))
done

# ファイルを1行ずつ読み込み(推奨パターン)
while IFS= read -r line; do
    echo "行: $line"
done < input.txt

# 無限ループ
while true; do
    echo "Press Ctrl+C to stop"
    sleep 1
done

untilループ

#!/bin/bash

count=1
until [ $count -gt 5 ]; do
    echo "カウント: $count"
    count=$((count + 1))
done

ループ制御

# breakでループを抜ける
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break
    fi
    echo $i
done

# continueで次のイテレーションへ
for i in {1..5}; do
    if [ $i -eq 3 ]; then
        continue
    fi
    echo $i
done

関数の定義

#!/bin/bash

# 関数の定義(2つの書き方)
function greet() {
    local name=$1
    echo "こんにちは、${name}さん!"
}

# または
greet2() {
    echo "Hello, $1!"
}

# 関数の呼び出し
greet "太郎"
greet2 "World"

# 戻り値(終了ステータス)
is_even() {
    if (( $1 % 2 == 0 )); then
        return 0  # 成功(偶数)
    else
        return 1  # 失敗(奇数)
    fi
}

if is_even 4; then
    echo "4は偶数です"
fi

# 値を返す場合はechoを使う
add() {
    echo $(( $1 + $2 ))
}

result=$(add 3 5)
echo "3 + 5 = $result"

実践: バックアップスクリプト

学んだ内容を使って、実用的なバックアップスクリプトを作成します。

backup.sh

#!/bin/bash
#
# 自動バックアップスクリプト
# 使用方法: ./backup.sh [source_dir] [backup_dir]
#

set -euo pipefail  # エラー時に即座に終了

# 設定
SOURCE_DIR="${1:-./src}"
BACKUP_DIR="${2:-./backups}"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$DATE.tar.gz"
RETENTION_DAYS=7

# ログ関数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# バックアップディレクトリの確認・作成
if [ ! -d "$BACKUP_DIR" ]; then
    log "バックアップディレクトリを作成します..."
    mkdir -p "$BACKUP_DIR"
fi

# ソースディレクトリの確認
if [ ! -d "$SOURCE_DIR" ]; then
    log "エラー: $SOURCE_DIR が見つかりません"
    exit 1
fi

# バックアップ実行
log "バックアップを開始します: $SOURCE_DIR -> $BACKUP_DIR/$BACKUP_NAME"
tar -czf "$BACKUP_DIR/$BACKUP_NAME" "$SOURCE_DIR"

if [ $? -eq 0 ]; then
    log "完了: $BACKUP_DIR/$BACKUP_NAME"
    log "サイズ: $(du -h "$BACKUP_DIR/$BACKUP_NAME" | cut -f1)"
else
    log "エラー: バックアップに失敗しました"
    exit 1
fi

# 古いバックアップの削除
log "古いバックアップを削除中(${RETENTION_DAYS}日以上前)..."
deleted=$(find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete -print | wc -l)
log "削除されたファイル数: $deleted"

log "すべて完了しました!"

スクリプトのベストプラクティス

  1. set -euo pipefailを使う

    • -e: エラー時に即座に終了
    • -u: 未定義変数をエラーにする
    • -o pipefail: パイプ内のエラーも検出
  2. 変数は常にクォートする: "$variable"

  3. 関数を活用する: 再利用性と可読性向上

  4. ログ出力を入れる: デバッグと監視のため

エラーハンドリング

#!/bin/bash

# トラップでエラー時の処理を定義
cleanup() {
    echo "クリーンアップ処理を実行..."
    # 一時ファイルの削除など
}

trap cleanup EXIT  # スクリプト終了時に実行
trap 'echo "エラーが発生しました: 行 $LINENO"' ERR

# エラーを無視したい場合
command_that_might_fail || true

# エラー時の代替処理
config_file="config.txt"
if [ -f "$config_file" ]; then
    source "$config_file"
else
    echo "設定ファイルがありません。デフォルト値を使用します。"
    default_value="fallback"
fi

デバッグのコツ

# スクリプト全体をデバッグモードで実行
bash -x script.sh

# スクリプト内でデバッグを有効化
set -x  # デバッグ開始
# ... デバッグしたいコード ...
set +x  # デバッグ終了

# エラー時に即座に終了(推奨)
set -e

# 未定義変数をエラーにする(推奨)
set -u

# パイプのエラーも検出(推奨)
set -o pipefail

# よく使う組み合わせ
set -euo pipefail

ShellCheckで静的解析

ShellCheckは、シェルスクリプトの問題を検出する静的解析ツールです。

# インストール
# macOS
brew install shellcheck

# Ubuntu/Debian
sudo apt install shellcheck

# 使い方
shellcheck script.sh

ShellCheckが検出する問題の例:

# 警告: 変数のクォート漏れ
echo $USER  # SC2086: Double quote to prevent globbing

# 修正後
echo "$USER"

Google Shell Style Guideでも、ShellCheckの使用が推奨されています。

よくあるパターン

引数のバリデーション

#!/bin/bash

if [ $# -lt 2 ]; then
    echo "使用方法: $0 <source> <destination>"
    exit 1
fi

source=$1
destination=$2

デフォルト値の設定

# 変数が未設定または空の場合にデフォルト値を使用
name="${1:-World}"
echo "Hello, $name"

# 変数が未設定の場合のみデフォルト値を使用
name="${1-World}"

一時ファイルの安全な作成

#!/bin/bash

# mktempを使用(推奨)
temp_file=$(mktemp)
temp_dir=$(mktemp -d)

# 終了時に削除
trap "rm -rf $temp_file $temp_dir" EXIT

echo "一時ファイル: $temp_file"
echo "一時ディレクトリ: $temp_dir"

ユーザー入力の取得

#!/bin/bash

# 入力を読み取る
read -p "名前を入力してください: " name
echo "こんにちは、$name さん"

# パスワード入力(非表示)
read -sp "パスワード: " password
echo ""

# タイムアウト付き
read -t 5 -p "5秒以内に入力してください: " input || echo "タイムアウト"

次のステップ

シェルスクリプトの基本をマスターしたら、次のステップへ進みましょう:

  • 正規表現とgrepsedawk
  • cronジョブによるスケジュール実行
  • より高度な自動化(Ansible、Makefileなど)

参考リンク

公式ドキュメント

スタイルガイド・ベストプラクティス

学習リソース

チートシート

← 一覧に戻る