Linuxのシェルオプションを一時的に変更する最も確実な方法は、変更を丸括弧のサブシェルへ閉じ込めることです。Bashにはsetで管理するオプションとshoptで管理するオプションがあり、確認・有効化・無効化の構文が異なります。現在の状態を読まずに最後だけ反対へ戻すと、呼び出し元でもともと有効だった設定を壊すため、適用範囲を先に決めます。
setとshoptの対象を区別する
set -oはerrexit、nounset、pipefail、noclobberなどのシェルオプションを表示します。set -o nameで有効、set +o nameで無効です。短縮形ではset -uとset +uのように、プラスが無効化を表す点に注意します。
set -o
set -o pipefail
set +o pipefail
printf 'short option flags=%s\n' "$-"
shoptはnullglob、globstar、extglobなどBashの追加動作を管理します。shopt -s nameで有効、shopt -u nameで無効、shopt -q nameは出力せず終了ステータスで状態を示します。名前がどちらに属するか不明なら、変更前にset -oとshoptの一覧を対象Bashで確認します。
if shopt -q nullglob; then
printf '%s\n' 'nullglob is enabled'
else
printf '%s\n' 'nullglob is disabled'
fi
shopt -p nullglob globstar
一時変更はサブシェルへ閉じ込める
丸括弧( list )の中はサブシェル環境で実行されます。そこで変更した変数やシェルオプションは、サブシェルが終わると親シェルへ残りません。途中のreturn、失敗、条件分岐で手動復元を通らない問題も避けやすく、一つの処理ブロックだけ挙動を変える第一候補です。
if (
set -o nounset
set -o pipefail
shopt -s nullglob
files=(/srv/reports/*.log)
for file in "${files[@]}"; do
printf 'checking %s\n' "$file"
done
generate_report | validate_report
); then
printf '%s\n' 'temporary-option block completed'
else
status=$?
printf 'temporary-option block failed: status=%s\n' "$status" >&2
fi
nullglobが有効なら、一致しないグロブは文字列のまま残らず消えるため、この配列は0要素になります。期待せず0要素になる場合もあるので、件数や業務要件を別に確認します。pipefailはパイプの途中の失敗を全体へ反映しますが、各段の値が必要なら直後のPIPESTATUSを扱います。いずれもBash固有の構成です。
波括弧のグループは設定を隔離しない
{ list; }は現在のシェルでコマンドをまとめる構文で、サブシェルを作りません。リダイレクトを一括適用する用途には便利ですが、その中のset、shopt、変数代入、cdは後にも残ります。一時変更の境界として丸括弧と波括弧を交換できるとは考えません。
( list ): 別のサブシェル環境。変更は通常、親へ戻らない。{ list; }: 現在のシェル環境。変更は後続処理へ残る。- どちらもグループ全体の終了ステータスは、実行したリストの終了ステータスになる。
サブシェルでは親の変数を更新できないため、結果を残したい場合は標準出力や明示したファイル記述子で受け渡します。大量データをコマンド置換へ入れると末尾改行やメモリの扱いが変わるため、単純な状態隔離だけを目的にするか、データ受け渡しまで必要かを分けて設計します。
関数内のsetオプションはlocal -で復元できる
Bashの関数ではlocal -を実行すると、その後にsetで変更したシェルオプションが関数終了時に元の状態へ復元されます。関数が呼び出し元の変数を更新する必要があり、サブシェルへ分けられない場合に使えます。これはBash固有であり、/bin/sh向けスクリプトでは使いません。
run_checked_pipeline() {
local -
set -o pipefail
set -o nounset
if load_records | verify_records; then
printf '%s\n' 'records verified'
else
local status=$?
printf 'verification failed: status=%s\n' "$status" >&2
return "$status"
fi
}
local -が復元するのは、説明上set組み込みで変更するシェルオプションです。shopt -s nullglobのようなshopt設定まで同じ仕組みで戻るとは仮定しません。shoptも一時変更するなら関数全体をサブシェル化するか、元の状態を明示的に記録してすべての終了経路で復元します。
元の状態を確認してから手動復元する
現在のシェルで変更せざるを得ない場合、単純に最後へset +o pipefailを書く方法は不十分です。もともと有効だった呼び出し元では、終了後に誤って無効化するからです。set +oは現在のsetオプションを再現する一連のコマンド、shopt -pは再入力できる形式の状態を表示します。
set +o
shopt -p
set +o pipefail
shopt -p nullglob
一覧は状態監査に便利ですが、出力文字列を安易に再評価するより、サブシェルかlocal -を優先します。個別のshoptを手動で戻す場合は、if shopt -q nameで元状態を真偽値として保存し、成功、失敗、早期return、シグナルなどすべての終了経路を検討します。外部入力を状態復元コマンドへ混ぜません。
子Bashだけ起動オプションを変える
既存スクリプトを別のBashプロセスとして動かせるなら、Bash起動時の-oと-Oを使えます。-oはset系、-Oはshopt系のオプションです。親の対話シェルを変えず、対象スクリプトのプロセスだけに適用できます。
bash -o pipefail -O nullglob ./check-reports.sh
ただし、スクリプトが想定していないオプションを呼び出し側から追加すると、条件判定やグロブの結果が変わります。対象スクリプトのテスト済みインターフェースとして採用し、Bashバージョン、期待するオプション、終了値を文書化します。単一コマンドの環境変数代入と異なり、pipefail=1 commandのような一般的な変数でシェルオプションを切り替えることはできません。
SHELLOPTSとBASHOPTSの継承に注意する
Bashの読み取り専用変数SHELLOPTSは有効なset系オプション、BASHOPTSは有効なshopt系オプションをコロン区切りで表します。これらが環境へexportされていると、子Bashは起動時にリストのオプションを有効化します。親での一時変更が想定外に子へ伝わる可能性を確認します。
printf 'SHELLOPTS=%s\n' "$SHELLOPTS"
printf 'BASHOPTS=%s\n' "$BASHOPTS"
export -p | grep -E ' (SHELLOPTS|BASHOPTS)='
読み取り専用変数へ直接代入して状態を変えるのではなく、setまたはshoptを使います。CI、SSH、ラッパースクリプト、テストランナーが環境を引き継ぐ場合は、実行ログへ秘密値を含む環境全体を出さず、必要な二つの変数だけ確認します。子プロセスに必要な設定は、起動コマンドかスクリプト自身で明示する方が再現しやすくなります。
よく使うオプションの副作用を理解する
pipefail: パイプ途中の非0を全体へ反映する。途中終了が正常な構成では判定が変わり得る。nounsetまたは-u: 未設定パラメーターの展開をエラーにする。空文字とは別。errexitまたは-e: 非0時の終了に多数の文脈上の例外があり、明示的な判定の代替ではない。noglobまたは-f: ファイル名展開を止める。引用不足の修正にはならない。noclobberまたは-C: 一部のリダイレクトによる既存ファイル上書きを抑えるが、>|やアプリ独自書き込みまで防がない。xtraceまたは-x: 展開後のコマンドを標準エラーへ出し、トークンや個人情報を漏らす可能性がある。nullglob: 一致しないパターンを削除するため、空配列を安全に扱う一方、必須入力の欠落を見逃さない検査が必要。globstar:**の再帰展開を有効にし、予想以上に広い対象や大きな処理量になり得る。
特にset -eを「すべてのエラーで必ず停止」、set -uを「変数の型検査」、set -xを「安全なデバッグログ」と説明するのは不正確です。オプションはシェルの解釈規則を変えるため、既存コードへ追加する前に、条件、関数、コマンド置換、パイプライン、グロブ不一致を含むテストを行います。
xtraceは秘密を除いた範囲だけで使う
set -xは展開後の引数を出力するため、APIトークン、Cookie、パスワード、個人データを含む処理で有効にしません。サブシェルで局所化しても、出力済みの秘密は取り消せません。安全と確認できた関数だけを対象にし、共有ログの保存先と閲覧権限を確認します。
(
PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x
run_nonsecret_syntax_check
)
この例は秘密値を扱わない確認関数であることが前提です。PS4自体へコマンド置換や秘密値を含めず、出力を公開チケットへ貼る前に内容を確認します。機密処理では、xtraceではなく、固定されたイベント名とステータスだけを記録する専用ログを使います。
POSIX shとの互換性を確認する
丸括弧によるサブシェルと一部のset短縮オプションは広く使えますが、pipefail、shopt、local -、配列、[[ ]]はPOSIX shの機能として仮定できません。スクリプトが#!/bin/shなら対象実装の機能だけを使い、Bash機能が必要ならBashを明示して実行します。
シェル名だけで判断せず、配布先の実際の/bin/sh、Bashバージョン、起動オプションでテストします。未知のオプションを設定しようとしたときの失敗も検知し、設定できたつもりで処理を続けません。対話シェルの便利設定を本番スクリプトが暗黙に継承する設計も避けます。
確認チェックリスト
- 変更対象がset系かshopt系かを確認した。
- 現在の設定を
set -o、shopt -p、$-で確認した。 - 最小範囲なら丸括弧のサブシェルへ閉じ込めた。
- 関数でset系だけを変える場合はBashの
local -を検討した。 - 手動で反対へ切り替えるだけでは元状態を復元できない点を確認した。
- 子Bashへの
SHELLOPTSとBASHOPTSの継承を確認した。 errexit、pipefail、グロブ、xtraceの副作用を個別にテストした。- スクリプトの対象がBashかPOSIX shかをシバンと実行方法で一致させた。
一時的なオプション変更では、元に戻す命令より、変更が外へ出ない実行境界を作る方が確実です。Bashならまずサブシェル、呼び出し元の状態を更新する関数でset系だけを変えるならlocal -を選びます。setとshoptを区別し、現在値、子への継承、失敗時の終了値、デバッグ情報の機密性まで確認すると、他の処理を壊さず必要な挙動だけを適用できます。

コメント