将命令的输出作为变量设置(使用管道)

64

你能使用管道将命令的输出重定向到变量中吗?

我尝试的并不多,因为我还没有想到要尝试什么,但我尝试了一种方法(有两种变化)...

例如:

echo Hello|set text=

没有起作用,以下也一样:

echo Hello | set text=

我知道你可以使用FOR命令很容易做到,但我认为使用管道看起来更“漂亮”。

如果你想知道的话,我提出这个问题并没有具体的原因,只是出于好奇,而且我找不到答案。


1
在这里检查dolphy的答案:https://dev59.com/gGw15IYBdhLWcg3w1_YN - 如果您真的不想使用for,可以使用临时文件。 但是,无法使用管道完成此操作。 - Vicky
1
这个问题在这里有详细描述:https://dev59.com/fWsy5IYBdhLWcg3w-i7h - Aacini
经过完善的管道、标准输出/错误重定向、多个可执行文件、输出存储和处理值作为数字以及其他操作,该程序非常有效。 - user257319
6个回答

56

你的方法行不通,原因有两个。

首先,你需要使用set /p text=来获取用户输入并设置变量。
其次,问题在于管道符。
管道符会启动两个异步的cmd.exe实例,当任务完成后这两个实例就会关闭。

这就是为什么看起来似乎变量未被设置,但一个小示例可以证明它们已被设置,只是后来丢失了。

set myVar=origin
echo Hello | (set /p myVar= & set myVar)
set myVar

输出

Hello
origin

替代方案:您可以使用FOR循环将值存入变量中,也可以使用临时文件。

for /f "delims=" %%A in ('echo hello') do set "var=%%A"
echo %var%
或者
>output.tmp echo Hello
>>output.tmp echo world

<output.tmp (
  set /p line1=
  set /p line2=
)
echo %line1%
echo %line2%

使用宏的替代方案:

您可以使用批处理宏,这有点像bash的等效物

@echo off

REM *** Get version string 
%$set% versionString="ver"
echo The version is %versionString[0]%

REM *** Get all drive letters
`%$set% driveLetters="wmic logicaldisk get name /value | findstr "Name""
call :ShowVariable driveLetters

该宏的定义可以在以下网址找到:
SO:使用MS批处理文件将程序输出分配给变量


3
那么,没有使用临时文件或"for"命令就无法避免这些限制了吗?即使没有,无论如何还是谢谢。 - BDM
2
顺便提一下,在我的测试中,似乎你只能在for语句中使用一个命令。你不能用&将更多的命令链接在一起。 - end-user
@end-user 您可以使用多个命令,但最好将它们与括号组合起来 for %%a in (a b c) do (echo %%a <NewLine>echo ----) - jeb
不,我的意思是在评估中。但事实证明,如果我转义&符号,我就可以使用它:for /f %%i in ('first command ^&^& second command') do echo result: %%i - end-user
你的tmp文件对我不起作用?它为我正确地将数据写入文件,但是它不能正确地将其读回变量中。我将我的命令更改为“wmic os get osarchitecture”。 - Zapnologica
显示剩余2条评论

15
缺乏类似Linux的反引号功能是PowerShell之前的主要烦恼。使用for循环通过反引号并不舒适。因此,我们需要一种setvar myvar cmd-line命令。

在我的%path%中,我有一个包含多个二进制文件和批处理文件以解决Windows的缺陷的目录。

我编写的一个批处理是:

:: setvar varname cmd
:: Set VARNAME to the output of CMD
:: Triple escape pipes, eg:
:: setvar x  dir c:\ ^^^| sort 
:: -----------------------------

@echo off
SETLOCAL

:: Get command from argument 
for /F "tokens=1,*" %%a in ("%*") do set cmd=%%b

:: Get output and set var
for /F "usebackq delims=" %%a in (`%cmd%`) do (
     ENDLOCAL
     set %1=%%a
)

:: Show results 
SETLOCAL EnableDelayedExpansion
echo %1=!%1! 

所以在你的情况下,你需要输入:

> setvar text echo Hello
text=Hello 

脚本会告知您结果,这意味着您可以:
> echo text var is now %text%
text var is now Hello 

你可以使用任何命令:

> setvar text FIND "Jones" names.txt

如果您想将命令管道到某个变量中,但该命令本身包含一个管道符号怎么办?请使用三个脱字符号进行转义:^^^|:
> setvar text dir c:\ ^^^| find "Win"

在那里,usebackq delims=部分是做什么用的? - Richard Le Mesurier
1
@RichardLeMesurier: usebackq 指定使用类似 Linux 的语义,其中反引号字符串被执行为命令,并将输出解析到 for 变量中(这里是 %%a)。delims=x 指定要在解析中使用的分隔符(这里没有分隔符)。 - antonio
感谢澄清。我在构建工具中成功地使用了这个模式。这个答案应该被接受。 - Richard Le Mesurier

10

我对互联网上缺乏我认为的最佳答案感到有点惊讶。多年来,我一直在努力寻找答案。虽然许多在线答案都接近于正确,但没有一个真正回答了这个问题。真正的答案是:

(cmd & echo.) >2 & (set /p =)<2

"秘密酱"在于"echo."发送CR/LF(ENTER/new line/0x0D0A),这是"紧密保护的珍贵秘密"。否则,在这里我正在将第一个命令的输出重定向到标准错误流。然后将标准错误流重定向到"set /p ="命令的标准输入流。

例如:

(echo foo & echo.) >2 & (set /p bar=)<2


6
很抱歉打破你的梦想,但这并不是通过 STDERR 进行操作,而是通过名为“2”的文件进行操作(使用“dir”查看)。 - Stephan
哦,谢谢Stephan。看来我仍然不知道是否可以在没有文件作为中间件的情况下将一个命令的输出流式传输到另一个命令的输入,除了罕见的管道到more。在Windows和DOS中,似乎more是少数几个可以将输入导向它的命令之一,这似乎很奇怪。经过十多年的努力,我还没有看到任何人对此做出令人满意的解释。 - Charles
1
还有几个命令可以通过管道接受输入:more,sort,find,findstr,clip,choice,date,time,diskpart,wmic,schtask,pause以及(未经验证但可能是的):ftp,telnet,ssh和可能还有其他一些。 - Stephan
非常有趣。谢谢你分享,Stephan。 - Charles
1
你已经为我节省了未来10-20年寻找这个解决方案的时间!!! - Alexander
我建议在这个回答中添加一个更新,内容是:(cmd & echo.) >temp & (set /p =)<temp & del temp - MatrixRonny

6

你可以将输出设置为临时文件,然后从文件中读取数据,之后可以删除临时文件。

echo %date%>temp.txt
set /p myVarDate= < temp.txt
echo Date is %myVarDate%
del temp.txt

在这个变量中,myVarDate 包含指令的输出结果。

6
已经有3个旧回答提到了tempfile。我看不出你的回答还有什么进一步的好处了? - jeb
1
它符合我的搜索,所以+1 - user1855805

5

这不使用管道,但需要一个单一的临时文件。
我用它来将简化的时间戳放入一个低技术的每日维护批处理文件中。

我们已经将系统时间格式化为HHmm(10:45 PM为2245)
我将维护例程的输出定向到带有$DATE%@%TIME%时间戳的日志文件中;
...但%TIME%是一个很长的丑陋字符串(例如,down to the hundredths of a sec为224513.56)

解决方案概述:
1. 使用重定向(">")将命令"TIME /T"每次发送到%TEMP%目录中的一个临时文件中进行覆盖
2. 然后将该临时文件用作设置新变量的输入(我称之为NOW)
3. 将

echo $DATE%@%TIME% blah-blah-blah >> %logfile%
      替换为
echo $DATE%@%NOW% blah-blah-blah >> %logfile%


====输出的差异:
之前:
SUCCESSFUL TIMESYNCH 29Dec14@222552.30
之后:
SUCCESSFUL TIMESYNCH 29Dec14@2252


实际代码:

TIME /T > %TEMP%\DailyTemp.txt
SET /p NOW=<%TEMP%\DailyTemp.txt
echo $DATE%@%NOW% blah-blah-blah >> %logfile%


后续影响:
之后只会剩下添加的日志文件和不断被覆盖的临时文件。如果临时文件被删除,它将根据需要重新创建。


这实际上是我尝试传递一个复杂的git命令时唯一有效的解决方案,例如 git log --pretty=format:%ad_commit_%h -n 1 --date=short - Kenn Sebesta
简单而有效 - Michael Zlatkovsky - Microsoft

3
在批处理文件中,我通常会在临时目录下创建一个文件,并将程序输出追加到该文件中,然后调用它,并使用变量名称来设置该变量。就像这样:
:: Create a set_var.cmd file containing: set %1=
set /p="set %%1="<nul>"%temp%\set_var.cmd"

:: Append output from a command
ipconfig | find "IPv4" >> "%temp%\set_var.cmd"
call "%temp%\set_var.cmd" IPAddress
echo %IPAddress%

1
相当熟练。通常来说,避免使用临时文件是不错的选择。 - zanlok

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