現場で使えるシェルスクリプト入門21:コードの共通化と再利用性を高める「関数」の基本と使い方

シェルスクリプト IT

前回の記事(入門20)では、curljqsource コマンドを組み合わせて、シェルスクリプトからAI(Gemini API)へ自動でリクエストを送信するAI自動応答スクリプトの構築方法について解説しました。

今回は、これまでに学習した知識をベースに、スクリプトが長くなった際や同じ処理を何度も書くのを防ぎ、コードの再利用性とメンテナンス性を劇的に高める「関数(Function)」の基本構文から実践的な使い方までを解説します!

ログ出力やエラーチェックなどを関数化することで、読みやすく拡張しやすいスクリプトが作成できるようになります。

シェルスクリプトで関数を活用するメリット

なぜシェルスクリプトで関数を定義するのでしょうか?そこには以下のようなメリットがあります。

  • コードの重複を排除できる(共通化)
    日時付きのログ出力や設定確認など、何度も行う処理を1箇所に集約できます。修正が必要になった際も関数側を書き換えるだけで済むため、変更漏れを防げます。
  • スクリプトの可読性と保守性が向上する
    処理の塊に「log_info」や「calculate_discount」といった意味の通る名前をつけることで、メイン処理の流れが直感的に理解しやすくなります。
  • 変数の汚染を防いで安全に開発できる
    local キーワードを組み合わせることで、関数の外にある変数に影響を与えずに独立した処理を構築できます。

関数の活用サンプルスクリプト(func_demo.sh)

ログ出力、数値計算(文字列返却)、ディレクトリ確認(判定の返却)を関数化した実践的なサンプルスクリプト(func_demo.sh)です。

#!/bin/bash

# スクリプトを安全に実行するための設定
# 💡 関数のエラー判定(return 1)による意図しない即時終了を防ぎ自前で制御するため、あえて -e を外して -uo pipefail としています
set -uo pipefail

# 1. 日時付きインフォログ出力関数 (local変数の活用)
log_info() {
    local msg="${1:-}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] ${msg}"
}

# 2. エラーログ出力関数 (標準エラー出力へのリダイレクト)
log_error() {
    local msg="${1:-}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] ${msg}" >&2
}

# 3. 割引計算を行う関数 (echo とコマンド置換で計算結果を返却)
calculate_discount() {
    local price="$1"
    local rate="$2"
    
    # 簡易計算 (整数演算)
    local discount=$(( price * rate / 100 ))
    local final_price=$(( price - discount ))
    
    # 計算結果を標準出力へ流す
    echo "${final_price}"
}

# 4. ディレクトリの存在確認関数 (return で終了ステータスを返却)
check_directory() {
    local dir_path="$1"
    if [ -d "$dir_path" ]; then
        return 0 # 成功 (存在する場合)
    else
        return 1 # 失敗 (存在しない場合)
    fi
}

# --- メイン処理 ---
log_info "処理を開始します..."

# 関数の呼び出しと引数の受け渡し
original_price=10000
discount_rate=20

# コマンド置換 $() で関数の標準出力を変数に代入
discounted_price=$(calculate_discount "$original_price" "$discount_rate")
log_info "元値: ${original_price}円 / 割引率: ${discount_rate}% ➔ 割引後: ${discounted_price}円"

target_dir="/tmp"
if check_directory "$target_dir"; then
    log_info "ディレクトリ (${target_dir}) の確認に成功しました。"
else
    log_error "ディレクトリ (${target_dir}) が見つかりません。"
    exit 1
fi

log_info "すべての処理が正常に完了しました。"

スクリプトの実行手順と事前準備

以下のステップで準備し、動作を確認します。

手順1. 実行権限の付与

ターミナルでスクリプトファイルに実行権限を付与します。

chmod +x func_demo.sh

手順2. スクリプトの実行

作成したスクリプトを実行します。

$ ./func_demo.sh
[2026-07-28 17:45:00] [INFO] 処理を開始します...
[2026-07-28 17:45:00] [INFO] 元値: 10000円 / 割引率: 20% ➔ 割引後: 8000円
[2026-07-28 17:45:00] [INFO] ディレクトリ (/tmp) の確認に成功しました。
[2026-07-28 17:45:00] [INFO] すべての処理が正常に完了しました。

関数化された各処理が呼び出され、ログ出力や計算結果が順番に処理されていることが確認できます。

プログラムコードの詳細解説

スクリプト内で使用している主要な技術ポイントを詳しく解説します。

  • 関数の定義と呼び出し(標準スタイル `func_name() { … }`)
    シェルスクリプトでの関数定義は func_name() { ... } という形式が一般的です。呼び出す際はカッコを付けず、通常のコマンドと同様に func_name 引数1 引数2 と記述します。
  • local キーワードによるスコープ管理
    local msg="${1:-}" のように local を宣言することで、変数のスコープを関数内に限定します。これにより、関数の外側で同名の変数が使われていても競合・汚染を防ぐことができます。
  • 引数の受け取り(位置パラメーター `$1`, `$2`)
    関数に渡された引数は、関数内部で $1(1番目の引数)、$2(2番目の引数)として参照します。$# で引数の個数、$@ で全引数を受け取ることができます。

local キーワードは POSIX 標準規格のコマンドではありませんが、主要シェルで広く採用されています。

function キーワードを「付けるパターン」と「付けないパターン」の使い分け

シェルスクリプトでは、関数の前に function キーワードを明記することも、省略することも可能です。それぞれの特徴と使い分けの基準です。

  • ① function キーワードを省略する(付けなくても良い)パターン【一般的・推奨】
    func_name() { ... } というスタイルです。POSIX標準規格に準拠しているため、様々なUNIX環境でエラーなく動作する互換性を持っています。移植性の高いスクリプトを書く場合は、このスタイルがお勧めです。
  • ② function キーワードを付けるパターン
    function func_name { ... }function func_name() { ... } というスタイルです。POSIX非互換となる場合があるものの、以下のような場面で有効です。
    可読性・視認性の向上:何百行もある巨大なスクリプトで、一目で「ここから関数が始まる」と目立たせたい場合。
    他言語経験者への配慮:Bash前提の環境で、JavaScriptや他のプログラミング言語のように明示的な関数宣言を行いたい場合。

シェルスクリプトにおける「戻り値」の2つの仕組み

他言語(Python等)に慣れている人が最も誤解しやすいのが、シェルスクリプトでの関数の「戻り値」の扱い方です。用途に合わせて以下の2つを使い分けます。

  • 1. return による終了ステータス(真偽値・成功判定)の返却
    return 0(成功)や return 1(失敗・エラー)のように、処理が成功したか否かを示す数値(0〜255)を返します。if check_directory "/tmp"; then のように if 文の条件判定にそのまま使用できます。
  • 2. echo とコマンド置換 `$()` による値・文字列の返却
    計算結果や生成した文字列を返したい場合は、関数内で echo "${final_price}" と出力し、呼び出し側で result=$(calculate_discount 10000 20) とキャッチします。

シェルスクリプトにおける関数の注意点

関数を扱う際、特に注意すべき「4つの落とし穴」とその対策です。

① local キーワード付け忘れによるグローバル変数汚染

関数内で local を付けずに定義した変数は、そのままスクリプト全体のグローバル変数になります。メイン処理で使っているループ変数 itmp などを関数内で書き換えてしまい、バグを引き起こす可能性があるため、関数内の変数には local の付与をお勧めします。

② return に文字列や256以上の指定によるエラー

return "OK"return 500 のように記述すると、構文エラーになったり数値がオーバーフロー(1バイトを超える)して正しいステータスが返りません。

③ 関数定義より前の呼び出しによる実行エラー

シェルスクリプトは上から順に1行ずつ実行されます。関数の定義より上の行で呼び出そうとすると「`command not found`」エラーになる落とし穴があるため、関数の定義はスクリプトの冒頭部分に記述しておくことが推奨されます。

④ set -e(エラー即死)設定と関数内の return 1 による意図しない即時終了

スクリプト冒頭で set -e(エラー発生時に自動停止するオプション)を有効にしている場合、関数が return 1 などの非ゼロ(失敗ステータス)を返した際の挙動に注意が必要です。

  • if 文の条件部で呼ぶ場合
    if check_directory "$target_dir"; then のように if の条件式として呼び出す場合、シェル(Bash等)の仕様により set -e の環境下であっても関数が return 1 を返したことでスクリプトが終了することはなく、正しく else 節へ分岐します。
  • 単体行で呼び出す場合の落とし穴
    if で囲まずに check_directory "$target_dir" を単体行で実行した場合、set -e が働いてスクリプト全体がその場で強制終了してしまいます。関数の戻り値(非ゼロ)を使って独自にエラーハンドリングを行いたい場合は、呼び出し側を必ず if で囲むか、あえて set -uo pipefail-e を外す)にする設計判断が必要です。

まとめ

今回は、シェルスクリプトにおける関数の基本構文、function キーワードの使い分け、引数と local 変数の使い方、returnecho による戻り値の使い分け、そして set -e との兼ね合いについて解説しました。

関数を活用して重複した処理を共通化することで、シェルスクリプトの読みやすさとメンテナンス性が向上します。各種環境でそのまま利用できるテクニックですので、ぜひ日常の自動化スクリプト作成に取り入れてみてください!

コメント

タイトルとURLをコピーしました