Windows 批处理脚本:尝试通过使用管道循环命令结果来提取 Java 版本

Windows batch script: trying to extract Java version by looping on command results with pipe

我想在我的机器上提取 Java 版本。
1/ 我安装了 Oracle Java 8,命令 where java 输出:

C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe

2/命令java -version输出如下:

java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

3/命令java -version 2>&1 | find "version"输出如下:

java version "1.8.0_221"

所以我想在批处理文件中执行与最后一个命令完全相同的操作。所以在批处理文件中,我有类似于步骤 2 的代码:

@echo off & setLocal enableExtensions enableDelayedExpansion
set /a count=0
for /f "tokens=*" %%j in ('where java') do (
    set /a count+=1
    set JAVA[!count!]=%%j
    call :echo_java
)
goto :eof

:echo_java
    set java_cmd="!JAVA[%count%]!" -version
    for /f "tokens=*" %%v in ('%java_cmd% 2^>^&1') do echo %%v
    exit /b 0

但是一旦我将函数 :echo_java 修改为等同于这样的第 3 步:

:echo_java
    set java_cmd="!JAVA[%count%]!" -version
    for /f "tokens=*" %%v in ('%java_cmd% 2^>^&1 ^| find "version"') do echo %%v
    exit /b 0

我收到这个错误:

'C:\Program' is not recognized as an internal or external command, operable program or batch file.

我想这里发生了一些糟糕的转义,但我真的看不出有什么问题。

行变量扩展后:

for /f "tokens=*" %%v in ('%java_cmd% 2^>^&1 ^| find "version"') do echo %%v

扩展为类似:

for /f "tokens=*" %%v in ('"C:\Program Files (x86)\...\java.exe" 2^>^&1 ^| find "version"') do echo %%v

所以for /F要执行的命令行以"开始和结束。

for /F 使用 cmd /c 执行该命令行,删除周围的 ",从而留下:

C:\Program Files (x86)\...\java.exe" 2>&1 | find "version

这显然是不正确的语法。

为避免这种情况,请更改命令行,使其不被引号括起来(通过将重定向部分移至此处):

for /f "tokens=*" %%v in ('2^>^&1 %java_cmd% ^| find "version"') do echo %%v

或者,提供可以被 cmd /C 删除的显式引号,以便其余部分保持有效(转义这些引号,以便不必更改其他转义序列):

for /f "tokens=*" %%v in ('^"%java_cmd% 2^>^&1 ^| find "version"^"') do echo %%v

此外,我建议稍微修改一下整个脚本(参见所有 rem 备注):

@echo off
rem /* Begin the script with delayed expansion disabled as it might lead to problems
rem    during expansion of `for` meta-variables in case the values contain `!`: */
setlocal EnableExtensions DisableDelayedExpansion
rem // Use quoted `set` syntax (this requires the command extensions to be enabled):
set /A "count=0"
for /F "tokens=*" %%j in ('where java') do (
    set /A "count+=1"
    rem // Enable delayed expansion only when needed, like for variable `count` here:
    setlocal EnableDelayedExpansion
    rem /* (Mis-)use a `for` loop to make the value of `!count!` available even when
    rem    delayed expansion is disabled, hence after `endlocal` then: */
    for %%c in (!count!) do endlocal & set "JAVA[%%c]=%%j"
    rem /* Pass an argument over to the sub-routine rather than using variables
    rem    globally; in this situation this even does not need delayed expansion: */
    call :ECHO_JAVA "%%j"
)
endlocal
goto :EOF

:ECHO_JAVA
    rem /* Explicitly disable delayed expansion in the sub-routine, so you may call
    rem    it even from a code section with delayed expansion enabled: */
    setlocal DisableDelayedExpansion
    rem /* Use the sub-routine argument (`%~1`) herein, with explicit control of
    rem    quotation (`~` removes potential quotes, then we explicitly add such): */
    for /F "tokens=*" %%v in ('2^>^&1 "%~1" -version ^| find "version"') do echo(%%v
    endlocal
    exit /B 0