# 珍惜生命，學習 Java 請少用 PowerShell 當成你主要的 Shell 環境

我個人在學習一門技術時，通常不太喜歡過渡依賴 IDE 幫我完成工作，因為方便的背後會錯過許多技術細節，所以我常常會不經意的刻意繞一下遠路。就像我今天在練習用 Maven 開發 Multi-Module Project 的時候，就發現我第一個命令就卡關了，這也太不順了吧。今天這篇文章，我就來說說為什麼建議大家還是少用 PowerShell 來當成你的主要工作環境。

### 錯誤發生的過程

今天我在跟著實作 [Multi-Module Project with Maven](https://www.baeldung.com/maven-multi-module) 這篇文章時，我發現連第一個命令都無法執行，命令是這樣的：

```bash
mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
```

我執行的時候，卻遭遇到以下錯誤訊息：

```
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.066 s
[INFO] Finished at: 2022-09-09T21:21:47+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory (G:\Projects\cc). Please verify you invoked Maven from the correct directory. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MissingProjectException
```

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/98f2e10fd1c9e2b79307-189359726-47ebb6e9-a62c-4234-ab42-aca8c08fd267.png)

老實說，這個錯誤訊息實在太詭異了，根本完全看不懂，我是要建立一個新專案啊，你幹嘛說我沒有 POM 檔案？

我就算加上了 `-X` 參數，依然是無法理解的錯誤訊息：

```
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory (G:\Projects\cc). Please verify you invoked Maven from the correct directory. -> [Help 1]
org.apache.maven.lifecycle.MissingProjectException: The goal you specified requires a project to execute but there is no POM in this directory (G:\Projects\cc). Please verify you invoked Maven from the correct directory.
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:85)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:294)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:960)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:293)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:196)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:77)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:568)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
[ERROR]
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MissingProjectException
```

### 錯誤發生的原因

完全相同的命令，我跑在 **Command Prompt** (命令提示字元) 下，是完全沒問題的！這就至少證實了一件事，在 PowerShell 環境下執行傳統的 Win32 程式或批次檔，是不太一樣的。至於哪裡不一樣，不特別去研究還真的說不上來，總之就是鬼打牆。

基本上你可以這樣想，在 Windows 執行任意程式都是以 **Command Prompt** (命令提示字元) 為基礎環境執行，這點再 50 年也不會改變的。這些年微軟推廣的 PowerShell 環境，其實也無法跳過底層架構，他有自己的命令、語法、情境，但是當你要執行的不是 Cmdlet 的時候，還是會幫你轉譯成**命令提示字元**下的命令執行，只是這個轉譯的過程沒有很清晰。在我的部落格中，不知道有幾篇文章跟這件事情有關了，總之遇到了就會採一堆雷，浪費一堆時間抓鬼。😠

這裡我整理了幾點差異給大家看看：

-   Command Prompt
    
    ```batch
    echo -DarchetypeGroupId=org.apache.maven.archetypes
    ```
    
-   Windows PowerShell
    
    由於 `echo` 是 `Write-Output` cmdlet 的 Alias 而已，理論上這個 `echo` 並非執行 Windows 執行檔或批次檔：
    
    ```powershell
    echo -DarchetypeGroupId=org.apache.maven.archetypes
    ```
    
    這裡我用 Git for Windows 內建的 `echo.exe` 來示範執行：
    
    ```powershell
    . "C:\Program Files\Git\usr\bin\echo.exe" -DarchetypeGroupId=org.apache.maven.archetypes
    ```
    
    此時你可以得到真正的轉譯結果，PowerShell 會把參數列中的第一個 `.` (dot) 分段，**一個參數**就變成**兩個參數**了，你說雷不雷！
    
    ```
    -DarchetypeGroupId=org .apache.maven.archetypes
    ```
    

我是刻意用 `echo.exe` 執行給你看，你看得出問題，如果你今天執行的是 `mvn` 或 `java` 或 `javac` 的話，你根本看不出參數寫錯，就像我今天遇到的問題一樣，從錯誤訊息完全看不出問題在哪裡，花了好多時間在找錯誤！😒

### 為什麼 PowerShell 會有這種問題？

我原本以為這是一種**語言特性**，畢竟 PowerShell 已經推出到這個市場上十多年的時間了，怎麼可能還有這種低級錯誤？

我後來在 PowerShell 的 GitHub Repo 找到 [Parameter parsing/passing: an unquoted argument that starts with a "-" (hyphen) is broken in two at the first "." (period) #6291](https://github.com/PowerShell/PowerShell/issues/6291) 這個 Issue (議題)，發現這還真的是一個 Bug 耶，而且存在很久了，微軟遲遲不修掉，也不說明原因，也不說何時要修。重點是，超多人遇到過跟這個 Bug 相關的錯誤，浪費大家無數的時間，我覺得真的是罪孽阿！😅

### 那還建議使用 PowerShell 嗎？

老實說，以前我都拒絕使用 PowerShell 環境，因為 Command Prompt 的啟動速度快，打命令也很少遇到問題。但我這幾年開始改用 PowerShell 為我主要的命令列環境，因為功能相對完整許多，像是 [PSReadline](https://learn.microsoft.com/zh-tw/powershell/module/psreadline/about/about_psreadline?view=powershell-7.2&WT.mc_id=DT-MVP-4015686) 就真的好用，還有 [Oh My Posh](https://ohmyposh.dev/) 可以打造超級華麗的 CLI 環境，這些都是我愛用 PowerShell 的原因。

我就以 Apache Maven 的 CLI 為例，我們很常看到有類似 `-DgroupId=com.duotify` 這樣的參數，或是用 `-Pname` 指定 Profile 名稱等等，這是在 Java 界常見的參數用法，其實在 Windows 比較少見這種參數用法，所以我之前遇到錯誤的機率沒有很高，但開始寫 Java 就會遇到了，而且會很常遇到！🔥

因為這種寫法，只要遇到**參數**沒有**小數點**(`.`)的時候並不會發生問題，但只要有小數點的時候，你就準備花時間抓鬼了！

解法不是沒有，但因為不是只有你會遇到問題，任何不熟悉 PowerShell 的人都有機會遇到，所以我是這樣建議的：

1.  盡量以 **Command Prompt** 做為你主要的 Shell 執行環境
    
    因為在學習 Java 的時候，我們會看很多網路上的文章，而大多數文章都不會跟你講他是在什麼 Shell 環境下執行。但我的經驗是，只要你用 Bash 或 Command Prompt 通常不太會遇到問題，但並非 100% 喔，因為 Windows 就是雷比較多！😅
    
    我認為對初學者來說，使用 **Command Prompt** 是首選，建議大家都這樣用，但非必要，因為 Command Prompt 有他自己的問題，複雜的命令也是非常難懂的，若要要做自動化還是寫 PowerShell 比較好。
    
2.  繼續使用 **PowerShell** 做為你主要的 Shell 執行環境，但要注意參數解析的問題
    
    我們這段命令：
    
    ```powershell
    mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
    ```
    
    在 PowerShell 環境下，你要自己知道遇到參數中有 `.` 的時候，要改成這樣：
    
    ```powershell
    mvn archetype:generate '-DgroupId=com.baeldung' -DartifactId=parent-project -B
    ```
    
    或者就不管這麼多了，所有參數都加上單引號也可以：
    
    ```powershell
    mvn 'archetype:generate' '-DgroupId=com.baeldung' '-DartifactId=parent-project' '-B'
    ```
    
    另外還有一種方法，就是透過 `cmd /c 'COMMAND'` 來執行命令，這可以讓你想傳到 `cmd.exe` 的命令與參數，全部都當成「字串」來處理，因此就不會有額外的解析行為發生：
    
    ```powershell
    cmd /c 'mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B'
    ```
    
    這三種都是很棒的解法，尤其是最後一種，大家可以思考一下要不要繼續用 PowerShell 喔！
    

### 停用 PowerShell 的命令解析行為

從 PowerShell 3.0 開始，有個「停用解析」功能，你只要在命令後加上 `--%` 這個特殊的 Token 就可以停用解析，後面的參數就會原封不動的傳入 `cmd.exe` 執行。

所以我們這段命令：

```powershell
mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
```

就可以改成這樣，這個命令就可以在 PowerShell 正常執行：

```powershell
mvn --% archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
```

> 注意: 上述的 `mvn` 命令其實是個 `mvn.cmd` 批次檔，並不是個 Cmdlet 喔！

感覺上，這應該是相當理想的一種解決方案，但你可能要注意的地方是，當「停用解析」之後，在 `--%` 之後的所有參數，都會傳給 `mvn.cmd` 這個批次檔，所以 `--%` 之後的所有內容，都是傳入到 `cmd.exe` 執行的喔！🔥

我寫個簡單的例子給你比較一下就知道了：

-   底下這段命令會輸出 `%JAVA_HOME%` 字串
    
    ```powershell
    . "C:\Program Files\Git\usr\bin\echo.exe" %JAVA_HOME%
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/f4dbc29037868e33b1d4-189371368-91d3565b-1392-42c8-817c-47d777d6e90c.png)
    
-   底下這段命令則會輸出 `cmd.exe` 環境下的 `%JAVA_HOME%` 環境變數
    
    ```powershell
    . "C:\Program Files\Git\usr\bin\echo.exe" --% %JAVA_HOME%
    ```
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/0247735707ff6e8048c3-189371242-2635d76f-add8-4031-9c88-ebf030bd4285.png)
    
    這種用法將無法讓你在 `--%` 後面加入任何 PowerShell 變數，這是最大的限制，必須注意！🔥
    

### 總結

在 Windows 使用 PowerShell 執行 Java 常見的 CLI 工具，總結就是一個字：「雷」！

老實說，知道了這些地雷般的技術細節之後，我還是會繼續用 PowerShell 啦，因為我的 CLI 用量比較大，只要知道怎樣不踩雷，還是可以安心使用的！😊

### 相關連結

-   [How to disable/override powershell dot notation - Stack Overflow](https://stackoverflow.com/a/73650127/910074)
-   [Parameter parsing/passing: an unquoted argument that starts with a "-" (hyphen) is broken in two at the first "." (period) #6291](https://github.com/PowerShell/PowerShell/issues/6291)
-   [Tokenizer treats . as a separator when parsing a parameter token, which causes problem to argument parsing for native commands #15541](https://github.com/PowerShell/PowerShell/issues/15541)
-   [about Parsing - PowerShell | Microsoft Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Parsing)
