副作用の影響を局所化する(シェルスクリプト)

課題

小さい機能を実現するために必要な変更がスクリプト全体へ影響を及ぼしてしまうので気を使う。 例えばカレントを変更したり変数に値を代入したりすることだ。 どのようにすれば影響を局所化することができるのか。


具体例を示す。 カレントが maindir で、その下に subdir が存在する状況とする。

$ pwd
/Users/hogehoge/maindir
$ ls
subdir

この状況で以下のスクリプトを実行してみる。 各部分を通過した時点でのカレントと変数の値を表示するスクリプトである。

myfunc() { 
  cd subdir
  var=fuga

  echo "line 2: pwd = $(pwd)"
  echo "line 2: var = $var"
}

cd '/Users/hogehoge/maindir'
var=foo

echo "line 1: pwd = $(pwd)"
echo "line 1: var = $var"
myfunc
echo "line 3: pwd = $(pwd)"
echo "line 3: var = $var"

実行結果は以下の通り。 注目するべきは関数を実行する前後(line 1 と line 3)でカレントと変数の値が変化していることだ。 関数内での副作用が呼び出し元にも波及し、そのあとのシェルスクリプトの動作に影響する。 これは好ましいことではない。

line 1: pwd = /Users/hogehoge/maindir
line 1: var = foo
line 2: pwd = /Users/hogehoge/maindir/subdir
line 2: var = fuga
line 3: pwd = /Users/hogehoge/maindir/subdir
line 3: var = fuga

解決策

サブシェルを利用する。

サブシェルはもとのシェル実行環境からは分離されたシェル実行環境である。 カレントや変数はシェル実行環境を構成する一部である。 サブシェル内での副作用はもとのシェル環境には影響しない。


「課題」で挙げた例を、今度はサブシェルを利用して実行してみる。 関数定義を囲む「{」と「}」をそれぞれ「(」と「)」に変更している。

myfunc() ( 
  cd subdir
  var=fuga

  echo "line 2: pwd = $(pwd)"
  echo "line 2: var = $var"
)

cd '/Users/hogehoge/maindir'
var=foo

echo "line 1: pwd = $(pwd)"
echo "line 1: var = $var"
myfunc
echo "line 3: pwd = $(pwd)"
echo "line 3: var = $var"

実行結果は以下の通り。 関数を実行する前後でカレントと変数の値が変化していないことがわかる。 つまり、関数内での変更が呼び出し元に影響せず、理想的な形になったということだ。

line 1: pwd = /Users/hogehoge/maindir
line 1: var = foo
line 2: pwd = /Users/hogehoge/maindir/subdir
line 2: var = fuga
line 3: pwd = /Users/hogehoge/maindir
line 3: var = foo


本記事の例として、関数を丸ごとサブシェルで実行することを挙げたが、もちろん関数単位でなくともサブシェルを利用することはできる。 というよりむしろそちらが普通で、関数をサブシェルで定義できることを知らない人の方が多いだろう。

例えば筆者は以下のように利用している。 簡易的なビルド用スクリプトである。 複数のRustプログラムのパッケージがあり、それらすべてをビルドするためにそれぞれカレントを変更してビルドを実行している(prog変数に次々とパッケージ名が反映される)。 いま自分がどこにいるのかを気にしないために(面倒くさいことを考えないために)サブシェルを利用しているわけだ。

...
(
    cd "rust/$prog"
    cargo build --release
)
...


なお、この記事と同様のことを実現するために、POSIX非準拠のコマンドを利用できる場合がある。 例えば変数の場合は local コマンドによって影響範囲をローカルに抑えることができる。 POSIX非準拠でも問題ない場合はそれらを利用することを検討しても良い。


サブシェル自体に関して別途記事を執筆する予定。

所感

サブシェルをうまく利用できるか否かは、シェルスクリスプトの習熟度合を測るひとつの指標かもしれない。 自身の周囲ではこれを利用する人はほとんどいない。 ひとたび挙動を理解できていれば、恐れずに便利に利用することができるようになる。