如何在批处理文件中将命令输出设置为变量

379

是否可以将批处理文件中语句的输出设置为变量,例如:

findstr testing > %VARIABLE%

echo %VARIABLE%

实际上,原始问题似乎是如何在Windows中将命令的结果存储到变量中? - Cristian Ciupitu
没有一个答案对我有用。我的命令是"openssl dgst -sha384 -binary %1% | openssl base64 -A",我希望字符串输出被存储在批处理文件/命令文件中的变量中。 - David Spector
比较低效的解决方案: @echo off openssl dgst -sha384 -binary %1% | openssl base64 -A > tmp set /p a= < tmp del tmp echo %a% - David Spector
编写一个Python脚本来完成这项任务? - Josja
9个回答

395
FOR /F "tokens=* USEBACKQ" %%F IN (`command`) DO (
SET var=%%F
)
ECHO %var%

我总是使用USEBACKQ,这样如果你有要插入的字符串或者一个很长的文件名,你就可以在不弄乱命令的情况下使用双引号。

现在如果你的输出会包含多行,你可以这样做:

SETLOCAL ENABLEDELAYEDEXPANSION
SET count=1
FOR /F "tokens=* USEBACKQ" %%F IN (`command`) DO (
  SET var!count!=%%F
  SET /a count=!count!+1
)
ECHO %var1%
ECHO %var2%
ECHO %var3%
ENDLOCAL

6
在Win7系统上对我有用,谢谢!(是的,已经使用了7年) 附注:不要忘记在命令中将%扩展为%%! - Dmitry Ilukhin
1
这个很好用...除非输出包含感叹号(!),因为在使用 ENABLEDELAYEDEXPANSION 时,它们将被解释为控制字符。 - JonathanDavidArndt
19
我使用^|代替|,就像这里描述的那样,对我有效。 - AST
1
请问有没有办法捕获command的退出代码?我确定它返回了一个非零代码,但是无论我在哪里尝试使用%ERRORLEVEL%(甚至在do块内部),它始终包含0。所以我猜测for结构的某个部分正在覆盖它。顺便说一句,为了做这么基本的事情需要写出如此疯狂的代码,真是难以置信。 - David Ferenczy Rogožan
4
这段代码非常聪明:SET var!count!=%%F 在批处理文件中,我以前从未见过动态变量名称。 - GregHNZ
显示剩余9条评论

118

一句话概括:

FOR /F "tokens=*" %%g IN ('your command') do (SET VAR=%%g)

命令输出将先设置为 %g,然后再设置为 VAR。

更多信息在这里:https://ss64.com/nt/for_cmd.html


33
我的程序批次没有使用 "%g",而是使用了 "%%g"。 - Santiago Villafuerte
11
是的,如果你是从批处理文件中运行,那么需要使用两个百分号(%%);但如果你想要在命令行自己运行它(例如:在将其放入批处理文件之前进行测试),则需要使用一个单独的百分号(%)。 - Brent Rittenhouse
1
我发现你的命令有点令人困惑。不知道*是否必要(否)。尽管如此,这个命令非常有帮助。 - tothphu
我收到了“%%g在此时是意外的”。 - ggonmar

105

我在那个叫做Interweb的地方找到了这个帖子。总结如下:

@echo off 
setlocal enableextensions 
for /f "tokens=*" %%a in ( 
'VER' 
) do ( 
set myvar=%%a 
) 
echo/%%myvar%%=%myvar% 
pause 
endlocal 

您也可以将命令输出重定向到临时文件,然后将该临时文件的内容放入您的变量中,如下所示。但是,它无法处理多行输入。

cmd > tmpFile 
set /p myvar= < tmpFile 
del tmpFile 

感谢Tom's Hardware论坛上的帖子。


8
底部方法的唯一缺点(在其他方面更为优越)是它无法处理多行输出。 - dgo
对于VER命令,我会选择另一种方式: for /f "tokens=2 delims=[,]" %%a in ('ver') do set version %%a echo %version% - Hardoman
2
你是“likesuchashereby”的第二个搜索结果 - https://imagebin.ca/v/4NPSlQFgifce - Shmuel Kamensky
2
你的第二个例子对我在编写批处理文件时检查chromedriver.exe版本与包含版本号的文本文件非常有帮助。我是一个老式的DOS恐龙,不知道set命令的开关。其他东西对我来说都是多余的。 - Bill Hileman

40
如果您不想将输出写入临时文件再读入变量,这段代码可以直接将命令结果存储到变量中:
FOR /F %i IN ('findstr testing') DO set VARIABLE=%i
echo %VARIABLE%

如果您想用双引号括起搜索字符串:

FOR /F %i IN ('findstr "testing"') DO set VARIABLE=%i

如果你想把这段代码存储在批处理文件中,需要添加一个额外的%符号:

FOR /F %%i IN ('findstr "testing"') DO set VARIABLE=%%i

一个有用的例子,用于计算目录中文件的数量并将其存储在变量中: (演示管道)
FOR /F %i IN ('dir /b /a-d "%cd%" ^| find /v /c "?"') DO set /a count=%i

请注意,在命令括号中使用单引号而不是双引号"或重音符号`。这是在for循环中比delims、tokens或usebackq更清洁的替代方法。

更新于2021年8月27日:

另一种方法是设置errorlevel变量,尽管许多人会劝阻在大型脚本或刚接触已安装操作系统变体的cmd风格时设置errorlevel

此方法适用于要存储的(返回)值适合32位整数的情况。

例如:计算目录中文件数量并将其存储在名为count的变量中:

(dir /b /a-d ^| find /v /c "?") | (set /p myVar=& cmd /c exit /b %myVar%)
set count=%errorlevel%

详细 - Win CMD 整数限制测试:

Win CMD 算术限制:2147483647(32 位整数)
堆栈溢出将在 -2147483648 继续计数, 并在达到 2147483647 后重新设置

REM See tests below:
cmd /c exit /b 2147483647
echo %errorlevel%
2147483647

cmd /c exit /b 2147483648
echo %errorlevel%
-2147483648

cmd /c exit /b 2147483649
echo %errorlevel%
-2147483647

上述方法可以修改为返回编码字符串以在父进程中解码(在32位限制内)。
第三个示例,虽然有限使用(因为变量是在子进程中设置的,而不是父进程),如下:
(dir /b /a-d ^| find /v /c "?") | (set /p myVar=& set myVar)

在这种情况下,myVar的值被设置为目录中文件的数量。
在Win 10 CMD上进行测试。

这对我没有起作用。 - knocte
你的Windows版本和构建号是多少? - Zimba
Windows 10 64位 - knocte
我已在Win 10 x64上进行了测试。哪段代码对您不起作用? - Zimba

31

读取文件的方法是...

set /P Variable=<File.txt

写入文件

@echo %DataToWrite%>File.txt

注意:在 <> 字符之前添加空格会导致变量末尾添加一个空格

要将内容添加到文件中(例如日志程序),首先创建一个名为 e.txt 的文件,里面只包含一个回车键。

set /P Data=<log0.log
set /P Ekey=<e.txt
@echo %Data%%Ekey%%NewData%>log0.txt

你的日志将会是这样的

Entry1
Entry2 

等等之类的

无论如何,有几个有用的东西


1
谢谢你的回答。顺便说一下,“set /P Variable=<File.txt”只读取文件的第一行。 - Deepscorn
your_command | set /P VAR= - Steve Mitchell

28

所有这些答案都非常接近我所需要的答案。这是对它们的进一步扩展。

在批处理文件中

如果你正在运行一个.bat文件,并且你想要一个单行命令来将类似jq -r ".Credentials.AccessKeyId" c:\temp\mfa-getCreds.json这样复杂的命令导出到一个名为AWS_ACCESS_KEY的变量中,那么你需要这个:

FOR /F "tokens=* USEBACKQ" %%g IN (`jq -r ".Credentials.AccessKeyId" c:\temp\mfa-getCreds.json`) do (SET "AWS_ACCESS_KEY=%%g")

在命令行中

如果您处于C:\提示符下,您需要一条单行命令来运行像jq -r ".Credentials.AccessKeyId" c:\temp\mfa-getCreds.json这样复杂的命令,并将其赋值给名为AWS_ACCESS_KEY的变量,则需要使用以下命令:

FOR /F "tokens=* USEBACKQ" %g IN (`jq -r ".Credentials.AccessKeyId" c:\temp\mfa-getCreds.json`) do (SET "AWS_ACCESS_KEY=%g")

解释

以上两个答案的唯一区别在于在命令行中,您使用单个%来表示变量。 在批处理文件中,您必须加倍百分号(%%)。

由于命令包括冒号,引号和括号,因此需要在选项中包含USEBACKQ行,以便您可以使用反引号指定要运行的命令,然后在其中使用各种有趣的字符。


1
好吧,显然有人遇到了我正在面对的问题,即使用批处理脚本从AWS cli管道传输JSON的奇妙之处。 - Trinidad

17

一些笔记和一些技巧。

将结果分配给变量的“官方”方式是使用 FOR /F,尽管在其他答案中也展示了如何使用临时文件。

对于命令处理,FOR 命令有两种形式,取决于是否使用 usebackq 选项。在下面的所有示例中,整个输出都被使用,而不会将其拆分。

FOR /f "tokens=* delims=" %%A in ('whoami') do @set "I-Am=%%A"
FOR /f "usebackq tokens=* delims=" %%A in (`whoami`) do @set "I-Am=%%A"

如果直接在控制台中使用:

FOR /f "tokens=* delims=" %A in ('whoami') do set "I-Am=%A"
FOR /f "usebackq tokens=* delims=" %A in (`whoami`) do set "I-Am=%A"

%%A是一个临时变量,仅在FOR命令上下文中可用,称为token。这两种形式在处理包含特定引号的参数时非常有用。它在其他语言或WMIC的REPL界面中特别有用。尽管在这两种情况下,表达式都可以用双引号括起来进行处理。

这里有一个使用Python的例子(可以将方括号中的表达式转移到单独的一行中,以便更容易阅读):

@echo off

for /f "tokens=* delims=" %%a in (
  '"python -c ""print("""Message from python""")"""'
) do (
    echo processed message from python: "%%a"
)

为在同一FOR块中使用分配的变量,还需检查DELAYED EXPANSION

还有一些技巧

为了避免编写FOR命令的所有参数,您可以使用MACRO将结果分配给变量:

@echo off

::::: ---- defining the assign macro ---- ::::::::
setlocal DisableDelayedExpansion
(set LF=^
%=EMPTY=%
)
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

::set argv=Empty
set assign=for /L %%n in (1 1 2) do ( %\n%
   if %%n==2 (%\n%
      setlocal enableDelayedExpansion%\n%
      for /F "tokens=1,2 delims=," %%A in ("!argv!") do (%\n%
         for /f "tokens=* delims=" %%# in ('%%~A') do endlocal^&set "%%~B=%%#" %\n%
      ) %\n%
   ) %\n%
) ^& set argv=,

::::: -------- ::::::::


:::EXAMPLE
%assign% "WHOAMI /LOGONID",result
echo %result%

宏的第一个参数是命令,第二个参数是我们想使用的变量名称,两个参数之间用,(逗号)分隔。尽管这只适用于简单直接的情况。

如果我们想为控制台创建类似的宏,可以使用DOSKEY

doskey assign=for /f "tokens=1,2 delims=," %a in ("$*") do @for /f "tokens=* delims=" %# in ('"%a"') do @set "%b=%#"
rem  -- example --
assign WHOAMI /LOGONID,my-id
echo %my-id%

DOSKEY接受双引号作为参数的包含符号,因此对于更简单的情况也很有用。

FOR也可以与管道一起使用,用于链接命令(虽然不适用于变量赋值)。

hostname |for /f "tokens=* delims=" %%# in ('more') do @(ping %%#)

可以通过宏来美化它:

@echo off
:: --- defining chain command macros ---
set "result-as-[arg]:=|for /f "tokens=* delims=" %%# in ('more') do @("
set "[arg]= %%#)"
:::  --------------------------  :::

::Example:
hostname %result-as-[arg]:% ping %[arg]%

为了完整起见,这里提供基于临时文件方法的宏(没有doskey定义,但也很容易实现。如果你使用的是SSD,速度不会太慢):

@echo off

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set "[[=>"#" 2>&1&set/p "&set "]]==<# & del /q # >nul 2>&1"
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

chcp %[[%code-page%]]%
echo ~~%code-page%~~

whoami %[[%its-me%]]%
echo ##%its-me%##

使用 /f 和另一个宏:

::::::::::::::::::::::::::::::::::::::::::::::::::
;;set "{{=for /f "tokens=* delims=" %%# in ('" &::
;;set "--=') do @set ""                        &::
;;set "}}==%%#""                               &::
::::::::::::::::::::::::::::::::::::::::::::::::::

:: --examples

::assigning ver output to %win-ver% variable
%{{% ver %--%win-ver%}}%
echo 3: %win-ver%


::assigning hostname output to %my-host% variable
%{{% hostname %--%my-host%}}%
echo 4: %my-host%

2
在大多数情况下,以您的变量名称命名临时文件可能是可接受的(因为您很可能使用有意义的变量名称...)。
在这里,我的变量名称是SSH_PAGEANT_AUTH_SOCK。
dir /w "\\.\pipe\\"|find "pageant" > %temp%\SSH_PAGEANT_AUTH_SOCK && set /P SSH_PAGEANT_AUTH_SOCK=<%temp%\SSH_PAGEANT_AUTH_SOCK

2
cd %windir%\system32\inetsrv

@echo off

for /F "tokens=* USEBACKQ" %%x in (      
        `appcmd list apppool /text:name`
       ) do (
            echo|set /p=  "%%x - " /text:name & appcmd.exe list apppool "%%x" /text:processModel.identityType
       )

echo %date% & echo %time%

pause

1
我看不出这如何解决问题。appcmd 不是标准程序,我也看不到你在哪里将程序输出分配给变量。最后,你需要更多的解释。 - jeb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接