この記事の狙い
Bash 関数を**「ただの処理の寄せ集め」**ではなく、再利用可能な部品として設計するためのパターンを整理します。
引数の受け方・戻り値の返し方・エラー伝播・副作用の扱いなどを、コピペ可の最小例とともに示します。
前提と対象
- Bash 4 以上を想定
set -Eeuo pipefailを前提とした安全設計- POSIX 準拠よりも Bash 独自の便利機能(
local,declare -n,printf -v等)を活用
TL;DR(最小実装・コピペ可)
#!/usr/bin/env bash
set -Eeuo pipefail
# 引数を受け、stdoutに値を返す
to_upper() {
local s="$1"
printf '%s\n' "${s^^}"
}
# エラー時は非ゼロexit
require_file() {
local path="$1"
[[ -f "$path" ]] || { printf 'no such file: %s\n' "$path" >&2; return 2; }
}
# 呼び出し元変数に書き込む(printf -v)
get_date() {
local __outvar="$1"
printf -v "$__outvar" '%(%Y-%m-%d)T' -1
}
# namerefで配列を加工
dedup_array() {
local -n arr="$1"
local -A seen=()
local out=()
for x in "${arr[@]}"; do
[[ ${seen["$x"]+x} ]] || { out+=("$x"); seen["$x"]=1; }
done
arr=("${out[@]}")
}
# --- 利用例 ---
echo "$(to_upper "foo")" # => FOO
require_file /etc/passwd # => OK
get_date today; echo "$today" # => 2025-09-25
items=(a a b); dedup_array items; declare -p items
設計の要点(原則)
関数の設計は「入力(引数)/出力(戻り値)/副作用(stderr, exit, global state)」を明確に切り分けるのが基本です。
- 引数は位置引数
$1$2… をlocalに受け直す
→ 可読性・安全性が上がり、"$@"展開事故を防ぐ。 - 戻り値は stdout に出す/呼び出し元変数に書き込む/exit code の3パターン
→ 目的に応じて一貫して選ぶ。 - 副作用は stderr(ログ)か環境変数 export のみに限定
→ 想定外の汚染を避ける。
引数の受け方パターン
単純入力:位置引数を local に
greet() {
local name="$1"
printf 'Hello, %s\n' "$name"
}
greet "Alice"
可変長入力:"$@" をそのまま利用
join_by() {
local sep="$1"; shift
local IFS="$sep"
printf '%s\n' "$*"
}
join_by ',' a b "c d" # => a,b,c d
オプション入力:getopts パターン
parse_opts() {
local verbose=0 out=""
while getopts "vo:" opt; do
case "$opt" in
v) verbose=1 ;;
o) out="$OPTARG" ;;
*) return 2 ;;
esac
done
shift $((OPTIND-1))
echo "verbose=$verbose out=$out rest=$*"
}
parse_opts -v -o file arg1 arg2
戻り値の流儀パターン
stdout で返す(最小・Unix 的)
basename_only() {
local path="$1"
printf '%s\n' "${path##*/}"
}
file="$(basename_only /tmp/a.txt)"
呼び出し元変数に格納(printf -v)
random_hex() {
local __out="$1"
printf -v "$__out" '%x' $((RANDOM<<16|RANDOM))
}
random_hex token; echo "$token"
nameref で書き換え(Bash 4.3+)
reverse_array() {
local -n ref="$1"
ref=($(printf '%s\n' "${ref[@]}" | tac))
}
arr=(1 2 3); reverse_array arr; declare -p arr
exit code で返す(成功/失敗のみ)
is_dir() { [[ -d "$1" ]]; }
if is_dir /etc; then echo ok; fi
エラーと例外処理
- 予測可能な失敗は return code で表現
- 想定外の失敗は
set -eとtrapで拾う - メッセージは stderr に送る
require_readable() {
local f="$1"
[[ -r "$f" ]] || { printf 'cannot read %s\n' "$f" >&2; return 2; }
}
パターン別まとめ表
| パターン | 用途 | 実装例 |
|---|---|---|
| stdout 戻り値 | 値をコマンド置換で受ける | result="$(func args)" |
| printf -v | 文字列を安全に変数へ | func outvar |
| nameref | 配列/構造体を in-place 書換 | func arrayname |
| exit code | 成否のみ判定 | if func; then ... fi |
失敗しやすい点(アンチパターン)
- 未クォートの
$@→ 引数が分裂/空要素消失 - 戻り値に echo → 改行や
-nオプションの混入で壊れる - グローバル変数へ直接代入 → 予期せぬ汚染
- exit で関数を終える → スクリプトごと終了する危険。
returnを使う
テストと品質(最小)
代表的な関数を検証する“コピペ可”ブロック。
#!/usr/bin/env bash
set -Eeuo pipefail
source ./functions.sh
out="$(basename_only /tmp/x.txt)"
[[ "$out" == "x.txt" ]] || { echo fail; exit 1; }
random_hex token
[[ "$token" =~ ^[0-9a-f]+$ ]] || { echo fail; exit 1; }
arr=(a b c); reverse_array arr
[[ "${arr[0]}" == "c" ]] || { echo fail; exit 1; }
is_dir /etc || { echo fail; exit 1; }
echo PASS
運用設計(実務)
- 関数名は動詞+目的語(
load_config,send_mail) - 入力は必ずクォートして受ける
- 出力は stdout/stderr を分ける
- 引数が多い場合は連想配列や設定ファイルに逃す
- ライブラリ化する場合は
set -uに耐える設計("${var-}")
互換性と移植性
declare -n(nameref)は Bash 4.3+。古い環境ではprintf -vで代替。- macOS 標準の Bash 3.x は要注意。必要なら Homebrew で新しい Bash を導入。
- POSIX シェルでは配列・連想配列が使えないため、別の設計が必要。
セキュリティと安全設計
- 外部入力はそのまま変数名にしない(
declare -n "$userinput"は危険)。 - 常に ダブルクォートで展開。
- 戻り値がファイルパスなら NUL セーフな形式で出力(
printf '%s\0'と組み合わせ)。
パフォーマンスの勘所(短く)
- 小さい値なら stdout 戻りで十分。
- 配列の大規模操作は nameref を使ってコピーを避ける。
- 関数の多用によるオーバーヘッドは軽微。可読性優先で分割してよい。
