シェルスクリプトの関数名の衝突を防ぐ設計|プレフィックス命名とdeclare -Fで既存の外部コマンド上書きを避ける

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトの関数名の衝突を防ぐ設計|プレフィックス命名とdeclare -Fで既存の外部コマンド上書きを避ける
「ライブラリを source した途端、ログ関数の出力フォーマットが変わった。なぜだ?」
シェルスクリプトのプロジェクトが育ってくると、こうした奇妙なバグに悩まされることがある。調べてみると、自分が付けた関数名が既存の外部コマンドや bash 組み込みと衝突していた、あるいは複数のライブラリ間で同名の関数が互いに上書きし合っていた、という原因が多い。

この記事では、bash 関数名の衝突がなぜ起きるのかという仕組みの解説から、declare -F による棚卸し・type コマンドによる事前確認・プレフィックス命名規約・readonly -f による保護まで、衝突を根本から防ぐ実践的な設計パターンを順番に解説する。

この記事のポイント

・bash は「関数 → 組み込み → 外部コマンド」の順で名前を解決するため、関数名が既存名と同じだと外部コマンドが呼ばれなくなる
・declare -F で定義済みの全関数を一覧し、type -t で衝突リスクを事前に確認できる
・プロジェクト識別子をプレフィックスに使う命名規約(例: bk_log)が衝突を根本から防ぐ
・readonly -f で公開済みの関数をロックし、source 読み込み時の意図しない上書きをブロックできる


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

なぜ bash 関数名が既存コマンドと衝突するのか

bash が名前を解決するとき、以下の優先順位で探索する。

# bash の名前解決優先順位(上が高い) # 1. エイリアス (alias) # 2. シェル関数 (function) ← 自分で定義した関数 # 3. シェル組み込み (builtin) ← cd, echo, printf, read など # 4. 外部コマンド (PATH 上のファイル) ← /bin/ls, /usr/bin/date など

ポイントは「シェル関数が外部コマンドより優先される」点だ。lsdate という名前の関数を定義すると、それ以降そのシェルセッション(および source で読み込んだすべての子スクリプト)で外部コマンドの lsdate が呼ばれなくなる。

1. 衝突が引き起こす問題(実例)

次のコードは現場でよく見かける事故パターンだ。

# 【事故例1】ls という名前で「常に -la を付けたい」と思って関数を定義 ls() { ls -la --color=auto "$@" # ← ls が ls を呼ぶ無限再帰になる } # 実行すると bash がスタックオーバーフローで強制終了する

# 【事故例2】ライブラリスクリプト lib_a.sh に date という名前の関数を定義 date() { echo "$(command date '+%Y%m%d')" # 引数なしで日付文字列だけ返す } # main.sh で source して呼び出す source ./lib_a.sh BACKUP_FILE="/backup/data_$(date '+%Y-%m-%d').tar.gz" # ↑ date 関数が呼ばれるため、'+%Y-%m-%d' は単なる引数として無視され # "data_20260908.tar.gz" ではなく "data_20260908.tar.gz" という # フォーマット崩れが起きる(または意図しない挙動になる)

2. 複数ライブラリ間での上書き衝突

外部コマンドだけでなく、ライブラリ同士の命名衝突も同様の問題を起こす。

# lib_a.sh: 汎用名を使ったライブラリ log() { echo "[INFO] $*"; } # lib_b.sh: 別の開発者が書いた別ライブラリ(同名で定義) log() { echo "[DEBUG] $*"; } # main.sh source ./lib_a.sh source ./lib_b.sh log "バックアップ完了" # → "[DEBUG] バックアップ完了" と出力される # lib_b.sh の log が lib_a.sh の log を上書きした

「汎用的で短い名前(log・check・backup・cleanup・run・init)は衝突しやすい」と覚えておくとよい。

declare -F で定義済み関数を棚卸しする

関数名の衝突を防ぐ第一歩は、「今シェルに何という名前の関数が定義されているか」を把握することだ。そのためのコマンドが declare -F だ。

1. declare -F の基本使い方

# 定義済みのシェル関数名を一覧する declare -F # 関数名だけを抽出する場合 declare -F | awk '{print $3}'

RHEL 9.4 でバックアップライブラリを source した後の実際の出力例を示す。

$ source ./lib/bk_core.sh $ declare -F declare -f _bk_build_tar_cmd declare -f _bk_rotate_old declare -f _bk_validate_args declare -f bk_cleanup declare -f bk_full declare -f bk_incremental declare -f bk_log declare -f bk_status

declare -F の出力は「declare -f 関数名」の形式で並ぶ。関数名だけ取り出したい場合は awk '{print $3}' で3列目を抽出すればよい。

2. declare -f 関数名 で定義内容を確認する

declare -f(小文字 f)に関数名を指定すると、その関数の定義内容がそのまま出力される。未知のライブラリを読み込んだ後に「この関数は何をしているか」を確認するのに使える。

$ declare -f bk_log bk_log () { local level="$1" local msg="$2" echo "[$level] $(date '+%Y-%m-%d %T') $msg" }

関数が定義されているかどうかだけ確認したい場合は、declare -F 関数名(大文字 F)の戻り値で判定できる。

# 関数が定義されているか確認する(0=定義済み、1=未定義) declare -F bk_log >&/dev/null && echo "定義済み" || echo "未定義"

type コマンドで衝突リスクを事前に確認する

declare -F はシェル関数の棚卸しツールだが、「これから定義しようとする名前が既存の何かと衝突しないか」を確認するには type コマンドを使う。

1. type -t で名前の種別を調べる

# type -t は名前の種別を alias/builtin/function/file のいずれかで返す # 何も見つからなければ何も出力せず終了コード 1 を返す $ type -t ls alias $ type -t cd builtin $ type -t date file $ type date date is /usr/bin/date $ type -t bk_log function $ type -t nonexistent_name (何も出力されない・exit 1)

上記の出力は Ubuntu 24.04 LTS での実測値だ(ls は .bashrc でエイリアス設定済みのため alias と表示される)。
種別ごとの意味をまとめると次のようになる。
type -t の出力 意味 定義すると
alias エイリアスとして定義済み alias より関数が優先される
builtin bash 組み込みコマンド 組み込みが関数に隠される
file PATH 上の外部コマンド 外部コマンドが関数に隠される
function 既にシェル関数として定義済み 既存の関数が上書きされる
(空) 未定義・衝突なし 安全に定義できる

2. 衝突チェックを関数化して再利用する

ライブラリスクリプトの冒頭でこのチェックを自動化しておくと、source した時点でミスを検出できる。

# _lib_assert_safe_name: 指定した名前が既存の builtin/file と衝突しないか確認する # 引数: 関数名 # 戻り値: 0=安全、1=衝突あり(エラーを stderr に出力) _lib_assert_safe_name() { local name="$1" local kind kind=$(type -t "$name" 2>/dev/null) case "$kind" in builtin|file|alias) printf "ERROR: '%s' は既に '%s' として存在します。関数名を変更してください。\n" \ "$name" "$kind" >&2 return 1 ;; *) return 0 ;; # 未定義または既存の function: 呼び出し元で判断 esac } # ライブラリの先頭でチェックしてから定義する _lib_assert_safe_name "bk_log" || exit 1 bk_log() { local level="$1" local msg="$2" echo "[$level] $(date '+%Y-%m-%d %T') $msg" }

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

プレフィックス命名規約で衝突を根本から防ぐ

衝突チェックは「後からミスを検出する」仕組みだ。しかし「そもそも衝突しない名前を付ける」設計の方が根本的な解決になる。それがプレフィックス命名規約だ。

1. プロジェクト識別子をプレフィックスに使う

関数名の先頭にプロジェクト名や機能ブロックの略称を付けることで、他のコマンドや別ライブラリとの衝突をほぼゼロにできる。

# 【悪い例】汎用的な名前は衝突しやすい log() { echo "[INFO] $*"; } backup() { tar czf "/backup/$(date +%Y%m%d).tar.gz" "$@"; } check() { df -h; } cleanup() { rm -f /tmp/work.*; } # 【良い例】プロジェクト識別子(2~4文字)をプレフィックスに使う # bk = backup project の略称 bk_log() { echo "[INFO] $*"; } bk_backup() { tar czf "/backup/$(date +%Y%m%d).tar.gz" "$@"; } bk_check() { df -h; } bk_cleanup() { rm -f /tmp/work.*; }

プレフィックスの選び方の指針を示す。
・プロジェクト名の略称を使う(backup → bk、deploy → dp、monitor → mn など)
・2~4文字程度にしてタイプコストを最小化する
・既存の外部コマンドや bash 組み込みと同じにならない文字列を選ぶ
・チーム内でプレフィックス一覧を管理し、新規ライブラリ追加時に割り当てる

2. アンダースコア始まりで内部専用関数を区別する

ライブラリの外から呼ばれる「公開関数」と、ライブラリ内部だけで使う「内部ヘルパー関数」を命名で区別すると、可読性と安全性が上がる。

# 公開関数: プレフィックス付き(外部スクリプトから呼び出してよい) bk_run_full() { _bk_validate_dir "$1" && _bk_do_tar "$1"; } bk_run_incremental() { _bk_validate_dir "$1" && _bk_do_rsync "$1"; } bk_get_status() { _bk_read_last_log; } # 内部ヘルパー: _ 始まり(同一ライブラリファイル内だけで使う) _bk_validate_dir() { [[ -d "$1" ]] || { echo "ERROR: $1 が存在しません" >&2; return 1; }; } _bk_do_tar() { tar czf "/backup/$(date +%Y%m%d_%H%M%S).tar.gz" "$1"; } _bk_do_rsync() { rsync -a --link-dest=/backup/latest "$1" "/backup/$(date +%Y%m%d)/"; } _bk_read_last_log() { tail -1 /var/log/bk_backup.log 2>/dev/null; }

「_ 始まりは内部専用」という慣例は C 言語や Python でも使われる共通概念なので、チームへの説明コストも低い。

3. 特に避けるべき関数名の例

bash の組み込みコマンドと外部コマンドの両方に存在するため、特に衝突リスクが高い名前を挙げる。
避けるべき関数名 理由
log 汎用名・複数ライブラリで重複しやすい
date /usr/bin/date(外部コマンド)を隠す
ls /bin/ls を隠す・エイリアスと衝突する
echo bash 組み込みを隠す(printf に影響が出ることも)
test bash 組み込みの test コマンドを隠す
run / exec / init 汎用的すぎて複数ライブラリで重複しやすい
cleanup / check / backup 同上

readonly -f で意図しない上書きをブロックする

プレフィックス命名規約だけでは防げない問題がある。「同じプレフィックスを使った別のライブラリが source され、後から定義を上書きしてしまう」ケースだ。これを防ぐのが readonly -f だ。

1. readonly -f の基本的な使い方

bk_log() { local level="$1" local msg="$2" echo "[$level] $(date '+%Y-%m-%d %T') $msg" } # 関数を定義した直後に readonly -f でロックする readonly -f bk_log # これ以降、bk_log を再定義しようとするとエラーになる bk_log() { echo "上書き"; }

$ source ./lib/bk_core.sh $ bk_log() { echo "上書き"; } bash: bk_log: readonly function

readonly -f は「その関数名への再代入を bash が拒否する」仕組みだ。source で新しいライブラリを読み込んでも、ロック済みの関数は書き換えられない。

2. ライブラリ末尾でまとめてロックするパターン

関数を1つずつ readonly -f するのは記述が冗長になる。ライブラリ末尾でプレフィックスによる絞り込みをしてまとめてロックする書き方が実用的だ。

# bk_core.sh の末尾に追加する # bk_ で始まる全公開関数を readonly -f でロック while IFS= read -r fn_name; do readonly -f "$fn_name" done < <(declare -F | awk '$3 ~ /^bk_/ {print $3}')

内部専用関数(_ 始まり)は意図的にロックしない選択もある。内部関数はライブラリ内での局所的な修正ニーズが生じることがあるためだ。どちらをロックするかはプロジェクトの方針で決める。

3. すでに readonly な関数を確認する

readonly -f が適用済みかどうかは declare -f -p 関数名 で確認できる。

# readonly -f が適用されているかを確認する # readonly な関数には "-fr" フラグが付く(f=function, r=readonly) $ declare -p -f bk_log declare -fr bk_log

declare -fr と表示されれば readonly が有効だ。-f だけなら readonly は未設定の状態になる。

実務パターン:複数ライブラリを安全に読み込む設計

バックアップスクリプトを例に、プレフィックス命名・declare -F・readonly -f を組み合わせた実務パターンを示す。

1. ライブラリファイルの構成

lib/ ├── bk_log.sh # ログ関数(bk_log, bk_log_error) ├── bk_backup.sh # バックアップ本体(bk_full, bk_incremental) └── bk_notify.sh # 通知関数(bk_notify_slack, bk_notify_mail)

各ライブラリは末尾で readonly -f によるロックを実施する(前述のパターンを使う)。

2. エントリポイントスクリプトでの安全な読み込み

#!/bin/bash set -euo pipefail LIB_DIR="$(cd "$(dirname "$0")/lib" && pwd)" # ライブラリをまとめて読み込む for lib_file in "${LIB_DIR}"/bk_*.sh; do if [[ ! -f "$lib_file" ]]; then echo "ERROR: ライブラリが見つかりません: $lib_file" >&2 exit 1 fi # shellcheck source=/dev/null source "$lib_file" done # 読み込み後に定義済み関数を確認(デバッグ時に有用) # declare -F | awk '$3 ~ /^bk_/ {print $3}' # エントリポイント bk_log "INFO" "バックアップ開始" bk_full "/var/data" || { bk_log "ERROR" "バックアップ失敗"; exit 1; } bk_notify_slack "バックアップ完了: $(date '+%Y-%m-%d %T')" bk_log "INFO" "バックアップ完了"

3. 読み込み後に衝突の有無を機械的に確認する

CI/CD に組み込む場合は、ライブラリ読み込み後に既知の外部コマンド名と衝突していないかを自動チェックするスクリプトを加えると安全だ。

# 衝突チェックスクリプト(CI で実行する) DANGER_NAMES=( ls cp mv rm mkdir find grep sed awk date printf echo read test true false cat head tail sort uniq wc cut log backup check cleanup run exec init ) for name in "${DANGER_NAMES[@]}"; do if declare -F "$name" >&/dev/null; then echo "WARN: 危険な関数名が定義されています: $name" >&2 fi done

この衝突チェックを make lint や GitHub Actions の linting ステップに追加しておけば、事故の多くを自動検知できる。

よくあるエラーと対処法

1. 「bash: 関数名: readonly function」が出る

readonly -f でロックした関数を再定義しようとすると発生する。意図せず同名の関数を定義しているスクリプトや読み込んでいるライブラリを特定する。

# エラー例 $ source ./lib_b.sh bash: bk_log: readonly function # 対処: どこで bk_log が再定義されているかを確認する grep -rn "bk_log\s*()" ./lib_b.sh # または、lib_b.sh を source する前に readonly フラグを確認する declare -p -f bk_log 2>/dev/null # "declare -fr bk_log" と表示されれば readonly が有効

すでに readonly -f が適用されている関数を上書きする正当な理由がある場合(テスト時のモック差し替えなど)は、新しいシェルプロセスを起動して readonly の影響を受けない環境で実行するのが正解だ。readonly は子プロセスには引き継がれない。

2. 意図せず定義された衝突関数を削除したい

unset -f で関数の定義を削除できる。ただし readonly -f が適用されていると unset -f も失敗する。

# 関数の定義を削除する unset -f ls # 関数として定義された ls を削除 unset -f date # 関数として定義された date を削除 # 削除後に確認する type -t ls # alias (元のエイリアスに戻る) type -t date # file (外部コマンドが使えるようになる) # readonly が適用された関数は unset -f できない $ unset -f bk_log bash: unset: bk_log: cannot unset: readonly function # → サブシェル(bash -c や スクリプト実行)で処理するしかない

3. declare -F で大量の関数が表示されて把握しきれない

インタラクティブシェルでは ~/.bashrc や各種ツール(git, nvm, rbenv など)が大量の関数を定義していることがある。自分のプロジェクトの関数だけを絞り込むにはプレフィックスでフィルタリングする。

# bk_ プレフィックスの関数だけを一覧する declare -F | awk '$3 ~ /^bk_/ {print $3}' # _ 始まりの内部関数を除いた公開関数だけを一覧する declare -F | awk '$3 ~ /^bk_[^_]/ {print $3}'

まとめ

bash 関数名の衝突防止は「名前解決の優先順位を知り、宣言前に確認し、命名規約でリスクを根絶し、readonly で保護する」という4層の設計で実現できる。
やりたいこと コマンド/方法
定義済み関数を一覧する declare -F
関数名だけ抽出する declare -F | awk '{print $3}'
関数の定義内容を確認する declare -f 関数名
名前の種別を確認する type -t 名前
関数が定義済みか確認する declare -F 関数名 >&/dev/null
関数を再定義不可にする readonly -f 関数名
readonly 適用済みか確認する declare -p -f 関数名(-fr なら適用済み)
衝突を根本から防ぐ命名 プレフィックス_関数名(例: bk_log)
内部専用関数を区別する _プレフィックス_関数名(例: _bk_validate)
シェルスクリプトが1ファイルで完結している間は衝突問題は表面化しにくいが、ライブラリ化・チーム開発・CI/CD 組み込みへとスケールした瞬間に一気に問題になる。プレフィックス命名規約と readonly -f は「書くのは数秒、後で救われるのは何時間も」という投資対効果の高い設計だ。今のプロジェクトに取り入れることを強くすすめる。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

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

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

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

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

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

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

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

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

この記事を書いた人

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

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

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