Windowsでzshを使えるようにする

WindowsでもPOSIX互換シェルは使いたくて、導入・設定が簡単なGit Bashをよく使っています。zshも使いたくなってきたので手順をまとめます。

Git BashとMSYS2

Git BashはGit for Windowsを入れると、おまけでついてくるようになっていて、デフォルトでWindows向けの統合設定がされているからすぐに使えます。Git for Windowsをインストールするだけで既にGit Bashが使えるようになっています。圧倒的に簡単で、Gitという強力なツールとともに保守してもらえる安心感もあって、安定した環境が手に入ります。

ミニマルなMSYS2環境が同梱されていて、その上で動作する bashWindowsユーザにも扱いやすい形で設定されている、というようなものでもあります。環境変数 USERPROFILE で表されるユーザディレクトリがホームディレクトリになるようマッピングされ、環境変数 PATH や各コマンドに渡すパスの解釈をいい感じにWindows形式とUNIX形式で変換してくれたりする機能はMSYS2の恩恵です。 bash の設定ファイル(自動で呼ばれるシェルスクリプト)としておなじみの .bash_profile.bashrc なんかもユーザディレクトリ直下にあるので、探しやすく編集も簡単です。

ロケール周りで混乱したくないのと、シンボリックリンク使いたいので、下記のような設定は書いていたりしますね。 LC_環境変数LC_ALL による一括設定でも基本的には大丈夫だと思いますが、一応選んで個別に指定してます。多分これらは zsh でも踏襲しておいていいです。

# Locale
export LANGUAGE=ja_JP:ja
export LANG=ja_JP.utf8
export LC_CTYPE="ja_JP.utf8"
export LC_NUMERIC="ja_JP.utf8"
export LC_TIME="ja_JP.utf8"
export LC_COLLATE="ja_JP.utf8"
export LC_MONETARY="ja_JP.utf8"
export LC_MESSAGES="ja_JP.utf8"
#export LC_ALL="ja_JP.utf8"

# MSYS2環境で ln -s によりシンボリックリンクを作成できるようにする(デフォルトではコピー)
export MSYS=winsymlinks:nativestrict

ところで、Windows標準のシェルといえばコマンドプロンプトcmd )とPowerShell 5.1( powershell )ですが、独自の文法やコマンド体系のため、UNIX環境の知識がそのまま使えないことがほとんどです。最新のPowerShellpwsh )を別途導入すれば、クロスプラットフォーム対応されてはいますが、世はPOSIX互換シェルの知識を中心に動いてきたので、結局POSIX互換シェルとの使い分けにせざるを得ないというのが実情でしょう。その最も簡単な手段がGit Bashだよ、ということでした。

zshを入れたい

私がこれまでGit Bashを使い続けてこられたように、安定した環境でポータブルな知識で開発したい需要には十分に応えてくれています。

ただ、シェルのヘビーユーザとなってくると、 bash 仕様の古さも気になってくるでしょう。私も設定面で bash のことを気にしたりと細かい場面で bash の古さを意識させられることがあります。 bash の良さはUNIX環境だったらほぼ間違いなく利用できるということで、シェルスクリプトのシバンに指定される代表格ではあります。

ただ、POSIX準拠シェルで使い勝手の良さを考えると zsh が圧倒的に有名です。補完まわりの設計が整理されて強力になっているのが特徴で、 bash の上位互換的な位置づけで利用されるものです(多少の互換性のなさはある)。macOSの標準シェルが zsh に置き換わってから数年経ちました。それぐらいの実績があります。

最近はAIエージェントを活用した開発が急速に進化したので、私も本腰を入れてCLI環境を整備するようになり、環境のポータビリティと利便性をよく考えるようになりました。Windowszsh を使えるようにするのはちょっと面倒な印象はあったのですが、それなりにシンプルな手順で導入・管理できそうだったのでトライしてみました。

そこそこ需要ある気もするのでまとめておこうかな、というのが本記事の目的です。

ちなみにPOSIX互換を気にしなくていいときは、fishやNushellといった新興勢力が最近は人気のようです。使い分けたいですね。

手順書

解説を省いた手順書を先に示します。このあと、理解のためにも丁寧目な説明をしていきますが、やらなきゃいけないことは少ないです。

  • scoop install msys2
  • MSYS2で pacman -Syu
  • MSYS2再起動して pacman -S zsh
  • nano /etc/nsswitch.conf で設定ファイルを編集して db_home: の値を windows に変更
  • ユーザ環境変数 MSYS2_PATH_TYPEinherit を指定
  • Windows Terminalにプロファイル追加
  • .zshrc で一部エスケープシーケンスを処理するよう bindkey 設定を追加

環境更新したければ改めて pacman -Syu を実行。これはzshの中からでよい。

MSYS2のインストール

scoop.sh

Windowsユーザはとりあえず scoop を使えるようにしてください。これがあるだけでクリーンな環境構築のハードルが劇的に下がります。

wizaman-tech.hatenablog.com

最近、 scoop 用の独自バケットを用意する記事も書いていましたが、これも自分のWindows環境のポータビリティを改善する取り組みです。

さて、Git Bash同様に zsh もMSYS2環境の上で動かすことになります。Git Bashと同じMSYS2環境を流用すればいいじゃないか、とはじめに考えるところですが、ちょっと内容が削られていてカスタム性を損なっています。具体的には pacman というパッケージマネージャが付属しておらず、MSYS2環境そのものの更新なんかを好き勝手にできないのです。 zsh も本来は pacman で入れるものです。Git Bashが利用するMSYS2環境に無理矢理 zsh 動作に必要な各種ファイルを配置してしまう手もありますが、安定性に懸念があるのと、やり方がまったくポータブルではないですね。

というわけで、 scoopmsys2 を別途インストールしておいて、これを zsh の土台としましょう。

scoop install msys2

scoopでインストールしたコマンド類は ~/scoop/shimsエイリアスが配置されるので、 msys2 コマンドはここに存在を確認できるようになります。

ショートカットもいくつか用意してくれていて、下記のものがありました。

  • Clang64
  • ClangArm64
  • MinGW32
  • MinGW64
  • MSYS2
  • UCRT64

なんだかよくわからないと思いますが、 msys2 をどのCランタイム環境をメインにして動かすか、といったオプションの違いであって、 msys2 内で採用されるC/C++ツールチェインに差分があると思って良さそう。まあ zsh をインストールしたいだけの文脈では忘れていいので、ショートカットは MSYS2 を選んでおけばいいです。必要になったら、そういえばここに差分があったな、と気付ければ十分でしょう。

zshのインストール

MSYS2ショートカットからMSYS2環境のターミナルが起動し、初期状態ではシェルに bash が使われます。

MSYS2初回起動時にはSkeleton Filesの生成といった初回セットアップ処理がなんか色々走ります。 .bashrc なんかのテンプレを配置してくれる感じです。

また、MSYS2はデフォルトではWindows環境と分離されるように管理されていて、 scoop でインストールした場合、 %USERPROFILE%\scoop\persist\msys2\home\%USERNAME% がホームディレクトリになるよう設定されていると思います。ここらへんの統一的な管理が scoop のよいところです。ホームディレクトリは統一したいですが、それは後ほど。

この状態で、パッケージマネージャ pacman を使用して zsh をインストールします。

まずはその前に pacman を利用してMSYS2環境そのものをアップグレードします。

pacman -Syu

更新後、MSYS2の再起動を求められることがあるので、その場合は応じます。ちなみにこのコマンドは pacman で管理している環境更新そのものなので、 zsh を最新化したいときにも使えます。

今度こそ zsh をインストール。

pacman -S zsh

これで既にMSYS2環境では zsh コマンドを実行すれば zsh が起動するようになっていますね。認識できる .zshrc などの設定ファイルがないと、初回起動扱いになって .zshrc を作成するかどうか問われたりはします。作っておいてもいいですが、次に案内するホームディレクトリの設定を待ちたいです。

ホームディレクトリの統合

MSYS2デフォルト環境では専用のホームディレクトリが用意されていて、Windowsのユーザディレクトリとは隔離されていることを説明しました。

ただ、 .bashrc とか .zshrc とか同一ホームディレクトリ下に配置して統一的に管理したいですし、いわゆる dotfilesリポジトリ管理したい都合でも環境を分けたい理由はないです。

そこで、Windows側のユーザディレクトリをMSYS2側のホームディレクトリに対応させるため、設定ファイル /etc/nsswitch.conf を編集します。このときのエディタは好みで nanovi か使ってください。

# nanoで編集する場合
nano /etc/nsswitch.conf
# Before
db_home: cygwin desc

# After
db_home: windows

これでホームディレクトリの解釈が変わります。MSYS2を再起動して pwd コマンドでも実行すればすぐ確認できると思います。

Windows環境変数の継承設定

MSYS2をそのまま起動すると実はWindows設定の環境変数PATHを引き継ぎません。引き継ぎ挙動はオプションになっていて、環境変数 MSYS2_PATH_TYPEinherit を指定した状態で msys2 を起動するのがわかりやすいです。

システムのデフォルト設定になっていてほしいので、私はWindowsのユーザ環境変数設定として MSYS2_PATH_TYPE を登録しておきました。これでどこから msys2 を呼んでも同じ条件になります。Git Bashと大体同じ状態になったと言えますね。

Windows Terminalへの登録

Windows Terminalから直接 zsh を起動するためのプロファイルを作成します。

Windows Terminalの設定で「新しいプロファイルを追加します」を選んで、プロファイルの「名前」は適当に Zsh とかわかりやすいものを入れておきます。

コマンドライン」は下記のようにしました。

"%USERPROFILE%\scoop\apps\msys2\current\msys2_shell.cmd" -msys2 -shell zsh -defterm -no-start

引数の意味はGeminiによると、以下のようになるらしい。

  • -msys2: MSYS2ランタイム環境(POSIX互換層)を有効化
  • -shell zsh: 起動するシェルをzshに指定
  • -defterm: minttyを介さず、呼び出し元のターミナルで実行
  • -no-start: 別プロセスとして分離せず、現在のセッションを維持

scoopがパスを通すのはshimsの方なんですが、 msys2.cmd の中身が下記のような感じで色々とオプションを勝手に渡しているので、これを参考にするなら current 以下に配置された msys2_shell.cmd を呼ぶべきかなと思って、上記設定はそうしました。

@rem C:\Users\username\scoop\apps\msys2\current\msys2_shell.cmd
@"C:\Users\username\scoop\apps\msys2\current\msys2_shell.cmd" -msys2 -defterm -here -no-start %*

起動コマンドがわかってしまえば、Windows Terminal以外の環境でも使えるので参考にしてください。

「開始ディレクトリ」は %USERPROFILE% が無難そうです。Windows Terminalの再起動時に、前回セッションのディレクトリを復元できなくなりますが、私が影響範囲を正しく理解できていないので、今回は見送ります。復元させたいなら OSC 7 (Operating System Command 7) という仕組みに対応して、シェルからターミナルへカレントディレクトリを通知するよう設定するべきだそうです(Gemini情報で未精査)。これで前回セッションのカレントディレクトリをWindows Terminalが認知・保持することで次回に使えるそうな。

開始時のディレクトリが毎回リセットされると移動が面倒かもしれませんが、 zoxide を使い始めるとそのあたりは気にならなくなってきます。

Visual Studio Codeへの登録(任意)

Git Bashは特別扱いで、デフォルト設定の定義の中に含まれているから、統合ターミナルから利用するのは簡単でした。

管理外のエディタを認識させるには、プロファイル定義を追加する必要がありそうです。

ユーザ設定 settings.json を開いて下記のようにしました。ユーザ名依存なのでパスは実態に合わせてください

    "terminal.integrated.profiles.windows": {
        "Zsh": {
            "path": "C:\\Users\\username\\scoop\\apps\\msys2\\current\\msys2_shell.cmd",
            "args": ["-msys2", "-shell", "zsh", "-defterm", "-no-start"],
            "env": {
                "CHERE_INVOKING": "1"
            }
        }
    },
    "terminal.integrated.defaultProfile.windows": "Zsh",

環境変数 CHERE_INVOKING は、強制的にホームディレクトリに飛ばされる挙動を抑制するものです。

Zedへの登録(任意)

zed.dev

AI機能が統合されたVS Code派生エディタが流行していますが、フルスクラッチでイチから実装したZedが軽量動作するエディタとして注目されています。

まだちゃんと使ってみていないんですが、こちらも settings.json で似たような指定をしたら動きました。

  "terminal": {
    "font_size": 10.0,
    "font_family": "UDEV Gothic NF",
    "env": {
      "CHERE_INVOKING": "1",
    },
    "shell": {
      "with_arguments": {
        "program": "C:\\Users\\username\\scoop\\apps\\msys2\\current\\msys2_shell.cmd",
        "args": ["-msys2", "-shell", "zsh", "-defterm", "-no-start"],
        "title_override": null,
      },
    },
  },

zshの設定

あとは zsh の世界なので好きなように .zprofile.zshrc を管理したり、何らかのプラグインマネージャを導入したり、補完定義をプラグインマネージャーから導入したりしてください。

Delete, Home, EndキーおよびCtrlと左右キーやHome/Endのコンビネーションキーで送信されるエスケープシーケンス処理が中途半端となり、意図通りに動かなかったので下記のように bindkey コマンドで各入力に対する操作を指示するようにはしました。これは .zshrc に書きました。

# Delete: 1文字削除
bindkey "^[[3~" delete-char

# Ctrl+←, Ctrl+→: 単語単位で移動
bindkey "^[[1;5D" backward-word
bindkey "^[[1;5C" forward-word

# Home, Ctrl+Home: 行頭へ移動
bindkey "^[[H" beginning-of-line
bindkey "^[[1;5H" beginning-of-line

# End, Ctrl+End: 行末へ移動
bindkey "^[[F" end-of-line
bindkey "^[[1;5F" end-of-line

antidote.sh

sheldon.cli.rs

プラグインマネージャーはOh My Zshが最もよく知られていると思うんですが、今ならRust製のAntidoteかsheldonになるんでしょうか。詳しくないので私もまだ検討中。

starship.rs

プロンプトを変えたい人はStarshipがおすすめです。これも scoop install starship での導入がスッキリします(通常のインストールだと Program Files 以下に置かれてスペースが邪魔になることがある)。

github.com

StarshipNerd Font対応フォント利用を前提とした表示をするので、私は UDEV Gothic NF を使っています。 Hackgen と同じ作者によるものです。

とりあえず動かせるようにしたばかりなので、暫く使ってみて気づいたことがあったら追記するかも。