関数設計パターン集|引数/戻り値の流儀

関数・モジュール化
スポンサーリンク

この記事の狙い

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 -etrap で拾う
  • メッセージは 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 を使ってコピーを避ける。
  • 関数の多用によるオーバーヘッドは軽微。可読性優先で分割してよい。

参考リンク

スポンサーリンク
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする