# 關於 PATH 環境變數過長而導致命令提示字元下無法執行特定程式的問題

最近由於經常在研究前端常用的各式工具，像是 npm, bower, gulp, webpack, yo, git 等等的工具用的很多，因此親手打指令執行程式的機會變多了，然而有好長一段時間間斷地出現了一些靈異現象，我卻無法解釋，直到最近終於發現了一些潛在的地雷，身處 Windows 平台下的前端工程師們不得不注意。由於我最近迷上了 [Cmder](http://cmder.net/) 工具，在使用這套工具時可以讓我在輸入指令時，就像身在 Linux 環境下一樣自在，功能很多、支援鍵盤快速鍵、還支援命令與參數自動完成，非常好用，不過有時候卻發現他有點不太靈光，明明已經裝好了 npm 或 bower 工具，就是無法正常執行，我開啟 Windows 內建的「命令提示字元」視窗卻又可以執行。但問題是並不是永遠都不能執行，因為有時候又可以正常執行。這完全是一個農曆七月鬼打牆的概念，我將用這篇文章來說明問題背後的原理與解決方法。

### 問題描述

我們先來看看打開 [Cmder](http://cmder.net/) 之後無法執行 npm, bower 的畫面：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0e1741bdeefdd0b59d74-image_2724.png "image")

如果此時你打開 Cmd.exe 命令提示字元，卻會發現程式明明是可以執行的：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/9581bd25f9cd39514d23-image_2725.png "image")

我遇到這個問題時覺得很納悶，第一時間會認為是不是 PATH 環境變數沒設定好？或是被清除掉了？但進一步檢查後卻發現明明都有設定好啊，為什麼就是 Cmder 不能用呢？

後來我認真的把 PATH 從環境變數的設定介面中全部複製到文字編輯器裡，仔細了檢查一下，除了 PATH 路徑變得很長外，其他基本上沒什麼異狀。

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/00044a2234e21173c177-image_2726.png "image")

然而，我在第一時間並沒有意識到 PATH 環境變數原來是有長度限制的，所以花了我好多時間研究為什麼會這樣。

最後我試著將依些很少用的執行檔路徑從 PATH 環境變數中移除，很神奇的 Cmder 就可以正常運作了，這時才終於知道原來 Windows 作業系統下不能設定太長的 PATH 環境變數！

### 發生原因

你可能會想說，是不是最近安裝了很多前端工具，所以把 Windows 環境變數搞得特別長呢？答案：**不是！**

發生這個問題的原因根本不在這些前端工具的安裝過程中，而是我的電腦在經年累月安裝各式軟體的過程中，PATH 環境變數不斷累加上去各個軟體的執行檔路徑，而最近剛好達到這個 **2,048** 字元的臨界點而已。所以當我在執行 Cmd.exe 命令提示字元的時候，我的工作階段下的 PATH 環境變數還沒超出他的長度限制，所以程式都可以正常執行。

我後來研究了一下，原來 Cmder 會在啟動的時候，自動先執行一個 **init.bat** 批次檔，這個批次檔也會在工作階段下的 PATH 環境變數加入一些其他的路徑（如下圖示），而意外地讓 PATH 環境變數超出 Windows 的限制，也就是 **2,048** 字元。

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5645db2eade14bd0b974-image_2727.png "image")

你可能更會納悶的是，什麼應用程式路徑這麼長，長到會有機會超過 **2,048** 這麼長呢？不說你不知道，你可以現在就打開你的命令提示字元，輸入一下 **echo %PATH%** 就可以知道你的 PATH 環境變數有多長了！ ^^

※ 你也可以執行 **ECHO.%PATH:;= & ECHO.%** 輸出每行一個路徑。

### 解決方案

解決方法別無他法：**你只能想辦法將 PATH 環境變數縮短**。

首先，你必須先搞懂環境變數分兩種：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/512547b9a1a09cee5649-image_2728.png "image")

-   **使用者變數**
    -   每位使用者都會有一組環境變數設定，所以屬於該使用者需要的環境變數請設定在這裡。
-   **系統變數**
    -   所有使用者都會共用的環境變數設定，全部都設定在這裡，所以使用者登入都會一律載入這裡定義的環境變數。
    -   不僅僅一般使用者，包含所有系統使用者或一些服務帳號執行的程式，預設都會載入系統環境變數。

另外，PATH 環境變數套用的順序是優先套用 **系統變數** 再套用 **使用者變數**，意思也就是說 **系統變數** 的優先權較高。

**方法 1: 減少重複定義的路徑**

-   你仔細去比對兩邊的設定，會發現重複率還蠻高的，你可以試圖刪除一些重複的路徑。

**方法 2: 刪除無用的路徑**

-   有些應用程式在移除之後並不會自動從 PATH 環境變數設定中移除路徑。
    -   例如我最近安裝了 Git for Windows 2.5 版，這個版本是 64 位元的，執行檔路徑改為 C:\\Program Files\\Git\\cmd，但是原先的 C:\\Program Files (x86)\\Git\\cmd 殘留其中，因此可以手動刪除。
    -   各位可以參考 [build - How to check if directories listed in System PATH variable are valid? - Stack Overflow](http://stackoverflow.com/questions/7337794/how-to-check-if-directories-listed-in-system-path-variable-are-valid) 提到的批次檔，他可以幫你快速檢查是否在 PATH 環境變數中是否存在無效的路徑。
-   有些應用程式同時安裝了多個版本，但你其實只需要用到最新版，這些舊版路徑也可以移除。
    -   例如我的 SQL Server Management Studio 同時安裝過 2012, 2014 與 2016 版，所以在我的 PATH 環境變數中同時存在了 C:\\Program Files (x86)\\Microsoft SQL Server\\110\\DTS\\Binn\\ 與 C:\\Program Files (x86)\\Microsoft SQL Server\\120\\DTS\\Binn\\ 與 C:\\Program Files (x86)\\Microsoft SQL Server\\130\\DTS\\Binn\\，舊版的路徑根本是不需要的，因為當我有需要在命令提示字元輸入 ssms 或 sqlcmd 時，一定是想執行最新版（因為相容性較高，新舊都能用），這時你就可以將舊的路徑刪除。
    -   你可以用 **where ssms** 或 **where sqlcmd** 查詢執行檔所在路徑，如果出現多筆，就代表你的 PATH 環境變數額外定義了一些不必要的路徑，第一順位的路徑就是你會執行的程式。如下圖示：  
        ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/74748f3729acee0a83a8-image_2729.png "image")
    -   **你很難想像，光是這樣一個檢查動作，可以刪除超過 2,000 個字元！！！**

除了上述方法外，由於系統層級的 PATH 環境變數通常會長很多，我個人還會把一些註冊在系統 PATH 環境變數的路徑搬到使用者層級的 PATH 環境變數中，這樣也有助於降低系統層級 PATH 環境變數的字元數。

### 技術細節

我是個熱愛追求細節的人，對於為什麼是 **2,048** 字元呢？我很想了解！

事實上，Windows 環境下的所有環境變數的總長度限制是 **32,760** 字元，這可是包含「所有的環境變數」（包含變數名稱與值），所以如果你完全沒有其他環境變數，你大可設定一個 PATH 環境變數，且長度可以長到 **32,754** 個字元。

另外，你在批次檔中執行程式時，PATH 環境變數的總長度會占用到「[最大單一命令長度](https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553/)」的字元限制，這個限制有 **32,767** 字元，所以執行時也不能超過這個數字。

然後上述這兩點都不是 **2,048** 字元的限制，照理來說 PATH 可接受的字元數應該會更長不是嗎？

事實上是這樣的，當你在控制台設定環境變數時，這些環境變數都是存到 **系統機碼** (Registry) 中的，雖然系統機碼的字串值可以儲存超過 **2,048** 字元，但**「命令提示字元」在從機碼讀取 PATH 環境變數時最多只能讀取 2,048 字元**，超出這個數字的字元全部都會被截斷，所以之前有讀到的程式可以執行，但之後被截斷的路徑由於沒有被完整讀入，所以打指令時會找不到路徑，這就是靈異事件的關鍵啊！

-   **「命令提示字元」在從機碼讀取 PATH 環境變數時最多只能讀取 2,048 字元**
-   **「命令提示字元」在從機碼讀取 PATH 環境變數時最多只能讀取 2,048 字元**
-   **「命令提示字元」在從機碼讀取 PATH 環境變數時最多只能讀取 2,048 字元**

另外，你在環境變數對話框上（如下圖），是可以顯示的 PATH 環境變數超過 **2,048** 字元的，但從這個輸入框卻無法輸入超過 **2,048** 字元：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cc8752c2402b7d3ecdaa-image_2730.png "image")

你若透過 [SET](http://ss64.com/nt/set.html) 命令設定 PATH 環境變數，長度並沒有 **2,048** 字元限制，因為這個 PATH 環境僅套用在當下工作階段中。但你若使用其他工具程式或 [SETX](https://technet.microsoft.com/en-us/library/cc755104.aspx) 工具，是可以寫入 PATH 環境變數超過 **2,048** 字元的 (寫入到系統機碼中)。

你看看、你看看，這種可以寫入機碼，但卻無法讀取超過 **2,048** 字元這種是，是不是有種被 Windows 惹惱的感覺，這真的好雷啊～ **\>"<**

知道了這些細節後，我想這個問題以後終於不會再鬼打牆了，這真是個好特別的冷知識啊！ ^\_^

**相關連結**

-   [What is the command line length limit? - The Old New Thing - Site Home - MSDN Blogs](https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553/)
-   [What is the maximum length of an environment variable? - The Old New Thing](https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083/)
-   [A returned path environment variable is truncated to 1,024 bytes on a Windows Server 2003-based or Windows XP-based computer](https://web.archive.org/web/20141212215420/http://support.microsoft.com/kb/906469/en-us)
-   [Limitation to the length of the System PATH variable | Intel® Developer Zone](https://software.intel.com/en-us/articles/limitation-to-the-length-of-the-system-path-variable)
-   [Set - Environment Variable | Windows CMD | SS64.com](http://ss64.com/nt/set.html)
-   [SetX - Set environment variables | Windows CMD | SS64.com](http://ss64.com/nt/setx.html)
-   [How do I add to the Windows PATH variable using setx? Having weird problems - Stack Overflow](http://stackoverflow.com/questions/19287379/how-do-i-add-to-the-windows-path-variable-using-setx-having-weird-problems)
-   [build - How to check if directories listed in System PATH variable are valid? - Stack Overflow](http://stackoverflow.com/questions/7337794/how-to-check-if-directories-listed-in-system-path-variable-are-valid)
-   [windows - How to check if directory exists in %PATH%? - Stack Overflow](http://stackoverflow.com/questions/141344/how-to-check-if-directory-exists-in-path)
-   [batch file - 'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell - Stack Overflow](http://stackoverflow.com/questions/5471556/pretty-print-windows-path-variable-how-to-split-on-in-cmd-shell/5472168#5472168)
