﻿# 使用 WSL 2 打造優質的多重 Linux 開發環境

由於微軟這次推出 WSL 2 全新架構，可以讓你將 Linux 完整的跑在一個羽量級的 Hyper-V 虛擬機器中，不但可以用有完整的 Linux 核心、完整的 system call 相容性、啟動時花費更少的記憶體，也大幅提昇了檔案 I/O 存取的效能與網路管理的便利性。這篇文章將帶你一步一步認識 WSL 2，並且打造出優質的多重 Linux 開發環境。

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/b5e2022bdba759f8ff5c-192759987-c0cc449e-bb7e-4441-babd-ea89ed57d03c.png)

> 注意: WSL 2 只能運行在 Windows 10, Version 2004, Build 19041 以上版本，任何 SKU 版本都支援，包含 Windows 10 Home 喔！

> 注意：WSL 2 可以跟 VMWare 15.5.5+ 與 VirtualBox 6+ 並存。

### 名詞解釋

-   Distro
    
    全名為 `distribution` (散佈版本)
    
    因為 Linux 有非常多分支，例如：RedHat, Ubuntu, CentOS, Fedora, Kali, ... 等等。每個不同家的 Linux 我們就通常為一個 Distribution，而我們經常會簡寫成 `Distro`。
    
    而且 WSL 可以安裝許多套 Linux 在你的 Windows 下，因此我們說 WSL Distro 就代表著 WSL 下的特定 Distro 版本！
    
-   WLinux
    
    本文所有的命令列範例，都會用 `WLinux` 代表特定一個 WSL Distro 版本，你實際要用的時候，應該要替換成你目前正在使用的 WSL Distro 名稱！
    

### 安裝 WSL Distro 版本

剛安裝好 WSL 功能後，預設並沒有安裝任何 Linux 發行版本 (distribution)，所以你必須先從 [Microsoft Store](https://www.microsoft.com/zh-tw/search/shop/Apps?q=WSL) 安裝好任何一套 WSL Distro 才能開始使用。

你可以開啟瀏覽器，直接在網址列輸入以下網址就會自動開啟 Microsoft Store 視窗，並自動搜尋出所有 WSL 相容的應用程式。

-   [ms-windows-store://search/?query=WSL](ms-windows-store://search/?query=WSL)

> 關於 `ms-windows-store://` 網址，你可以參考 [Launch the Microsoft Store app - Windows UWP applications](https://learn.microsoft.com/en-us/windows/uwp/launch-resume/launch-store-app) 文件說明。

目前已經有 [Ubuntu 20.04 LTS](https://www.microsoft.com/zh-tw/p/ubuntu-2004-lts/9n6svws3rx71), [Ubuntu 18.04 LTS](https://www.microsoft.com/zh-tw/p/ubuntu-1804-lts/9n9tngvndl3q), [Alpine](https://www.microsoft.com/zh-tw/p/alpine-wsl/9p804crf0395), [Kali Linux](https://www.microsoft.com/zh-tw/p/kali-linux/9pkr34tncv07), ... 以及各種不同的 Distro 都會陸續上架，你甚至可以**自行製作**你自己的 WSL Distro！

> 參考 [介紹好用工具：WSL (Windows Subsystem for Linux)](/post/2019/02/01/Useful-tool-WSL-Windows-Subsystem-for-Linux) 文章。

### 列出 WSL Distro 版本

-   列出目前已經安裝的 WSL Distro 版本
    
    ```bash
    wsl -l
    ```
    
-   列出目前已經安裝的 WSL Distro 版本（包含顯示執行狀態與 WSL 版本）
    
    ```bash
    wsl -l -v
    ```
    
    執行結果如下，你可以看得出執行狀態與 WSL 版本：
    
    ```
      NAME                   STATE           VERSION
      WLinux                 Stopped         2
      docker-desktop         Stopped         2
      docker-desktop-data    Stopped         2
    * Ubuntu-20.04           Running         1
      Ubuntu-18.04           Stopped         1
    ```
    
-   列出目前正在執行中的 WSL Distro 版本
    
    ```bash
    wsl -l --running
    ```
    
-   列出所有的 WSL Distro 版本（包含已經解除安裝的版本）
    
    ```bash
    wsl -l --all
    ```
    
-   列出所有的 WSL Distro 版本（僅顯示 WSL Distro 名稱）（方便做 CLI 整合）
    
    ```bash
    wsl -l -q
    ```
    

### 升級 WSL Distro 至 WSL 2 版本

**請注意**：我目前的 Windows 10 ver 2004 的 OS Build 版本為 `19041`，這個版本目前預設還是以 WSL 1 為主，你必須要從 OS Build 版本 **20150** 開始，WSL 2 才是預設值。因此，你從 [Microsoft Store](https://www.microsoft.com/zh-tw/search/shop/Apps?q=WSL) 安裝好的任何一套 WSL Distro，預設都會採用 WSL 1 版本，你必須手動升級到 WSL 2 版本才能享有 WSL 2 的絕佳 I/O 效能！

以下是升級的命令與參數：

-   將 WSL Distro 升級到 WSL 2 版本
    
    ```bash
    wsl -d Ubuntu-20.04 --set-version WLinux 2
    ```
    
-   將 WSL Distro 降級到 WSL 1 版本 (你應該不會想這麼做吧!)
    
    ```bash
    wsl -d Ubuntu-18.04 --set-version WLinux 1
    ```
    

### 調整 Shell 執行環境

由於我用 [Pengwin Linux](https://github.com/WhitewaterFoundry/Pengwin/blob/master/README.zh-hant.md) (based on Debian®)，一套針對 WSL 最佳化過的 WSL Distro，第一次啟動時可以執行 `pengwin-setup` 命令，自動調整系統到最佳狀態！

-   設定 PPA ([Personal Package Archives](https://launchpad.net/ubuntu/+ppas)) 工具
    
    安裝 才可以使用 `add-apt-repository` 工具！
    
    ```bash
    sudo apt-get install -y software-properties-common
    ```
    
    匯入 PPA 金鑰與測試安裝 ASCII 水族館套件 (Optional)
    
    ```bash
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F3985DE9980B9063
    sudo add-apt-repository ppa:ytvwld/asciiquarium
    sudo apt-get update && sudo apt-get install asciiquarium
    ```
    
-   建議先更新系統到最新版
    
    ```bash
    sudo apt update && sudo apt upgrade
    ```
    
-   要用 Git 版控，建議調整一下設定值
    
    一定要設定 `user.name` 與 `user.email` 資訊（請自行調整內容）
    
    ```bash
    git config --global user.name Will
    git config --global user.email doggy.huang@gmail.com
    ```
    
    常用設定（避免顯示亂碼）
    
    ```bash
    git config --global core.editor vim
    git config --global core.autocrlf false
    git config --global core.quotepath false
    git config --global help.autocorrect 30
    git config --global color.diff auto
    git config --global color.status auto
    git config --global color.branch auto
    ```
    
    常用 alias 設定
    
    ```bash
    git config --global alias.ci   commit
    git config --global alias.cm   "commit --amend -C HEAD"
    git config --global alias.co   checkout
    git config --global alias.st   status
    git config --global alias.sts  "status -s"
    git config --global alias.br   branch
    git config --global alias.re   remote
    git config --global alias.di   diff
    git config --global alias.type "cat-file -t"
    git config --global alias.dump "cat-file -p"
    git config --global alias.lo   "log --oneline"
    git config --global alias.ls   "log --show-signature"
    git config --global alias.ll   "log --pretty=format:'%h %ad | %s%d [%Cgreen%an%Creset]' --graph --date=short"
    git config --global alias.lg   "log --graph --pretty=format:'%Cred%h%Creset %ad |%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset [%Cgreen%an%Creset]' --abbrev-commit --date=short"
    git config --global alias.alias "config --get-regexp ^alias\."
    git config --global alias.ignore '!'"gi() { curl -sL https://www.gitignore.io/api/\$@ ;}; gi"
    git config --global alias.iac  '!'"giac() { git init && git add . && git commit -m 'Initial commit' ;}; giac"
    ```
    
    > [如何設定 git ignore 命令並自動下載所需的 .gitignore 範本](/post/2020/05/24/Setup-git-ignore-alias-to-download-gitignore-templates)
    
-   新增 `~/.vimrc` 檔案 (學習 [Vim](https://www.vim.org/) 是 Linux 玩家必備技能)
    
    ```vim
    syntax on
    set background=dark
    
    let &t_SI .= "\<Esc>[?2004h"
    let &t_EI .= "\<Esc>[?2004l"
    
    inoremap <special> <expr> <Esc>[200~ XTermPasteBegin()
    
    function! XTermPasteBegin()
      set pastetoggle=<Esc>[201~
      set paste
      return ""
    endfunction
    ```
    
-   設定更清楚的命令提示字串(Prompt)，將以下設定加入到 `.profile` 檔案中
    
    為了能夠在不同的 Shell 環境區分不同的 WSL Distro，我特別自訂 Bash Shell 的命令提示字串(Prompt)，只要將以下設定加入到 `.profile` 檔案中即可：
    
    ```bash
    export PS1="\[\033[32m\]\u@$WSL_DISTRO_NAME\[\033[00m\]:\[\033[36m\]\w\[\033[00m\]$ "
    ```
    
    > 參見 [Customizing my WSL bash prompt](https://medium.com/@callback.insanity/customizing-my-wsl-bash-prompt-374daf85a7ae) 與 [自訂 Linux 的 Bash Shell 命令提示字串 Prompt（一）：基本用法](https://blog.gtwang.org/linux/how-to-make-a-fancy-and-useful-bash-prompt-in-linux-1/) 文章。
    
-   要用 vim 當成預設編輯器，可以將 `EDITOR` 環境變數到 `.profile` 檔案中
    
    ```bash
    sudo update-alternatives --set editor /usr/bin/vim.basic
    ```
    
    ```bash
    export EDITOR=vim
    ```
    
-   要用 bash 當成預設 shell 環境，有兩條重要的 [shopt](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) 參數要調整，請加入到 `.bashrc` 檔案中
    
    ```bash
    shopt -s direxpand
    shopt -s no_empty_cmd_completion
    ```
    
    啟用 `direxpand` 會讓你的 bash 在執行路徑自動完成時自動展開資料夾路徑，否則會出現[這個問題](https://github.com/microsoft/vscode/issues/103315)。不過當時我在 GitHub 提供的解決方案是錯的，設定 `shopt -u progcomp` 會導致無法使用 Bash 自動完成功能！
    
    `no_empty_cmd_completion` 請務必設定，因為有兩種狀況會讓你的 WSL 近乎死當！
    
    1.  在沒有輸入任何命令的情況下，直接按下 `ESC` 鍵兩次
    2.  你可以先輸入一個以上的空白字元，然後按兩下 `TAB` 鍵
    
    這兩種情境，都會讓整個 Shell 環境等待超過 35 秒才會有所反應，期間你按什麼都沒用，因為 bash 會自動掃描所有可執行檔的名稱。在 WSL 之下，不但會掃描 Linux 環境下 `PATH` 環境變數指定路徑下的所有可執行檔，還會掃描 Windows 環境下所有 `PATH` 環境變數指定路徑下的所有可執行檔，這是相當恐怖的數字，以我的電腦為例，就有超過 7000 個建議，所以反應相當的慢！
    
    > 參見文章：[4.3.2 The Shopt Builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) & [Shell: “Display all 2588 possibilities?”](https://unix.stackexchange.com/questions/171489/shell-display-all-2588-possibilities/171494)
    
-   要用 GPG 簽章，一定要設定以下 `GPG_TTY` 環境變數到 `.profile` 檔案中
    
    ```bash
    export GPG_TTY=$(tty)
    ```
    
    > 參見 [如何使用 GPG (GnuPG) 對 Git Commit 與 Tag 進行簽章](/post/2020/05/04/How-to-use-GPG-sign-git-commit-and-tag-object) 文章
    
-   匯入自己的 GPG 金鑰並設為絕對信任
    
    > 請自行替換 `doggy8088` 為你的個人帳號，基本上 GitHub 會為每個人建立一組 GPG 私鑰密碼。
    
    ```bash
    curl -s -o doggy8088.gpg https://github.com/doggy8088.gpg
    
    gpg-agent --daemon
    gpg --import doggy8088.gpg # 這個步驟需要輸入 GPG 私鑰密碼
    
    rm -f doggy8088.gpg
    gpgconf --kill gpg-agent
    
    GPGKEYID=$(gpg --list-keys --keyid-format SHORT | sed -n '3p' | cut -d '/' -f 2 | cut -d ' ' -f 1)
    echo -e "trust\n5\ny\n" | gpg --command-fd 0 --expert --edit-key $GPGKEYID
    
    git config --global user.signingkey $GPGKEYID
    git config --global commit.gpgsign true
    ```
    
-   設定 WSL 共用 Windows 下的 Git Credential Manager
    
    ```bash
    git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager.exe"
    ```
    
    > 參見 [How do I set up Git Credential Manager? (How do I use my Windows Git permissions in WSL?)](https://learn.microsoft.com/en-us/windows/wsl/faq#how-do-i-set-up-git-credential-manager-how-do-i-use-my-windows-git-permissions-in-wsl) 文章。
    
-   建立 SSH 金鑰組
    
    ```bash
    ssh-keygen -t rsa -b 4096 -f $HOME/.ssh/id_rsa -N ''
    touch ~/.ssh/authorized_keys
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    ```
    
-   讓執行 sudo 的時候免輸入密碼
    
    ```bash
    echo "will ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/will
    ```
    
-   調整 `~/.bashrc` 讓 WSL 預設開啟時進入 `$HOME` 目錄，而不是 Windows 的 Home 目錄 (`%USERPROFILE%`)
    
    以下命令可以限制只有開啟 WSL 2 時的預設 Windows Home 目錄 (`%USERPROFILE%`) 才會自動切換到 Linux 的 `$Home` 目錄：
    
    ```bash
    if [ "${PWD:-}" = "/mnt/c/Users/$USER" ]; then
      cd ~
    fi
    ```
    
    > 請不要建立 `~/.bash_profile` 檔案，以免 `~/.profile` 檔案無法自動載入。詳見：[VSCode Integrated Terminal Doesn't Load .bashrc or .bash\_profile](https://stackoverflow.com/a/73879778/910074)
    
-   調整 `/etc/wsl.conf` 設定檔（將預設登入帳戶設定為 `will` 帳號）
    
    將以下兩行加入到 `/etc/wsl.conf` 設定檔最後面：
    
    ```bash
    [user]
    default=will
    ```
    
    > 如果檔案不存在，也可以直接建立新的 `/etc/wsl.conf` 檔案！
    

### 將 WSL Distro 移動到不同磁碟機

-   匯出特定 WSL Distro 版本為 `*.tar` 檔
    
    ```bash
    wsl --export WLinux WLinux.tar
    ```
    
    1.  在 `--export` 後面的第 1 個參數為 WSL Distro 名稱
    2.  在 `--export` 後面的第 2 個參數為 `*.tar` 檔案路徑
-   匯入特定 WSL Distro 版本
    
    ```bash
    wsl --import WLinux C:\WSL\WLinux WLinux.tar
    ```
    
    1.  在 `--import` 後面的第 1 個參數為 WSL Distro 名稱
    2.  在 `--import` 後面的第 2 個參數為 WSL Distro 的**檔案系統**要直接存放在哪裡
    3.  在 `--import` 後面的第 3 個參數為先前 WSL Distro 匯出的 `*.tar` 檔案路徑

### 同時安裝多套 Ubuntu 20.04 LTS 版本

基本上，你一次只能從 Microsoft Store 安裝**一種** WSL Distro 版本，如果我想準備多個不同的開發環境，但都使用 `WLinux` 這個 WSL Distro 怎麼辦呢？

其實很簡單，你先找到一個 `WLinux`，並且把基本設定都搞定，當成 WSL Distro 範本，接著先匯出，再匯入多個版本即可！ 👍

底下這個例子(PowerShell)，就是將現有的 WLinux 先匯出，然後匯入成三個不同的 WSL Distro 名稱 (指定使用 WSL 2 版本)：

```powershell
cd $env:USERPROFILE

wsl --export WLinux WLinux.tar

wsl --import WLinuxDev    .\WLinuxDev    WLinux.tar --version 2
wsl --import WLinuxGo     .\WLinuxGo     WLinux.tar --version 2
wsl --import WLinuxDotNet .\WLinuxDotNet WLinux.tar --version 2
```

如果要指定啟動特定 WSL Distro 的話，只要加上 `-d <Distro>` 參數即可：

```bash
wsl -d WLinuxDev
```

### 如何存取 Windows 與 WSL Distro 之間的檔案

-   Windows -> WSL 2
    
    你可以開啟「檔案總管」並直接用 `\\wsl$\<DistroName>` 這個路徑，透過「網路芳鄰」的方式直接存取 WSL 2 下的任何檔案！
    
    > 注意：從 [Windows 10 Build 20175](https://learn.microsoft.com/en-us/windows/wsl/release-notes#build-20175) 開始，WSL 2 會開始支援 `\\wsl\<DistroName>` 這種路徑，但舊的 `\\wsl$\` 還會支援。
    
    例如：你的 WSL 2 有個名為 `WLinux` 的 Linux 環境，那麼你從 Windows 就可以直接以 `\\wsl$\WLinux` 來存取檔案。如果檔案是 `/home/will/output.json` 的話，就可以從 Windows 用 `\\wsl$\WLinux\home\will\output.json` 路徑來直接存取檔案。
    
    > 請注意：WSL 2 每份 WSL Distro 內建使用的虛擬硬碟只有 `256GB` 而已，如果會用超過就會發生錯誤，必須參考 [Expanding the size of your WSL 2 Virtual Hardware Disk](https://learn.microsoft.com/en-us/windows/wsl/compare-versions#expanding-the-size-of-your-wsl-2-virtual-hardware-disk) 文件進行調整。
    
-   WSL 2 -> Windows
    
    你可以直接用 `/mnt/<DRIVE>` 這個路徑，存取任何 Windows 上面的檔案！
    
    例如：Windows 的 `C:\Projects` 目錄，就可以從 WSL 2 中以 `/mnt/c/Projects` 來存取檔案。
    
    另外，你可以從 WSL Distro 內執行以下命令，就可以快速開啟「檔案總管」並且直接進入你在 WSL Distro 內的資料夾，超級方便！
    
    ```bash
    cd ~
    explorer.exe .
    ```
    
    > 請注意：你一定要輸入 `explorer.exe .` 才可以，若是輸入 `explorer .` 是沒有效果的。
    

### Windows 與 WSL Distro 之間的網路服務如何互通

-   Windows -> WSL 2
    
    你所有從 WSL 2 運行的網路服務，只要監聽**本機網路介面**(loopback interface)，預設在 Windows 就可以透過 `localhost` 直接連接，非常方便！
    
    例如你在 WSL 2 中執行 MySQL 資料庫伺服器，從 Windows 連過去就只要從 `localhost:3306` 即可連上。
    
    > 如果想在 WSL 2 任何一個 WSL Distro 安裝資料庫，可以參考 [Get started with databases on Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-database) 這份文件。
    
-   WSL 2 -> Windows
    
    你所有從 Windows 運行的網路服務，只要是能**對外連接**的服務（如 IIS, SQL Server, ... 等），都可以從 WSL 2 直接連過去，但是要先找到對的 IP 地址才可以連接。
    
    要先透過 `cat /etc/resolv.conf` 取得 WSL 2 的 Linux 下註冊的 `nameserver` IP 地址，這個 IP 地址就是 Windows 本機的網路介面 IP，你可以透過這個 IP 從 WSL 2 連接到 Windows 本機的網路服務。
    
    > 注意：對 Windows 來說，這算是「公開網路」連線，記得要看一下**防火牆**(`wf.msc`)設定是否有封鎖「公開網路」的**連內規則**(Inbound Rules)。
    
    如果你在 Windows 命令提示字元執行 `ipconfig` 的話，會看到有個標示 `(WSL)` 字樣的網路介面，這個 IPv4 地址(`172.19.239.1`)就是你可以從 WSL Distro 內部連接到 Windows 主機的 IP 地址：
    
    ```log
    Ethernet adapter vEthernet (WSL):
    
      Connection-specific DNS Suffix  . :
      Link-local IPv6 Address . . . . . : fe80::99e5:4b5:5d8a:7eeb%39
      IPv4 Address. . . . . . . . . . . : 172.19.239.1
      Subnet Mask . . . . . . . . . . . : 255.255.240.0
      Default Gateway . . . . . . . . . :
    ```
    
    由於每次都要找本機 IP 很麻煩，我直接寫成以下命令，放到 `~/.profile` 啟動檔，以後直接透過 `$local` 就可以自動取得 Windows 本機的 IP 地址：
    
    ```bash
    export local=$(cat /etc/resolv.conf | grep nameserver | cut -d ' ' -f 2)
    ```
    

### 指定「特定使用者」登入 WSL Distro 執行

-   指定「特定使用者」登入（而且可以免密碼就能直接登入到 Shell 環境）
    
    ```bash
    wsl -d Ubuntu-20.04 -u root
    ```
    
    > 注意：登你可以用 `root` 登入系統，自然可以用 `passwd <LOGIN>` 來修改任何帳戶的密碼！
    

### 關閉 WSL Distro 執行

-   關閉特定 WSL Distro 執行
    
    ```bash
    wsl -t WLinux
    ```
    
-   關閉所有 WSL Distro 執行（等同於關閉 VM 執行）
    
    ```bash
    wsl --shutdown
    ```
    

### 移除 WSL Distro 版本

> 注意：所有該 WSL Distro 下所有的檔案，都會被全數刪除，刪除後無法復原喔！

```bash
wsl.exe --unregister WLinux2
```

### 相關連結

-   [Windows Subsystem for Linux Documentation](https://learn.microsoft.com/en-us/windows/wsl/)
-   [Release Notes for Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/release-notes)
-   [WSL commands and launch configurations](https://learn.microsoft.com/en-us/windows/wsl/wsl-config)
    -   [Configure per distro launch settings with wslconf](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#managing-multiple-linux-distributions-in-earlier-windows-versions)
    -   [Configuration Options](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#configuration-options)
    -   [Configure global options with .wslconfig](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#configuration-options)
-   [Accessing network applications with WSL](/post/2020/07/26/Multiple-Linux-Dev-Environment-build-on-WSL-2)
-   [Export and Import WSL Linux Distro in Windows 10](https://winaero.com/blog/export-import-wsl-linux-distro-windows-10/)
-   [Install WSL on Windows Server](https://learn.microsoft.com/en-us/windows/wsl/install-on-server)
-   [回報任何 WSL 軟體問題的地方](https://github.com/Microsoft/wsl/issues)
-   [回報任何 WSL 文件問題的地方](https://github.com/MicrosoftDocs/wsl/issues)
