bashで関数を定義して再利用する方法|シェルスクリプトのライブラリ化と実践例

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Linuxtips, シェルスクリプト > bashで関数を定義して再利用する方法|シェルスクリプトのライブラリ化と実践例
「同じ処理を何度もスクリプトに書き直している」「コピペで済ませているが、バグを直すたびに複数のスクリプトを修正するはめになる」

Linuxのシェルスクリプトを書いていると、こうした非効率にぶつかります。解決策は関数(function)です。繰り返し使う処理を関数にまとめておけば、呼び出すだけで済み、修正も1箇所で完結します。

この記事では、bashで関数を定義・呼び出す基本構文から、引数の扱い・戻り値・ローカル変数の使い方、さらに複数スクリプトから再利用できる「ライブラリ化」の手法まで、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した実践例とともに解説します。また、declareコマンドを使った関数内の整数変数保護や、ライブラリ関数の存在確認パターンも合わせて紹介します。

この記事のポイント

・bash の関数は function 名 { } または 名 () { } の2通りで定義できる
・引数は $1 $2 ... で受け取り、return で整数の終了ステータスを返す
・local 宣言でローカル変数にすればグローバル変数との衝突を防げる
・source(.コマンド)でライブラリ化し、複数スクリプトから共有する
・declare -F で関数の存在確認をしてからライブラリ関数を安全に呼び出せる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

スクリプトの先頭が `#!/bin/bash` であればどちらでも問題ありません。チーム開発では表記を統一しておくと可読性が上がります。現場では `名前 () {` の形式がよく使われます。

3. 定義の確認と削除

# 定義済み関数の一覧確認 declare -F # 特定の関数の定義を確認 declare -f greet # 関数の削除 unset -f greet

実行結果(declare -f greet の出力例):

greet () { echo "Hello, $1" }

`declare -F` は定義済み関数の一覧を出力します。ライブラリを `source` した後に本当に関数が読み込まれたか確認したい運用場面でよく使います。`declare -f 関数名` を使うと本体コードまで確認できるため、デバッグ時に重宝します。

引数の渡し方($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/存在しないファイル"

実行結果(RHEL 9.4での確認):

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

実行結果(RHEL 9.4 実サーバーでの出力例):

--- サービス確認 --- [起動中] 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 の使い方)

関数内で変数を定義するとき、`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

関数の引数を受け取る変数(`local filepath="$1"` など)は必ず `local` で宣言するのが現場の鉄則です。セミナーでは「`local` を忘れてグローバル変数が壊れる」というトラブルを何度も見てきました。

3. declare -i でローカル整数変数を守る

カウンタや処理件数を関数内で扱う場合、`local` と `declare -i`(整数属性)を組み合わせると、文字列の誤代入をbashが自動検出してくれます。

#!/bin/bash count_lines() { local file="$1" local -i total=0 # local + 整数属性 while IFS= read -r _; do total=total+1 # $(( )) 不要で直接加算できる done < "$file" echo "$total" } result=$(count_lines /etc/hosts) echo "/etc/hosts の行数: $result"

`local -i` は `local` と `declare -i` を1行に書いた省略記法です。整数として宣言した変数は、直接代入構文(`total=total+1`)で算術演算できるため `$(( ))` を省けます。文字列が代入されると自動的に 0 になるため、外部入力を扱う処理でバグの混入を抑制できます。

複数スクリプトから使い回す「ライブラリ化」の手順

同じプロジェクト内の複数スクリプトで共通関数を使いたい場合は、関数だけを集めたライブラリファイルを作り、`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

4. declare -F で関数の存在確認をしてから呼ぶ

ライブラリが正常に読み込まれたかを確認するもう一つの方法として、`declare -F 関数名` を使う方法があります。終了コード 0 が返れば関数が存在し、呼び出せる状態です。

これは「ライブラリが任意読み込み(存在しないことがある)」の環境で特に有効です。実務では監視スクリプトや汎用デプロイスクリプトでよく見るパターンです。

#!/bin/bash # ライブラリを読み込む(存在しない場合はスキップ) LIB_PATH="./lib/logging.sh" if [[ -f "$LIB_PATH" ]]; then source "$LIB_PATH" fi # declare -F で関数が存在するか確認してから呼ぶ if declare -F log_info &>/dev/null; then log_info "スクリプト開始" else echo "[INFO] スクリプト開始" >&2 fi

実行例(RHEL 9.4での確認):

# ライブラリあり [miyazaki@rhel94 ~]$ source myscript.sh [miyazaki@rhel94 ~]$ declare -F declare -f log_info declare -f log_error declare -f send_alert # 関数が存在するか判定(終了コード0=存在) [miyazaki@rhel94 ~]$ declare -F log_info &>/dev/null && echo "OK" || echo "NG" OK

`declare -F` の出力を `&>/dev/null` で捨て、終了コードだけで判定するのが実務でよく使われるパターンです。

応用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' は整数ではありません '' は整数ではありません

3. declare -r でライブラリ内の定数を保護する

複数スクリプトから読み込まれるライブラリでは、設定値を `declare -r`(読み取り専用)で宣言しておくと、呼び出し側から誤って上書きされるのを防げます。

#!/bin/bash # common_lib.sh — ライブラリ側の定数定義 # 二重読み込み防止 [ -n "${_COMMON_LIB_LOADED:-}" ] && return 0 _COMMON_LIB_LOADED=1 # 定数を読み取り専用で宣言(呼び出し側からの上書きを阻止) declare -r LIB_LOG_DIR="${LIB_LOG_DIR:-/var/log/myapp}" declare -r LIB_MAX_RETRY=3 log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "${LIB_LOG_DIR}/app.log" }

呼び出し元で `LIB_MAX_RETRY=99` のような誤代入をしようとすると、bashが即座にエラーを出してくれます。`declare -ir`(整数かつ読み取り専用)と組み合わせれば、型の保護も同時に行えます。

トラブルシュート|よくある関数のエラーと対処

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"

`${BASH_SOURCE[0]}` はスクリプト自身のパスを返すbash組み込み変数です。`source` で読み込まれた場合も正しいパスが取れます。

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 変数名="値"
ローカル整数変数を使う local -i count=0(文字列混入を自動で0に変換)
ライブラリを読み込む source /path/to/lib.sh
定義済み関数を確認する declare -F(一覧)declare -f 関数名(定義内容)
関数の存在を確認する declare -F 関数名 &>/dev/null(終了コード0で存在)
定数を保護する declare -r 変数名="値"(再代入でエラー)
関数を削除する unset -f 関数名
関数を使いこなすとシェルスクリプトの「設計力」が問われるようになります。`local` によるスコープ管理、`return` による終了ステータスの設計、`source` を使ったライブラリ化、そして `declare -F` / `declare -r` / `local -i` を活用した堅牢な変数管理。これらを押さえることで、現場で長く安定稼働するスクリプトが書けるようになります。

シェルスクリプトをはじめとするLinux実務スキルを体系的に身につけたい方は、現役サーバー管理者が直接指導する少人数ハンズオンセミナーもご活用ください。

・Linuxシェルスクリプト入門(基礎から学ぶ自動化の第一歩)
・flockコマンドで二重実行を防止する方法(排他制御の実践)
・set -x でシェルスクリプトをデバッグする方法(set -e・set -u との組み合わせも)
・printf コマンドで出力を整形する方法(echo との違いも解説)
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Linux無料マニュアルを受け取る >>

<PR>手元に置いて学びを深める1冊

Bashシェルスクリプト入門

まず無料メルマガで学ぶのが第一歩、加えて手元に置くなら本書。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。