我個人在學習一門技術時,通常不太喜歡過渡依賴 IDE 幫我完成工作,因為方便的背後會錯過許多技術細節,所以我常常會不經意的刻意繞一下遠路。就像我今天在練習用 Maven 開發 Multi-Module Project 的時候,就發現我第一個命令就卡關了,這也太不順了吧。今天這篇文章,我就來說說為什麼建議大家還是少用 PowerShell 來當成你的主要工作環境。
錯誤發生的過程
今天我在跟著實作 Multi-Module Project with Maven 這篇文章時,我發現連第一個命令都無法執行,命令是這樣的:
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
老實說,這個錯誤訊息實在太詭異了,根本完全看不懂,我是要建立一個新專案啊,你幹嘛說我沒有 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
echo -DarchetypeGroupId=org.apache.maven.archetypes
-
Windows PowerShell
由於 echo
是 Write-Output
cmdlet 的 Alias 而已,理論上這個 echo
並非執行 Windows 執行檔或批次檔:
echo -DarchetypeGroupId=org.apache.maven.archetypes
這裡我用 Git for Windows 內建的 echo.exe
來示範執行:
. "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 這個 Issue (議題),發現這還真的是一個 Bug 耶,而且存在很久了,微軟遲遲不修掉,也不說明原因,也不說何時要修。重點是,超多人遇到過跟這個 Bug 相關的錯誤,浪費大家無數的時間,我覺得真的是罪孽阿!😅
那還建議使用 PowerShell 嗎?
老實說,以前我都拒絕使用 PowerShell 環境,因為 Command Prompt 的啟動速度快,打命令也很少遇到問題。但我這幾年開始改用 PowerShell 為我主要的命令列環境,因為功能相對完整許多,像是 PSReadline 就真的好用,還有 Oh My Posh 可以打造超級華麗的 CLI 環境,這些都是我愛用 PowerShell 的原因。
我就以 Apache Maven 的 CLI 為例,我們很常看到有類似 -DgroupId=com.duotify
這樣的參數,或是用 -Pname
指定 Profile 名稱等等,這是在 Java 界常見的參數用法,其實在 Windows 比較少見這種參數用法,所以我之前遇到錯誤的機率沒有很高,但開始寫 Java 就會遇到了,而且會很常遇到!🔥
因為這種寫法,只要遇到參數沒有小數點(.
)的時候並不會發生問題,但只要有小數點的時候,你就準備花時間抓鬼了!
解法不是沒有,但因為不是只有你會遇到問題,任何不熟悉 PowerShell 的人都有機會遇到,所以我是這樣建議的:
-
盡量以 Command Prompt 做為你主要的 Shell 執行環境
因為在學習 Java 的時候,我們會看很多網路上的文章,而大多數文章都不會跟你講他是在什麼 Shell 環境下執行。但我的經驗是,只要你用 Bash 或 Command Prompt 通常不太會遇到問題,但並非 100% 喔,因為 Windows 就是雷比較多!😅
我認為對初學者來說,使用 Command Prompt 是首選,建議大家都這樣用,但非必要,因為 Command Prompt 有他自己的問題,複雜的命令也是非常難懂的,若要要做自動化還是寫 PowerShell 比較好。
-
繼續使用 PowerShell 做為你主要的 Shell 執行環境,但要注意參數解析的問題
我們這段命令:
mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
在 PowerShell 環境下,你要自己知道遇到參數中有 .
的時候,要改成這樣:
mvn archetype:generate '-DgroupId=com.baeldung' -DartifactId=parent-project -B
或者就不管這麼多了,所有參數都加上單引號也可以:
mvn 'archetype:generate' '-DgroupId=com.baeldung' '-DartifactId=parent-project' '-B'
另外還有一種方法,就是透過 cmd /c 'COMMAND'
來執行命令,這可以讓你想傳到 cmd.exe
的命令與參數,全部都當成「字串」來處理,因此就不會有額外的解析行為發生:
cmd /c 'mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B'
這三種都是很棒的解法,尤其是最後一種,大家可以思考一下要不要繼續用 PowerShell 喔!
停用 PowerShell 的命令解析行為
從 PowerShell 3.0 開始,有個「停用解析」功能,你只要在命令後加上 --%
這個特殊的 Token 就可以停用解析,後面的參數就會原封不動的傳入 cmd.exe
執行。
所以我們這段命令:
mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
就可以改成這樣,這個命令就可以在 PowerShell 正常執行:
mvn --% archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project -B
注意: 上述的 mvn
命令其實是個 mvn.cmd
批次檔,並不是個 Cmdlet 喔!
感覺上,這應該是相當理想的一種解決方案,但你可能要注意的地方是,當「停用解析」之後,在 --%
之後的所有參數,都會傳給 mvn.cmd
這個批次檔,所以 --%
之後的所有內容,都是傳入到 cmd.exe
執行的喔!🔥
我寫個簡單的例子給你比較一下就知道了:
-
底下這段命令會輸出 %JAVA_HOME%
字串
. "C:\Program Files\Git\usr\bin\echo.exe" %JAVA_HOME%
-
底下這段命令則會輸出 cmd.exe
環境下的 %JAVA_HOME%
環境變數
. "C:\Program Files\Git\usr\bin\echo.exe" --% %JAVA_HOME%
這種用法將無法讓你在 --%
後面加入任何 PowerShell 變數,這是最大的限制,必須注意!🔥
總結
在 Windows 使用 PowerShell 執行 Java 常見的 CLI 工具,總結就是一個字:「雷」!
老實說,知道了這些地雷般的技術細節之後,我還是會繼續用 PowerShell 啦,因為我的 CLI 用量比較大,只要知道怎樣不踩雷,還是可以安心使用的!😊
相關連結