> [!abstract] 概要(abstract の日本語訳)
> 通常、ユーザーはログイン時に、セッション中に参照するすべてのアプリケーションについて環境情報を設定することで自分の環境を初期化する。Modules パッケージは、シェルの初期化を単純化し、セッション中にユーザーが自分の環境を容易に変更できるようにする、データベースとスクリプト一式である。
>
> Modules パッケージは UNIX 環境保守の負担を軽減しつつ、アプリケーション環境の変更を単一のまとまりとして動的に操作する仕組みを提供する。UNIX 環境に不慣れなユーザーほど、この単一コマンドインタフェースから最も恩恵を受ける。Modules パッケージは、新規・変更されたアプリケーションに関する情報の文書化と周知についてシステム管理者を支援する。
>
> 本稿は Modules パッケージの設計・実装の背後にある動機と概念を述べる。従来のユーザー環境を変更することの問題点と、Modules パッケージがそれらの問題にどう解決策を提供するかを論じる。ユーザーの視点とシステム管理者の視点の双方を記述する。本稿はまた、Modules パッケージの部分的な実装を読者に提示する。C Shell と Bourne Shell のサンプルスクリプトを説明とともに用いて実装を記述する。最後に、ログインセッションの例により、従来のユーザー環境と Modules パッケージを使う環境とを対比する。
# Modules: Providing a Flexible User Environment
## 論文情報
- タイトル: Modules: Providing a Flexible User Environment
- 著者: [[John L. Furlani]]([[Sun Microsystems]] Research Triangle Park Facility、当時)
- 媒体: USENIX LISA(Large Installation System Administration)V, 1991
- 発表年月: 1991年6月29日
- 謝辞: Ken Manheimer・Don Libes(National Institute of Standards and Technology)、Maureen Chew([[Sun Microsystems]])
## 概要
UNIX のシェル起動ファイル(`.cshrc`・`.profile` 等)による環境初期化は、対象アプリケーションが増えるほど保守が困難になる。Modules パッケージは、アプリケーションごとの環境変更情報を「module ファイル」というファイルへカプセル化し、`module(1)` という単一コマンドの add/remove/switch/display のサブコマンドで環境を動的に操作させることで、この問題を解決する。C Shell と Bourne Shell の双方に対応したシェル非依存の実装が示される。
## 問題設定
- ユーザー視点: 起動ファイルの保守は UNIX に不慣れなユーザーには負担が大きく、セッション中に不要なアプリケーションの環境情報まで読み込まれるため PATH が肥大化し、automounter 経由のサーバがダウンした場合はシェルがハングしうる。異なるバージョンのアプリケーション間切り替えは、環境変数の手動再設定と PATH の付け替えを要する煩雑な作業である。
- 管理者視点: 新規アプリケーションの周知は e-mail や `/etc/motd` に頼っており、必要な環境変数の説明を都度書く必要がある。多くのサイトでログファイルやデータベースで各アプリケーションの癖を記録しているが、これは肥大化しやすく保守コストが高い。
- 設計目標: (1) 単一コマンドインタフェースで習得負荷を下げる、(2) module ファイルの自己文書化で管理者の周知コストを下げる、(3) セッション中の頻繁な環境変更を容易にする、(4) 不要なサーバへの依存を減らす、(5) 異なるリリース間の切り替えを容易にする、(6) シェル非依存にする。
## 提案手法
### 全体構成
Modules パッケージは、サイト共通の初期化スクリプト(`.cshrc`/`.profile` からソースされる)が `MODULESHOME`(master module ファイルとコマンドスクリプトの場所)・`MODULEPATH`(module ファイルを探索するパス)・`_loaded_modules`(読み込み済み module の一覧)を定義するところから始まる。`module(1)` コマンドは、シェルが対応していればエイリアスまたは関数として次のように定義される(Figure 1)。
```sh
## module(1) User Command as Function
module() {
_module_argv="$*"
. $MODULESHOME/.module.sh
unset _module_argv
}
```
module ファイルはシェル非依存の 1 ファイルにアプリケーション 1 つ分の環境情報をまとめたもので、`_set_environ`(環境変数の設定・除去)、`_prepend_*path`/`_append_*path`(PATH・MANPATH・MODULEPATH・LD_LIBRARY_PATH への追加)、`_rm_*path`(パスからの除去)、`_prereq`/`_conflict`(依存・衝突の宣言)という内部関数を呼び出して構成される。`_append_manpath` は例えば次のように実装される(Figure 2、Bourne Shell 版)。
```sh
_append_manpath() {
if [ "$_rm_flag:-X" = "X" ]; then
_rm_manpath $1
else
MANPATH="$MANPATH":"$1"; export MANPATH;
fi
}
```
module の削除(`module rm`)は、追加時と同じ内部関数群を `_rm_flag` を立てて再実行することで実現される。同じ関数が `_rm_flag` の有無で「追加」と「除去」を切り替える対称設計であり、`_rm_manpath` は awk で MANPATH から該当ディレクトリだけを取り除く(Figure 3)。
環境変数は、依存関係にあるパス変数を除去し終えるまで実際には unset されない。たとえば OpenWindows の module ファイル(Figure 4)は `OPENWINHOME` を設定してから、それを参照するパスを `_prepend_newpath`/`_prepend_manpath`/`_prepend_ldpath` で追加する。除去時にこの順序を守るため、`_set_environ`(Figure 5)は除去要求時に変数名を `_unset_list` に積むだけにとどめ、module ファイルの読み込み完了後にまとめて unset する。
```sh
_set_environ() {
if [ "$_rm_flag:-X" = "X" ]; then
_unset_list="$_unset_list $1"
else
eval $1="$2"; export $1;
fi
}
```
`_prereq`/`_conflict`(Figure 6)は、複数指定を OR 条件として扱う(AND を得るには複数回呼び出す)。たとえば AnswerBook は `openwin` または `openwin-v3` のいずれかを前提条件として要求し、OpenWindows 2.0 と 3.0 は互いを衝突として宣言する。
`module(1)` コマンド本体はサイト共通スクリプトが実装し、引数は `_module_argv` 経由で渡される。サブコマンドは Load/Add・Remove/Erase・Switch/Change・Show/Display・Initadd・Initrm・List・Available・Help の 8 系統である。`_add_module` はまず引数の module 名がすでに読み込み済みでないかを確認し(Figure 7)、次に `MODULEPATH` を走査して見つかった module ファイルを source し `_loaded_modules` に追記する(Figure 8)。`_rm_module`(Figure 9)はほぼ対称の手順で、module ファイルを `_rm_flag` 付きで再度 source してから `_unset_list` の変数を unset する。`_list_modules`/`_avail_modules`(Figure 10)は、読み込み済み module の一覧表示と、`MODULEPATH` 上の各ディレクトリを `ls(1)` で列挙する availability 表示を担う。
```sh
_avail_modules() {
for dir in $MODULEPATH; do
echo $dir":"
(cd $dir; ls)
done
}
```
`initadd`/`initrm` サブコマンドは、起動ファイル中の `module(1)` 呼び出し行(専用のコメント行の直後)を書き換えることで、恒常的に読み込む module の集合を編集する。`switch` は本稿執筆時点では未完成の機能として設計のみ示され、切り替え可能な module 同士は環境変数とパス変更の形式が一致していることを前提とする。
### システム module と ユーザー module
`MODULEPATH` により、サイト共通ディレクトリの前後にユーザー独自のディレクトリを追加できる。これにより「システム module(管理者が保守)」と「ユーザー module(個人がサイト共通版から派生、または独自に作成)」を区別しつつ、両方に同じ操作性を与える。
## 新規性
- アプリケーション単位の環境変更を、シェル非依存の単一ファイル(module ファイル)へカプセル化し、`module(1)` という単一コマンドインタフェースで add/remove/switch/display する、という設計そのものが本稿の提案である(後継の Environment Modules・Lmod の直接の起源)。
- 追加と除去を同一の内部関数群の対称操作として実装し、環境変数の除去タイミングを依存パスの除去後に遅延させることで、パス変数が参照する環境変数を安全に巻き戻す設計。
- module ファイル間の `_prereq`/`_conflict` により、依存・非互換なアプリケーション組み合わせを module ファイル自身に記述させ、`module(1)` に検出させる。
## 実験設定
本稿は定量ベンチマークではなく、実装の記述と対比セッション例による定性的な提示である。Figure 11(従来方式)と Figure 12(Modules 方式)は、同一ユーザーが OpenWindows 2.0 からバージョン 3.0 の開発版へ切り替える操作を対比する。
```text
[Figure 11. Conventional Style(抜粋)]
jlf@system% setenv OPENWINHOME /depot/openwin-v3
jlf@system% setenv PATH /depot/lang:/depot/openwin-v3/bin:/depot/openwin-v3/bin/xview:...
jlf@system% setenv LD_LIBRARY_PATH /depot/lang/SC1.0:/depot/openwin-v3/lib:/usr/lib
jlf@system% setenv MANPATH /depot/lang/man:/depot/openwin-v3/man:...
jlf@system% openwin
[Figure 12. Modules Style]
jlf@system% module rm openwin
Removing /site/Modules/openwin
jlf@system% module add openwin-v3
Loading /site/Modules/openwin-v3
jlf@system% openwin
```
Figure 13 はより複雑な例として、`module avail` で利用可能な module を一覧し、`module show lang` で内容を確認してから `module add lang` で PATH・MANPATH・LD_LIBRARY_PATH に `lang` の設定を反映し、`module show answerbook` で前提条件(`openwin` または `openwin-v3`)を確認したうえで `module add answerbook` を実行する流れを示す。
```text
[Figure 13. A More Complex Modules Example(抜粋)]
jlf@system% module avail
jlf@system% module show lang
Prepend PATH: /depot/lang
Prepend MANPATH: /depot/lang/man
Prepend LD_LIBRARY_PATH: /depot/lang/SC1.0
jlf@system% module add lang
jlf@system% module show answerbook
Prerequisites(ORed): openwin openwin-v3
Append PATH: /depot/answerbook
jlf@system% module add answerbook
```
## 実験結果
- 対比セッション(Figure 11 対 12)で示されるとおり、Modules 方式は複数行の `setenv` を `module rm`/`module add` の 2 コマンドに置き換え、PATH 等の変数も必要最小限に短くなる。
- 定量的な性能記述は「module 1 つの読み込みに数秒かかる」という 1 点のみで、これを改善するために内部関数をシェルに常駐させたままにするオプションが言及される(常駐させない場合、呼び出しのたびに関数が再定義されるコストがかかる)。
- 著者は「Modules はまだ新しく開発中であり、少数のユーザーのみが使用を開始しているが、好評である」と述べるにとどまり、定量評価は示されない。
## 考察
- 本稿は環境変数管理の**カプセル化**と**単一コマンド操作**という設計原則を提示するにとどまり、`switch` サブコマンドや if-else 文法拡張、変数単位で書き換えを抑制するオプションなど、当時未実装の機能を Future Work として列挙している。
- シェルへの機能組み込み(現在の shell に対する外部スクリプトの source ではなく、シェル自体に Modules 相当の機能を持たせる)が、性能・簡潔性の両面で望ましいとされているが、実現されていない。
- 検索パスの論理的な構造化(ユーザーが自分でパスの順序を管理する負担の軽減)も将来課題として挙げられている。
## 強み / 弱点・課題
### 強み
- 単一コマンド `module(1)` によるカプセル化という単純な設計原則が、UNIX 初心者から上級者まで扱える柔軟性を両立させている。
- 追加/除去を対称な内部関数として実装し、依存パス除去後に環境変数を unset する順序設計により、環境の巻き戻しを安全にしている。
- `_prereq`/`_conflict` による宣言的な依存・衝突管理は、後の環境モジュールシステム(Environment Modules・Lmod)にも引き継がれる基本機構である。
### 弱点・課題
- 定量的な性能評価・ユーザー評価が示されず、初期導入時点での定性的な報告にとどまる。
- `switch` サブコマンドが未実装であり、リリース間切り替えの主要な謳い文句の一部が設計止まりである。
- 環境変数名にアンダースコア接頭を用いる衝突回避策は、ユーザー変数との衝突を「完全に防ぐ」のではなく「検出して通知する」対症療法にとどまると著者自身が認めている。