Linuxのシェルスクリプトを書いていると、こうした非効率にぶつかります。解決策は関数(function)です。繰り返し使う処理を関数にまとめておけば、呼び出すだけで済み、修正も1箇所で完結します。
この記事では、bashで関数を定義・呼び出す基本構文から、引数の扱い・戻り値・ローカル変数の使い方、さらに複数スクリプトから再利用できる「ライブラリ化」の手法まで、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した実践例とともに解説します。
この記事のポイント
・bash の関数は function 名 { } または 名 () { } の2通りで定義できる
・引数は $1 $2 ... で受け取り、return で整数の終了ステータスを返す
・local 宣言でローカル変数にすればグローバル変数との衝突を防げる
・source(.コマンド)でライブラリ化し、複数スクリプトから共有する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
bashの関数とは何か(再利用の仕組み)
関数とは「名前を付けた処理のまとまり」です。一度定義すれば、同じシェルセッションやスクリプト内で何度でも呼び出せます。プログラミング言語の関数と同じ発想で、以下のメリットがあります。
・DRY原則の実現:同じ処理を1箇所にまとめ、コードの重複を排除できる
・保守性の向上:バグを直す箇所が1箇所で済む
・可読性の向上:処理に名前が付くためスクリプト全体の意図がわかりやすくなる
・ライブラリ化:複数のスクリプトから共通ロジックを使い回せる
bashの関数はシェルの組み込み機能であり、外部コマンドの呼び出しよりも高速に動作します。ループや条件分岐を多用するスクリプトでは、共通処理を関数化するだけでコードが劇的にすっきりします。
関数の定義構文(2通りの書き方)
bashで関数を定義する書き方は2通りあります。どちらも動作は同じです。1. function キーワードを使う書き方
# function キーワードを使った定義 function greet { echo "Hello, $1" } # 呼び出し greet "Linux"
Hello, Linux
2. 名前 () { } の書き方(POSIX互換)
# POSIX互換の書き方 greet() { echo "Hello, $1" }
3. 定義の確認と削除
# 定義済み関数の一覧確認 declare -F # 特定の関数の定義を確認 declare -f greet # 関数の削除 unset -f greet
greet () { echo "Hello, $1" }
引数の渡し方($1 $2 ... と $@)
関数に渡した引数は、コマンドライン引数と同じ記法で参照できます。1. 基本的な引数の受け取り
#!/bin/bash # ファイルが存在するか確認する関数 check_file() { local filepath="$1" if [ -z "$filepath" ]; then echo "エラー: ファイルパスが指定されていません" >&2 return 1 fi if [ -f "$filepath" ]; then echo "OK: $filepath は存在します" return 0 else echo "NG: $filepath が見つかりません" >&2 return 2 fi } # 呼び出し check_file "/etc/hosts" check_file "/etc/存在しないファイル"
OK: /etc/hosts は存在します NG: /etc/存在しないファイル が見つかりません
2. 複数引数と $@ の使い方
#!/bin/bash # 複数のサービスをまとめて起動確認する関数 check_services() { echo "--- サービス確認 ---" for svc in "$@"; do if systemctl is-active --quiet "$svc"; then echo " [起動中] $svc" else echo " [停止中] $svc" fi done echo "--------------------" } # 複数サービスを一括確認 check_services sshd crond firewalld
--- サービス確認 --- [起動中] sshd [起動中] crond [起動中] firewalld --------------------
戻り値とreturnの使い方
bashの `return` で返せるのは 0~255 の整数(終了ステータス)のみです。文字列や複雑なデータを「戻り値」として返したい場合は別の方法を使います。1. return で終了ステータスを返す
#!/bin/bash # ディスク使用率が閾値を超えたらエラーを返す check_disk_usage() { local mount_point="${1:-/}" local threshold="${2:-80}" # dfの出力からUse%の数値だけ取得 local usage usage=$(df --output=pcent "$mount_point" 2>/dev/null | tail -1 | tr -d '% ') if [ -z "$usage" ]; then echo "エラー: $mount_point のディスク情報を取得できません" >&2 return 1 fi echo "$mount_point の使用率: ${usage}%" if [ "$usage" -ge "$threshold" ]; then echo "警告: 使用率が ${threshold}% を超えています" >&2 return 2 fi return 0 } # 呼び出しと戻り値の確認 check_disk_usage "/" 70 exit_code=$? echo "終了コード: $exit_code"
/ の使用率: 45% 終了コード: 0
2. echo で文字列を返す(コマンド置換で受け取る)
文字列を「返したい」場合は `echo` で出力し、呼び出し元でコマンド置換 `$()` を使って受け取ります。#!/bin/bash # タイムスタンプ付きのログファイル名を生成して返す generate_logname() { local prefix="${1:-backup}" local timestamp timestamp=$(date '+%Y%m%d_%H%M%S') echo "${prefix}_${timestamp}.log" } # コマンド置換で受け取る LOGFILE=$(generate_logname "deploy") echo "ログファイル名: $LOGFILE"
ログファイル名: deploy_20260626_143022.log
ローカル変数とスコープ(local の使い方)
関数内で変数を定義するとき、`local` を付けないとグローバルスコープになります。これは予期しないバグの原因になります。1. local を使わない場合の問題
#!/bin/bash counter=10 # グローバル変数 bad_func() { counter=99 # local なし → グローバルを上書きしてしまう echo "関数内: counter=$counter" } echo "呼び出し前: counter=$counter" bad_func echo "呼び出し後: counter=$counter" # 99 になってしまう
呼び出し前: counter=10 関数内: counter=99 呼び出し後: counter=99
2. local を付けて安全に使う
#!/bin/bash counter=10 # グローバル変数 good_func() { local counter=99 # ローカルスコープ echo "関数内: counter=$counter" } echo "呼び出し前: counter=$counter" good_func echo "呼び出し後: counter=$counter" # 元の値を保持
呼び出し前: counter=10 関数内: counter=99 呼び出し後: counter=10
複数スクリプトから使い回す「ライブラリ化」の手順
同じプロジェクト内の複数スクリプトで共通関数を使いたい場合は、関数だけを集めたライブラリファイルを作り、`source` コマンド(または `.` コマンド)で読み込みます。1. ライブラリファイルを作成する
# /usr/local/lib/bash/common_lib.sh として保存する例 #!/bin/bash # common_lib.sh — 共通ライブラリ # ログ出力(タイムスタンプ+レベル付き) log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" } log_warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $*" >&2 } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2 } # ファイル存在確認(なければ即エラー終了) require_file() { local path="$1" if [ ! -f "$path" ]; then log_error "必須ファイルが存在しません: $path" return 1 fi } # ディレクトリが存在しなければ作成する ensure_dir() { local dir="$1" if [ ! -d "$dir" ]; then mkdir -p "$dir" || { log_error "ディレクトリ作成失敗: $dir"; return 1; } log_info "ディレクトリを作成しました: $dir" fi }
2. 呼び出し側スクリプトで source する
#!/bin/bash # deploy.sh — デプロイスクリプト # ライブラリを読み込む(source または "." コマンド) LIB_DIR="/usr/local/lib/bash" # shellcheck source=/usr/local/lib/bash/common_lib.sh source "${LIB_DIR}/common_lib.sh" || { echo "ライブラリの読み込みに失敗しました: ${LIB_DIR}/common_lib.sh" >&2 exit 1 } DEPLOY_DIR="/var/www/html/app" BACKUP_DIR="/var/backup/app" log_info "デプロイ開始" ensure_dir "$BACKUP_DIR" require_file "/etc/app/config.yml" || exit 1 log_info "デプロイ完了"
[2026-06-26 14:30:01] [INFO] デプロイ開始 [2026-06-26 14:30:01] [INFO] ディレクトリを作成しました: /var/backup/app [2026-06-26 14:30:01] [INFO] デプロイ完了
3. source の失敗に備えるパターン
ライブラリファイルが存在しない場合、`source` はエラーを出してスクリプト全体が止まります(`set -e` 使用時)。上記のように `||` で失敗時の処理を書いておくのが安全です。また、ライブラリが重複して読み込まれないようにするには、読み込み済みフラグを使う方法が効果的です。
# common_lib.sh の先頭に追加 # 二重読み込み防止 [ -n "${_COMMON_LIB_LOADED:-}" ] && return 0 _COMMON_LIB_LOADED=1
応用Tips|再帰・デフォルト引数・型チェック
1. デフォルト引数の設定(${1:-デフォルト値})
#!/bin/bash # 引数が省略されたらデフォルト値を使う create_backup() { local src="${1:-/var/www/html}" local dest="${2:-/var/backup}" local filename="${3:-backup_$(date '+%Y%m%d').tar.gz}" echo "バックアップ: $src → $dest/$filename" tar -czf "${dest}/${filename}" "$src" 2>/dev/null } # 引数なしで呼び出し → デフォルト値が使われる create_backup # 引数を指定して呼び出し create_backup /etc /tmp "etc_backup.tar.gz"
2. 引数が数値かどうかチェックする関数
#!/bin/bash is_integer() { local val="$1" [[ "$val" =~ ^-?[0-9]+$ ]] } # 使用例 for val in "42" "-5" "3.14" "abc" ""; do if is_integer "$val"; then echo "'$val' は整数です" else echo "'$val' は整数ではありません" fi done
'42' は整数です '-5' は整数です '3.14' は整数ではありません 'abc' は整数ではありません '' は整数ではありません
トラブルシュート|よくある関数のエラーと対処
1. 関数が定義されていないというエラー
`command not found` が出る場合、関数の定義より前に呼び出している可能性があります。bashはスクリプトを上から順に実行するため、関数の定義は呼び出しより前に書く必要があります。# NG: 定義より前に呼び出している my_func # ← command not found my_func() { echo "hello" }
# OK: 先に定義してから呼び出す my_func() { echo "hello" } my_func # ← 正常に実行される
2. return が意図通りに動かない
`return` はあくまで「関数内での終了ステータス」を設定するものです。`exit` と違い、関数を抜けるだけでスクリプト全体は終了しません。また、コマンド置換 `$()` 内で `return` を使うと意図と異なる動作になることがあります。関数の終了ステータスは `$?` で受け取るのが基本です。
3. ライブラリが読み込まれない(パスの問題)
`source common_lib.sh` のように相対パスで指定すると、スクリプトをどこから実行するかによってパスが変わります。# スクリプト自身のディレクトリを基準にライブラリを読み込む SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common_lib.sh"
4. set -e 使用時に関数が途中終了する
`set -e`(エラー即終了)を有効にしている場合、関数内でゼロ以外の終了ステータスが返ると即座にスクリプトが終了します。意図的にエラーを無視したい処理は `command || true` や `if command; then ...` の形で書くことで回避できます。
#!/bin/bash set -e # NG: ファイルが存在しないと set -e でスクリプトが終了する check_optional_file() { local path="$1" [ -f "$path" ] # 存在しない場合、終了ステータス1で set -e が発動 } # OK: || true で set -e の影響を抑制する check_optional_file() { local path="$1" [ -f "$path" ] || true }
本記事のまとめ
bashの関数機能を活用すると、シェルスクリプトの品質と保守性が大きく向上します。| やりたいこと | コマンド・記法 |
|---|---|
| 関数を定義する | 関数名() { 処理; } |
| 引数を受け取る | local arg="$1" |
| 全引数をループで処理する | for item in "$@"; do |
| 終了ステータスを返す | return 0(成功)return 1(失敗) |
| 文字列を「返す」 | echo "$result"(呼び元で $(...) で受取) |
| ローカル変数を使う | local 変数名="値" |
| ライブラリを読み込む | source /path/to/lib.sh |
| 定義済み関数を確認する | declare -F(一覧)declare -f 関数名(定義内容) |
| 関数を削除する | unset -f 関数名 |
シェルスクリプトをはじめとするLinux実務スキルを体系的に身につけたい方は、現役サーバー管理者が直接指導する少人数ハンズオンセミナーもご活用ください。
・Linuxシェルスクリプト入門(基礎から学ぶ自動化の第一歩)
・flockコマンドで二重実行を防止する方法(排他制御の実践)
・set -x でシェルスクリプトをデバッグする方法(set -e・set -u との組み合わせも)
・printf コマンドで出力を整形する方法(echo との違いも解説)
Linux無料マニュアルを受け取る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:Linuxのネットワークボンディングを設定する方法|bond0作成・VLANとブリッジのnmcli実践例
- この記事の属するカテゴリ:Linuxtips・シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます