• Windows Subsystem for Linuxガイド 第9回 Win32相互運用性 環境変数編

WSLのWin32相互運用性に関連して、WSLとWin32で環境変数を共有する機能がある。環境変数には、ファイルパスを含めることが多いため、この機能には、Win32側パス(NTFSのパス)とWSL側パス(ドライブファイルシステムパス)を相互に変換する機能がある。

参考:前回記事
【ハウツー】Windows Subsystem for Linuxガイド 第8回 Win32相互運用性編
https://news.mynavi.jp/article/20220720-2403485/

また、Win32相互運用性では、WSL内でcmd.exeやPowerShellといったWin32側のシェルを起動できることから、Win32からWSLへ移行するときの共有だけでなく、WSLからWin32環境に移行するときの共有も可能だ。この移行時にどの環境変数を共有すべきかを制御することもできる。

注意するのは、Win32相互運用性で共有される環境変数は、bashなどが起動する前の段階で共有が開始されるため、.bashrcなどの起動スクリプトで設定している環境変数を上書きできない点だ。このような場合、後述するパスの自動追加を変更する方法のように別の環境変数を共有させ、“.profile”、“.bashrc”などの起動スクリプトで共有した環境変数を使って上書き、あるいは追加するようにしなければならない。

環境変数の共有

WSLとWin32で環境変数を共有するには、「WSLENV」という環境変数に共有したい環境変数の名前を記述する。なお、このWSLENV自体も自動的に共有対象となる。WSLENVに複数の環境変数名を入れるなら、区切りにコロン「:」を使う。

各環境変数には、スラッシュ「/」で区切って、表01のような共有オプションを記述できる。具体的には、


set WSLENV=Env1:Env2/u:Env3/wp:Env4/l

のような指定を行う。

  • ■表01

オプション「w」と「u」は、共有をどの環境と行うのかを示す。「w」が指定された環境変数は、WSL側からWin32環境を起動したときに共有が行われ(Windowsのwだと考えられる)、「u」が指定された環境変数は、Win32からWSLが起動されたときに共有が行われる(同Unixのu)。どちらも指定されないときには、どの場合でも共有が行われる。また、「w」と 「u」を同時に指定することもできるが動作としては、「w」、「u」を指定しない場合と同じである。

Win32側であるcmd.exe(コマンドプロンプト)でVAR1、VAR2、VAR3、VAR4という4つの環境変数が定義されているとき、cmd.exeのsetコマンドを使いWSLENVに


WSLENV=VAR1:VAR2/u:VAR3/w

と指定しておく。VAR1には、何も指定がないため、どの場合にも共有が行われる。VAR2はWin32からWSLの場合に共有、VAR3は、WSLからWin32が起動した場合に共有される。また、VAR4は、Win32環境では定義されているが、WSLENVには記述がない。ここでwsl.exeを使ってWSLを起動したとき、共有されるのは、VAR1とVAR2のみになり、VAR3とVAR4は、WSL側とは共有されない(写真01)。

  • 写真01: WSLENVを使って共有したい環境変数を指定する。Win32からWSLを起動したとき、uオプションかオプション指定のない環境変数が共有対象になる

WSLからcmd.exeを起動するとき

bashでは、exportコマンドを使って環境変数を定義する。環境変数は「公開された」シェル変数であり、使い方はシェル変数とほとんど同じである。定義するときには、変数名をそのまま使い、参照するときには、変数名の前に“$”を付けるか、printenvコマンドを使う(写真02)。また、bashでは、環境変数名は大文字と小文字が区別される点に注意が必要だ。つまり“PATH”と“Path”は異なる変数として扱われる。printenvコマンドで、引数には環境変数名を指定するとき“$”をつけないように注意する。$をつけると、その名前の変数の値がprintenvの引数として与えられてしまう。

  • 写真02: bashでは環境変数は、exportコマンドで定義する。参照には、$を先頭につけた変数名か、printenvコマンドを使う

同様にWSL側でVAR1~VAR4を定義し、WSLENVを同じ値にする。bashからcmd.exeを起動すると、共有される環境変数は、VAR1とVAR3のみになる(写真03)。

  • 写真03: 写真01と同じWSLENVを使ってWSLからWin32環境(cmd.exe)を起動する。このとき、オプションの指定なし(VAR1)かwオプション(VAR3)が共有対象になる

注意が必要なのは、WSLもWindowsのプロセスであるため、WSLからWin32環境を起動した場合に、デフォルトのWin32環境が引き継がれるという点だ。Win32で前述のようにVAR1~VAR4を定義して、WSLを起動し、さらにcmd.exeを起動した場合、最初のVAR1~VAR4が戻り、それに対して環境変数の共有が行われるため、「u」が指定されたVAR2や共有対象にしていなかったVAR4が復活したように見える点だ(図01)。しかし、これは、共有された環境変数ではなく、残っていた環境変数がcmd.exeの起動で見えるようになっただけにすぎない。

  • 図01: 孫プロセスを起動する場合、最初のプロセス(親の親プロセス)の環境変数が継承され、その上でWSLの環境変数の共有が行われる。このため、最初の環境変数が復活するので注意が必要だ

ユーザーが既定値として設定していた環境変数は、どの環境にも使われるため、WSLENVで「u」を付けて指定しても、WSL側で設定した値が共有されないだけで、起動するWin32環境では環境変数としては残ってしまい、消すことはできない。

パスの変換

WSLENVのオプション「p」は環境変数に単一のパスが含まれていることを示し、「l」は複数の環境変数が含まれていることを示す。pまたはlが指定された場合、環境変数の中身は、環境に合わせて自動的にパスが変換される(表02)。なお、オプションpとlは排他的でどちらか1つしか指定できない。ただし、前述の「w」、「u」オプションとは自由に組み合わせることができる。

  • ■表02

環境変数に複数のパスが含まれる場合、Win32側では、パスはセミコロンで区切って指定し、WSL側ではセミコロンで区切る。コロンは、Win32側ではドライブ文字を区切る記号文字であるためパスの一部となるからだ。しかし、WSL側では、複数のパスの区切りにコロンを使うのが標準的である。この区切り文字は、環境変数の共有が行われるときに自動的に置き換えられるので、特に考える必要はない。なお、この環境変数内のパスの区切り文字とWSLENVの環境変数名の区切り文字(コロン)を混同しないように注意が必要だ。

具体的にためしてみる。cmd.exe上で、W3PenvとWSLPenv、W3PLenvの3つの環境変数を定義する。W3PenvとWSLPenvは単体のパスで、NTFS上のパスとwsl$ファイルシステム上のパスだ。W3PLenvは2つのパスをセミコロンで区切ったものだ(写真04)。WSLENV環境変数は、


WSLENV=W3Penv/p:W3PLenv/l:WSLPenv/p

と設定したあと、WSLを起動する。すると、環境変数W3PenvとW3PLenvは、WSL側のパスに変換される。

  • 写真04: 環境変数がパスを含む場合、単独のパスの場合にはp、セミコロンで区切った複数パスの場合にはlオプションを指定すると、共有時に適切な形式に変換される

Win32相互運用性の設定

/etc/wsl.confには、Win32相互運用性の設定として“[interop]”セクションがある。ここでは、2つの設定が可能だ。1つは、Win32相互運用性によるWin32コマンドの実行をWSL側で有効にするかどうかを示す“enabled”だ。もう1つは、このときにWin32側Path変数を、自動的にWSL側のPATH変数に追加するかどうかを示す“appendWindowsPath”である。どちらも、真理値として“true”(有効)または“false”(無効)のどちらかを指定する。

既定値はどちらもtrueであり、Win32相互運用性は有効で、Win32側パスを自動的にWSL側のPATH環境変数に追加する。このため、wsl.confに“[interop]”セクションがない、もしくはwsl.conf自体が存在しない場合、既定値が指定されたのと同じ動作になる。

パスの自動追加は、WSL側のPATH変数を肥大させ、WSL側でのPATH環境変数の扱いを面倒にしてしまう可能性がある(写真05)。実際、Linuxのbashなどでは、PATH環境変数は最低限の状態になっていて、変数値もさほど長くない。しかし、Win32側では、アプリケーションがインストール時に自動的にパスの追加を行い、しかもそのパスが長いために、かなり長い値がPathに設定されている。長期間使ったWindowsでは、パスが適切に管理されておらず、不要のコマンドの起動パスが残っている場合がある。

  • 写真05: 長い間利用したWindowsでは、パス環境変数が大きくなっていることがある。これをこのままWSL側のPATHに自動追加させると大量のパスが定義されてしまう。パスの総数は、コロンの数を数えればよい。この場合65個のパス設定が行われていた

最短のパスのみ共有させる

ここでは、Windowsの標準コマンドのパスだけを残した専用の環境変数を作り、これをWSLENVで共有し、bashの起動スクリプトなどでPATH環境変数に設定する方法を解説する。この設定を行うことで、WSL側のPATH環境変数が短くなり扱いが容易になる。

まずは、準備として、Windowsの環境変数の既定値を修正する。定義する環境変数は2つ。1つはWSLENVであり、もう1つはパスを記録するためのもの。ここでは“MyWin32Path”という名前にしておく。cmd.exeのコマンドラインからsetxコマンドを使い、この2つの環境変数をシステム起動時の初期値として設定できる(リスト01)。setxコマンドはシステム環境変数にも設定が可能だが、この場合には、管理者権限で行う必要がある。ただし、通常はユーザー環境変数の設定で十分である。また、setxは、setコマンドと違い“=”を使わないことに注意されたい。

・リスト01


setx MyWin32Path C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Windows\System32\OpenSSH\;C:\WINDOWS\System32\WindowsPowerShell\v1.0\
setx WSLENV MyWin32Path/l

環境変数の初期値の設定は、「設定アプリ ⇒ システム ⇒ 詳細情報 ⇒ システムの詳細設定」(Windows 10)、または「設定アプリ ⇒ システム ⇒ バージョン情報 ⇒ システムの詳細設定」(Windows 11)で、システムのプロパティダイアログを開き、そこにある環境変数ボタンからGUIで行うこともできる。

次にwsl.confを編集して“[interop]”セクションに“appendWindowsPath=false”を指定する。wsl.confの編集に関しては、第6回の記事を参照していただきたい。

次に、WSL側で、ユーザーのホームディレクトリにある“.profile”を編集して、末尾に以下の行を追加する。


export PATH=$PATH:$MyWin32Path

これは、PATH環境変数の現在の値に共有したMyWin32Path環境変数の値を追加するためのものだ。

この状態で、WSLから抜け、“wsl.exe --terminate”コマンドを使って、WSLを完全に終了させる。その後、すべてのWindows Terminalウィンドウを終了させておく。Windows Terminal内で起動するシェルは、Windows Terminalが親プロセスとなる。しかし、Windows Terminalは、setxを実行する前の起動時の環境変数を継承したままの状態だからだ。

“wsl.exe -l -v”コマンドで、WSL環境が停止しているのを確認したら、WSLを起動し、printenvコマンドを使ってPATH環境変数を確認する。設定前に比較して、PATH環境変数がコンパクトになっているはずだ(写真06)。なお、この設定を行うとユーザーがインストールしたコマンドをWSL側から起動するときにフルパスを指定する必要がある。どうしてもWSL内でWindows版を起動したいプログラムがあるときには、手順の最初で指定した環境変数(MyWin32Path)にパスを追加しておく。

  • 写真06: 設定を行い最低限のパスのみにすると、パスの数は16個にまで減る。ここまですればWSL側でPATH環境変数が扱いやすくなる

しかし、現在では、多くコマンドラインプログラムがWindows版、Linux版で用意されているため、環境ごとにネイティブのコマンドを使ったほうがパス変換や文字コードエンコーディングなどの問題を回避しやすい。なのでLinuxでもリリースされているコマンドのWindows版をWSLから使う必要はほとんどない。

環境変数の共有を使うことで、WSLとWin32側で情報を共有することが可能になる。この機能を使うことで、複数のWSLディストリビューションに関して、共通する設定用の環境変数(オプションの既定値などを環境変数で指定できるコマンドは多い)を、Win32側でまとめて行うことができ、新規にWSLディストリビューションをインストールしても.bashrcなどの環境設定をしないで済ませられる。