Linuxでのシェルスクリプトの関数内ローカル変数の活用方法

Bash function内のlocal variableで最初に求められるのはcommand暗記ではなく、何を正しい結果とするかの定義です。結論は「Bash functionではlocalで一時variableを閉じ込め、globalを書き換える副作用を減らします。ただしdynamic scope、array、nameref、subshellの挙動を明示します。」。BashのlocalはPOSIX shの必須機能ではなく、dynamic scopeで呼び出し先functionから見える場合があることを確認し、表示、加工、変更を混同しない順序で進めます。 確認ポイント:localはBash固有かつdynamic scopeであり、array出力はlocal配列そのものを展開します。

目次

Bash interpreterとglobal変数を先に確認する

現在値を変更前snapshotとして保存します。存在確認だけでなく、所有者、権限、版、依存関係を必要な範囲で読み、backupが必要な対象を洗い出します。

  • scriptのinterpreterがBashか確認する
  • globalと同名のvariableを棚卸しする
  • functionの入力とreturn方法を決める
  • subshellとcommand substitutionの境界を確認する

権限errorをsecurity設定の弱体化で回避しません。担当者と必要なroleを確認してから再試行します。

scalar・command結果・arrayをlocal化する

基本のlocal variable

format_name() {
  local first=$1 last=$2
  printf '%s %s\n' "$first" "$last"
}

assignment右辺をquoteし、function外の同名variableを保護します。

local宣言とcommand結果を分離

load_value() {
  local value
  value=$(some_command) || return
  printf '%s\n' "$value"
}

local value=$(…)ではcommand substitutionのstatusが隠れる実装差を避けます。

local arrayを使う

collect() {
  local -a items=("$@")
  printf '%s\n' "${items[@]}"
}

arrayに保存する場合もquoted引数で要素境界を保ちます。

readonly localを作る

show_root() {
  [ "$#" -ge 1 ] || return 2
  local -r root=$1
  printf '%s\n' "$root"
}

function内で再代入しない入力を固定します。

呼び出し後のglobalを確認

value=global
f() { local value=local; printf 'inside=%s\n' "$value"; }
f
printf 'outside=%s\n' "$value"

sampleでscopeを目視し、globalが保持されることを確認します。

dynamic scopeを呼出し先まで確認する

Bashのlocalはfunction内でvariableのvisible instanceを作りますが、lexical scopeではなくdynamic scopeです。呼び出された別functionが同名localを参照する可能性があります。出力はstdout、statusはreturn codeというinterfaceに分けるとtestしやすくなります。

local代入で終了statusを隠さない

  • POSIX shでもlocalが必ず使えると思う
  • local assignmentでcommand failureを隠す
  • dynamic scopeをlexical scopeと誤解する
  • 配列をunquoted展開して要素を壊す
  • stdoutのdataと診断messageを混ぜる

想定外を抑え込むためsecurity機能やcertificate検証を無効化しません。

引数不足とglobal上書きで停止する

secretをlocalにしてもmemory、trace、error logへの露出は防げません。set -x中は値を扱うfunctionの前後でtrace方針を見直します。global環境variableの上書きやexportをfunction内で行う場合は元値を保存し、trapを含む復元経路をtestします。

変更前後のhash、時刻、対象IDを一対一で結びます。戻す対象をwildcardで再探索しません。

function前後の値とstatusを記録する

globalとlocalの同名、nested function、command substitution、array空要素、failure statusをtestします。shellcheck等の補助結果だけに依存せず、対象Bash versionでstdout、stderr、statusを記録します。

成功だけでなく、0件とerror時に後続が止まることも確認します。

Bash固有機能とscope境界を引き継ぐ

対象範囲を確定する段階では「functionの入力とreturn方法を決める」が判断材料になります。また「subshellとcommand substitutionの境界を確認する」を満たさない場合は、技術的に実行できても運用上の準備不足です。

実例のうち「基本のlocal variable」はbaselineを得る用途、「local宣言とcommand結果を分離」は対象をさらに具体化する用途として使い分けます。両方の出力を同じ形式へ無理に整形せず、元の型と件数を保持したまま比較します。期待値は画面の見た目ではなく、対象ID、path、時刻など再照合できる列で定義します。

ローカル変数の動作は、同名のグローバル変数、入れ子の関数、配列、未設定値を含む小さな関数で確認します。関数の呼び出し前後でグローバル値が変わらず、ローカル値が戻り後に参照できないことを確かめてください。local の細かな挙動はシェルごとに異なるため、Bash向け手順を sh で実行せず、先頭行と実行シェルを一致させます。

関数の前後でグローバル値を表示すると、local が同名変数を呼び出し中だけ隠すことを確認できます。ただし関数が global=value のように明示代入すれば外側は変わります。またcommand substitutionやpipelineの実行位置ではsubshellになる場合があり、そこでの代入が親シェルへ戻らない点を、ローカル変数の効果と混同しないでください。

再試行の前に、POSIX shでも local が必ず使えると思っていないか、local value=$(command) がcommandの失敗値を隠していないか確認します。Bashのlocal変数は動的scopeを持つため、呼び出した別関数から見える場合があります。小さな入れ子関数で実際のscopeを確認します。

配列は "${array[@]}" のように引用して要素境界を保ち、診断messageは標準エラーへ分けます。ローカル変数にtokenなどを入れても、traceやexportで外へ出れば保護にはなりません。関数の確認でグローバル変数を書き換えず、変更例では呼び出し前の値を保存して戻せるようにします。

再現記録にはshellとversion、shebang、対象関数、呼び出し前・関数内・戻り後の値と終了値を含めます。配列なら要素数も照合し、command substitutionを使う場合は内側の失敗値を別に保存します。関数内だけの代入なら永続rollbackは不要です。

完了条件は、同名グローバルが関数終了後も元の値で、local変数が外へ漏れず、複数回呼び出して同じ結果になることです。入れ子関数と配列のfixtureも通し、失敗commandが成功として扱われていないことを確認します。

Bash function内のlocal variableの開始記録には「scriptのinterpreterがBashか確認する」を最初に置きます。続けて「globalと同名のvariableを棚卸しする」を確認すると、対象違いと環境違いを作業前に分けられます。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次