改用 Mac 已經六年了。現在要再回頭碰 Windows,還真有點不習慣。

這年頭,許多軟體其實都有 Mac 及 Windows 版了:Chrome、Firefox、Slack、Evernote、Dropbox、VSCode……最大的差別,應該是終端機命令列工具。

上古時代,需要靠 CygwinMinGW 方案,才能勉強湊出一點點 Unix 的命令列感覺,但地雷超級多,難以作為嚴肅用途。後來,到了 2015 年,從保哥那邊知道有 Cmder 這個好物 1,微軟又於 2016 年推出 WSL (Windows Subsystem for Linux) 機制,Windows 這邊似乎出現曙光,對 Unix 命令列愛好者展現出久違的吸引力。

為了在 Windows 10 上面復刻我的 Mac 的體驗:iTerm2 + Zsh + Oh My ZSH,我試了幾天,把步驟整理如下。

WSL

請根據保哥的文章〈介紹好用工具:WSL (Windows Subsystem for Linux)〉進行以下步驟:

  1. 安裝 WSL。

  2. 安裝一個 Linux distribution。

  3. 第一次執行 WSL,並設定 Linux 的帳號密碼。

Zsh + Oh My ZSH

在 WSL 中安裝 zsh 及 oh-my-zsh:

% sudo apt-get install zsh

% sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

將 zsh 設為內定 shell:

% chsh -s $(which zsh)

如果你的 zsh theme 含有許多特殊的符號字元,請順便安裝 Powerline 系列字型。

Cmder

Cmder 有「完整版」及「迷你版」兩種安裝方式。有了 WSL 之後,msysgit 可以退場,因此,我們只需安裝 Cmder 迷你版。

用 Chocolatey 套件管理工具安裝比較簡單:

C:\> choco install cmdermini

細節請見 https://chocolatey.org/packages/cmdermini

整合 Cmder 與 WSL

為了讓 Cmder 以 WSL + Zsh 模式啟動,我們需要新增一個 Cmder 的 “Startup / Tasks”。

譬如說,我們可新增一個名叫 {WSL::zsh} 的 task,將 command 寫成:

set "PATH=%ConEmuBaseDirShort%\wsl;%PATH%" & %ConEmuBaseDirShort%\conemu-cyg-64.exe --wsl -C~ -cur_console:p:t:"zsh" -t zsh -l
新增 Cmder 啟動設定

新增 Cmder 啟動設定

存檔完畢,以後只要以 {WSL::zsh} 模式啟動,Cmder 就會自動套用 WSL + zsh 組態。

進一步的設定細節,請參考 https://conemu.github.io/en/BashOnWindows.html 一文。

設定 Cmder 熱鍵

為了復刻出類似 Mac + iTerm2 的使用習慣,我會花一些時間調整 Cmder 熱鍵設定。請參考保哥的文章〈介紹好用工具:Cmder (具有 Linux 溫度的 Windows 命令提示字元工具)〉進行熱鍵設定。

在 Windows 與 WSL 之間進行複製貼上的剪貼簿操作時,常會遇到換行問題。此時我也會順便將 Ctrl-V 組合鍵的 “Paste mode #2” 設定成 “Multi lines” 2

調整 Cmder 剪貼簿的換行處理方式

調整 Cmder 剪貼簿的換行處理方式

檔案系統

我習慣將 Windows 的 D: 作為文件儲存專用區。

預設情況下,WSL 會將 D: 掛載在 /mnt/d,檔案系統則是 DrvFs:

% mount -l
rootfs on / type lxfs (rw,noatime)
none on /dev type tmpfs (rw,noatime,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,noatime,gid=5,mode=620)
none on /run type tmpfs (rw,nosuid,noexec,noatime,mode=755)
none on /run/lock type tmpfs (rw,nosuid,nodev,noexec,noatime)
none on /run/shm type tmpfs (rw,nosuid,nodev,noatime)
none on /run/user type tmpfs (rw,nosuid,nodev,noexec,noatime,mode=755) binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,noatime)
D: on /mnt/d type drvfs (rw,noatime,uid=1000,gid=1000,case=off)
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,case=off)

不過,因為某些複雜的原因 3,我們必須重新設定 /mnt/d 的 mount 參數,才能讓 Linux 的檔案讀寫權限正常運作。

我們先卸載 /mnt/d,再用夾帶 metadata 的方式重新掛載它:

% sudo umount /mnt/d

% sudo mount -t drvfs D: /mnt/d -o metadata,uid=1000,gid=1000,umask=22,fmask=111

先查看是否成功:

% mount -l
rootfs on / type lxfs (rw,noatime)
none on /dev type tmpfs (rw,noatime,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,noatime,gid=5,mode=620)
none on /run type tmpfs (rw,nosuid,noexec,noatime,mode=755)
none on /run/lock type tmpfs (rw,nosuid,nodev,noexec,noatime)
none on /run/shm type tmpfs (rw,nosuid,nodev,noatime)
none on /run/user type tmpfs (rw,nosuid,nodev,noexec,noatime,mode=755) binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,noatime)
D: on /mnt/d type drvfs (rw,relatime,uid=1000,gid=1000,umask=22,fmask=111,metadata,case=off)
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,case=off)

如果成功了,請記得將這設定寫進 /etc/fstab 裡面,下次 WSL 啟動時就會自動生效:

D: /mnt/d drvfs rw,relatime,uid=1000,gid=1000,metadata,umask=22,fmask=111 0 0

Docker 與 Kubernetes

雖然保哥的文章推薦在 WSL 裡的 Linux 再安裝一份 Docker engine 4,但我比較傾向不要。我比較傾向共用既有資源,讓 WSL 直接連接到 Docker Desktop for Windows 身上。畢竟,在同一台電腦上,要維護兩套 Docker engine,太累了。

我參考上官林傑的文章〈在 Windows Subsystem for Linux (WSL) 下使用 Windows 上的 Docker Engine〉,做好必要的設定,但省略文中提到的 alias 步驟(稍後會說明為什麼)。簡單來說,請先進行這三步驟:

  1. 打開 Docker Desktop for Windows 的 “Expose daemon on tcp://localhost:2375 without TLS.” 選項。

  2. 在 WSL 裡設定 DOCKER_HOST 環境變數: export DOCKER_HOST="tcp://localhost:2375"

  3. 在 WSL 裡 sudo visudo 以下環境變數: Defaults env_keep += "DOCKER_HOST"

上述文章建議透過 alias docker=docker.exe 的方式來使用 Docker Desktop for Windows 的 docker client。不過,這些 alias 未必都能在 script 中展開 5 ——有鬆綁的方法,但我不喜歡破例。我比較傾向在 WSL 裡用簡單的 wrapper script 來處理。

首先是 /usr/local/bin/docker 檔案:

#!/bin/bash

# allow WSL to have access to Docker Desktop for Windows
exec  "/mnt/c/Program Files/Docker/Docker/resources/bin/docker.exe"  "$@"

其次是 /usr/local/bin/docker-compose 檔案:

#!/bin/bash

# allow WSL to have access to Docker Desktop for Windows
exec  "/mnt/c/Program Files/Docker/Docker/resources/bin/docker-compose.exe"  "$@"

別忘記打開它們的 'x' 權限:

% sudo chmod a+x  /usr/local/bin/docker  /usr/local/bin/docker-compose

也別忘了,要將 Windows 上面的 Kubernetes 設定連接過來:

% ln -s /mnt/c/Users/xxx/.kube/config ~/.kube/config

好了,現在已經復刻一部分 Mac 的使用習慣了。是該要好好享受一下這種混血環境了。

2019-04-17 後續發展

使用了一個月,不太能夠忍受 Cmder 不夠穩定的顯示邏輯:在視窗尺寸變化時,無法正確處理字元位置。因此,我改用〈WSLtty + tmux 組合技〉。

2023-02-06 後續發展

四年來 WSL 與容器世界都起了一些變化,本文有些內容已經不敷使用。請改用〈WSL2 + Podman + K3s 組合技〉。


  1. 保哥於 2015 年九月舉辦過【打造一個具有 Linux 溫度的 Windows 命令提示字元工具】線上講座,也寫了〈介紹好用工具:Cmder〉一文。 ↩︎

  2. Cmder 的剪貼簿設定細節,請見 https://conemu.github.io/en/SettingsPaste.html 。 ↩︎

  3. 關於 WSL DrvFs 的 metadata 權限設定細節,請參考這三篇文章:“Chmod/Chown WSL Improvements”、“Going Overboard with WSL metadata”、〈WSL 配置指北:打造 Windows 最强命令行〉。 ↩︎

  4. 保哥的文章〈我的 Windows Subsystem for Linux (WSL) 終極開發人員配置 - 2018 版〉。 ↩︎

  5. Bash 的行為是:“Aliases are not expanded when the shell is not interactive, unless…”,請參考 “Why doesn’t my Bash script recognize aliases?” 這類文章。 ↩︎