Edited at

Visual Studio Codeの統合シェルをMSYS2のBashにしたら.bash_profileが読み込まれなかった

More than 1 year has passed since last update.

Windows7前提。

ガッツリ勘違いしてたのでガッツリ修正済み。


問題

Visual Studio Codeの統合シェルをMSYS2のbashにしたところ、.bashrcの読み込み時にエラーが発生した。

anyenvコマンドが見つからないとか言われたのだが、色々試してみると、.bashrcは読み込まれているが、.bash_profileが読み込まれていないことがわかった。

anyenvのイニシャライズは.bash_profileで行っていた。

.bash_profileに以下の部分がある。

if [ -d "$HOME/.anyenv/bin" ]; then

PATH="$HOME/.anyenv/bin:$PATH"
fi
eval "$(anyenv init -)"

.bashrc内でanyenvコマンドを実行しており、そこでエラーが発生していたのだ。


シェルのモード

このあたりについては、言及した記事がQiitaにも多数ある。

ここで、Visual Studio Codeのsetting.jsonの記述を確認してみる。

"terminal.integrated.shell.windows": "c:\\msys64\\usr\\bin\\bash.exe",

-lオプションの指定が無く、かつ-cオプションもないので、ログインシェルではないインタラクティブシェルであるということになる。

この場合、.bash_profileが読み込まれず、.bashrcが読み込まれるのは、正常な動作だ。


全部.bashrcに書けばよいのかというと

そういうわけにもいかない。

特にexport PATHを.bashrcに書くと、bashを実行するたびに環境変数PATHがじゃんじゃん長くなっていく。Windowsでそれはやばすぎる。

よって、このへんの処理は.bash_profileに書く必要がある。

しかし、統合シェルでanyenvが使えないのも困るし、いちいちexec $SHELL -lで手動ログインするのも面倒くさい。


setting.jsonをログインするように書けばいいじゃん

こんな感じにする。

  "terminal.integrated.shell.windows": "c:\\msys64\\usr\\bin\\bash.exe",

"terminal.integrated.shellArgs.windows": [ "-l" ],

すると、ログインシェルとして実行されるので、.bash_profileも読み込まれる。一件落着

……と思いきや


ワークスペースを作業ディレクトリとして開いてくれない

VSCodeでワークスペースを開いている場合、統合シェルの作業ディレクトリは、通常、ワークスペースのルートディレクトリになる。


In Visual Studio Code, you can open an integrated terminal, initially starting at the root of your workspace.

Integrated Terminal in Visual Studio Code


例えば、統合シェルをGit Bashにすると、この仕様通りの挙動になる。

"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",

"terminal.integrated.shellArgs.windows": [ "-l" ],

ところが、MSYS2 Bashの場合だと、全部環境ルート、すなわち/c/msys64/home/<ユーザー名>になってしまう。

ちなみに、terminal.integrated.cwdという設定項目があるのだが、これは環境ルートやワークスペースルートなどの、自動的に選択される作業ディレクトリ以外を開きたい場合に設定するもの。

// 端末を起動する明示的な開始パスです。これはシェル プロセスの現在の作業ディレクトリ (cwd) として使用されます。特にルート ディレクトリが cwd に適していない場合に、ワークスペースの設定で役立ちます。

"terminal.integrated.cwd": "",

今回はワークスペースルートを開いてほしいので必要ないのだが、いちおうユーザー設定とワークスペース設定の両方で設定してみた。そしてやっぱり無駄だった。

そして、元通り-lオプションを付けずに起動した場合、きちんとワークスペースルートを開いてくれる。

要するに、MSYS2 Bashのログイン時の動作によって、環境ルートに飛ばされているわけだ。


改めて、Bashログイン時の挙動を確認


bash が対話的なログインシェルとして起動されるか、 --login オプション付きの非対話的シェルとして起動されると、/etc/profile ファイルが存在すれば、 bash はまずここからコマンドを読み込んで実行します。 このファイルを読んだ後、 bash~/.bash_profile, ~/.bash_login, ~/.profile をこの 順番で探します。 bash は、この中で最初に見つかり、かつ読み込みが可能であるファイルから コマンドを読み込んで実行します。

Man page of BASH


で、これを参考にMSYS2の/etc/profileを確認してみたのだが、作業ディレクトリを変更するような処理は行われていなかった。.bash_profileおよび.bashrcも念のため改めて確認したが、やはりcdなど書いていない。


Terminal Here 拡張

詰まったので、根本的な解決にはなっていないが、拡張機能で手当てすることにした。


Exposes the command terminalHere.create that creates a terminal at the current file's directory.


読んで字のごとく、ワークスペースルートではなくエディタで開いているファイルの親ディレクトリで統合シェルを開くための拡張である。なので、これでワークスペースルートを開こうとすると、ルート直下のファイルを開いてから実行せねばならず、いささかめんどっちい。

が、今回の用途では好都合なことに、この拡張はユーザー設定の規定の動作で統合シェルを起動した後、cdコマンドで作業ディレクトリを切り替える。すなわち、これ以上.bash_profile絡みの問題に振り回されることがない。


暫定対応:ログインしない

その後また改めてbashの起動オプションを確認してみたところ、-lを省略し、--init-fileで~/.bash_profileを読み込めばなんとかなるような気がした。


setting.json

    "terminal.integrated.shellArgs.windows": [

"--init-file",
"~/.bash_profile"
],

すると、いちおうワークスペースルートで起動した!

~/.bash_profileで~/.bashrcをsourceしているので、自分で書いた設定は一通り読み込まれたようだ。

ただ、環境変数PATHを確認してみると/usr/bin/mingw64/binがないようだったので、~/.bash_profileでPATHgrepして自動的に追加するようにしてみた。


~/.bash_profile

A_PATH=(/usr/bin /mingw64/bin)

for V_PATH in ${A_PATH[@]}; do
if
! echo "$PATH" | grep -Eq ":?$V_PATH:?"; then
export PATH="$V_PATH:$PATH"
fi
done


これで、とりあえずパスが通ってなくて困るということはない。

相変わらず「ログインすると環境ルートに飛ばされる」という根本的な問題は何も解決していないのだが、当面これで運用してみることにした。



オマケ:terminal.integrated.shellArgs.windowsに気付いてなかった頃の試行錯誤

コメントいただいたubnt_intrepidさんありがとうございます。

まあ対策としては的はずれなのだが、調査した内容は何かの役に立つからも知れないので残しておく。


対策

.bashrcは読み込まれるのだから、.bashrcでログイン処理をすればよい。

Visual Studio Code以外で「非ログインシェルかつインタラクティブシェル」としての実行ができなくなるとまずいので、どうにかしてVisual Studio Codeから実行した場合のみログインする処理を書きたい。


Visual Studio Codeから実行したかどうか確認する


ターミナルエミュレータ名($TERM変数)を確認する

最初は、ターミナルエミュレータで判定すればいいのではないかと考えた。

例えばConEmuだったらログインシェルとして実行してくれるからなにもしなくていい。Visual Studio Codeの場合だけ……というところで、Visual Studio Codeで使ってるターミナルエミュレータってなんやねんとなった。

作法的には、ターミナルエミュレータがシェルを実行する際、環境変数$TERMにターミナルエミュレータ名を格納して渡しているはずである。

そこで、統合シェルで$TERM変数を出力してみる。

$ echo $TERM

cygwin
$ tset - # 同じ意味
cygwin

ファッ!?

ちなみに、ConEmuだと同じくcygwin、minttyではxtermとなった。

あとで気付いたが、minttyはOptionsでこれを変更できる。

mintty>Options>Terminal

Visual Studio CodeにはCygwinが組み込まれており、これでbashを実行しているということなのだろう。これでは判定ができない。


bashから親プロセスを確認する

ターミナルエミュレータがなんだろうが、Visual Studio Codeがbashを呼び出していることは間違いないのだから、プロセスレベルでは親子関係が成立しているはずだ。

そこでまず、bash上で親プロセスのプロセスID(PPID)を確認してみる。

$ echo $$ # このbash自身のプロセスIDを出力

12104
$ ps -f # 実行中プロセスの親プロセスID(PPID)を出力
UID PID PPID TTY STIME COMMAND
MM 12104 1 cons0 18:31:10 /usr/bin/bash
MM 11112 12104 cons0 18:37:47 /usr/bin/ps

いきなり詰んだ。

このbashすなわちPID:12104の親プロセスIDは1となっている。これは親プロセスが存在しない(または終了している)場合の表示であるっぽい。

bashは親の顔を忘れているのだ。親不孝者だ。


ProcessExplorerでプロセスの親子関係を確認

bashから直接親プロセスを調べることができなかったので、まずはGUIアプリで確認してみる。

結論を言うと、こういったプロセスツリーになっていた。

Code.exe

┗Code.exe
┗Code.exe
┗winpty-agent.exe
┗bash.exe
┗bash.exe

winpty-agent.exeというプロセスが、統合シェルで実行したbashの親プロセスであることがわかる。


bashから、親プロセスがwinpty-agent.exeであるか確認する

統合シェルで実行したbashの世界には、親プロセスは存在しない。bashに訊いても親の顔は知らんという答えしか返ってこない。

なので、それを知ってる人に訊く必要がある。コマンドプロンプトだ。

wmicを利用して、cmd.exe経由で、そのbashのPPIDを出力してもらう。

$ cmd.exe /c "wmic process where ^(processid^=12104^) get parentprocessid /value" | grep -o '[0-9]\+'

3896

そして、この親プロセスがwinpty-agent.exeであるかを確認する。

$ cmd.exe /c tasklist | grep 3896

winpty-agent.exe 3896 Console 1 4,764 K

間違いなくwinpty-agent.exeであることがわかった。


.bashrcで判定処理を入れる

あとは、.bashrcで判定すればよい。

# Visual Studio Codeの統合シェルはログインシェルではないため.bash_profileが読み込まれない。

# そのため、bashの親プロセスを判定し、統合シェルの場合のみログインを実施する。
if shopt login_shell | grep -q off; then # (インタラクティブシェルであり)ログインシェルではない場合
ParentPID=$(cmd.exe /c "wmic process where ^(processid^=$$^) get parentprocessid /value" | grep -o '[0-9]\+') # このbashのPPIDを取得
if cmd.exe /c tasklist | grep $ParentPID | grep -q 'winpty-agent.exe'; then # 親プロセスがwinpty-agent.exeである場合
echo "detect winpty.exe. exec bash as login-shell"
exec -l $SHELL
fi
else
echo "login-shell: USER[$USER]"
fi

bashがインタラクティブシェルとして実行された場合、.bashrcが読み込まれる。

ログインシェルではない場合、親プロセスがwinpty-agent.exeであるか判定し、そうであればログインシェルとして再度実行する。

ログインシェルである場合は、メッセージを表示する。Visual Studio Code統合シェルで実行した場合、.bashrcが再度読み込まれて、メッセージが表示されることになる。

これで、統合シェルでも自動でログインすることができた。