Windows命令提示符:如何在一行命令中使用设置的变量

19

好的,我了解到可以使用&&&将多个命令组合成一行,但是看起来在同一行中声明的变量set实际上无法插值:

C:\Users\Andrew>set foo=hello, world!&& echo %foo%
%foo%

C:\Users\Andrew>echo %foo%
hello, world!

为什么我无法使其工作,是否有任何方法可以在一行中使其工作?

我需要一行代码的原因是,我正在使用的外部程序接受单个命令作为预运行钩子,并且当然,我需要运行多个命令。


预防性防御措施

  1. "hello, world!应该用双引号括起来!" 实际上,这样做似乎会将文字中的双引号存储在变量中,而我不想要这样,例如:

    C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
    %bar%
    
    C:\Users\Andrew>echo %bar%
    "hello, world!"
    
    "在 && 前应该有一个空格!" 实际上,这样做似乎会在变量中存储一个尾随空格,我不希望这样,例如:
  2.        foo = bar && baz;

  3. C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
    %bar%
    
    C:\Users\Andrew>echo %bar%
    "hello, world!"
    
  4. "两个都需要!" >:(

  5. C:\Users\Andrew>set mu="hello, world!" && echo %mu%
    %mu%
    
    C:\Users\Andrew>echo (%mu%)
    ("hello, world!" )
    
4个回答

18
你可以在同一行内完成它,但我建议使用类似于preHook.bat 的批处理文件。

作为一行命令。

set "mu=hello, world!" && call echo %^mu%

起初,您可以看到引号与您的不同。


原因如下:

set test1="quotes"
set "test2=no quotes" with comment

在第一个测试中,引号将成为test1变量的一部分,以及最后一个引号之后的所有字符。

在第二个测试中,它使用了SET命令的扩展语法。
因此,内容将是no quotes,因为只使用到最后一个引号之间的内容;其余的内容将被删除。

要回显变量内容,我使用call echo %^mu%,因为百分号扩展将在解析该行之前扩展它,而不是在任何命令执行之前。

但是call命令将在稍后执行,并重新启动解析器。当在命令行上使用时,解析器使用不同的扩展规则:空变量(在本例中为%^mu%)在该行中保持不变;但是,在下一个解析器阶段中,^符号将被移除。

在您的情况下,call echo %mu%也可以工作,但仅当mu始终为空时。如果在执行该行之前mu具有内容,则脱字符变体也有效。

有关解析器的更多信息,请参见SO:Windows命令解释器(CMD.EXE)如何解析脚本? 有关变量扩展的更多信息,请参见SO:变量扩展


2
@AndrewCheong 希望我的解释清楚易懂。 - jeb
1
很好的解释。我没有意识到set有这种替代语法,而且那个消失的插入符号技巧真的很聪明。非常感谢!(是的,我考虑过把所有东西都放在一个bat文件中,但出于心理/后勤原因,一行代码会更好。这就是“好了,大家只需将此行复制/粘贴到您的窗口中,然后瞧!”,和“每个人下载这个文件,在您的$PATH中放置它”,等等之间的区别。) - Andrew Cheong
1
@jeb 如果你的英语语法和你的批处理语法一样好就好了,哈哈... 我编辑了你的答案,使它更易读,但我不太确定我理解了 %^mu% 部分。我认为你的意思是使用 ^ 来防止第一阶段过早地评估 %mu%,并使用 call 在脱字符被移除但 set 命令已经通过时再次解析 echo 命令。这正确吗?谢谢。 - Andrew
1
@andrew 謝謝你改善我的答案,我從中學習到了很多。而且,你說得對。 - jeb
插入符号技巧在脚本中不起作用,但您可以使用以下方法:set "mu=hello, world!" && call echo %%mu%%(仅适用于脚本)。 - Vopel
显示剩余2条评论

8
尽管我接受了@jeb的答案,但最终我不得不走另一条路,因为我需要将命令输出导入管道,在使用call时会导致混乱的结果。实际上,call的文档本身就是这样说的:

不要在call中使用管道和重定向符号。

相反,受到@Blorgbeard提醒,我意识到可以通过启动子进程来解决,并使用cmd/v选项(我认为应该是小写而不是大写)。
C:\Users\Andrew>cmd /v /c "set foo=hello, world!&& echo !foo!"
hello, world!
/v 必须出现在 /c 之前,原因不明。在引号内,我能够将输出导向其他实用程序。对于那些选择这条路线的人,一个提示是:如果你发现自己需要在这些引号中使用引号,请完全避免使用它们,并尝试使用字符代码,例如 \x20 表示空格,\x22 表示双引号等等。
例如,这是我的问题的最终解决方案(警告:可能会导致眼睛流血):
C:\Users\Andrew>cmd /v /c "set source=C:\source& set target=C:\target& set archive=C:\archive& robocopy.exe !source! !target! /l /e /zb /xx /xl /fp /ns /nc /ndl /np /njh /njs | sed -e s/^^[\t\x20]\+// | sed -e /^^$/d | sed -e s/^!source:\=\\!// | sed -e s/.*/xcopy\x20\/Fvikrhyz\x20\x22!source:\=\\!^&\x22\x20\x22!archive:\=\\!^&\x22/"

5
由于在/c之后指定的任何内容都是/c本身的参数,而/v是cmd.exe的参数,因此需要先放置/v,所以出于某种原因,/v必须出现在/c之前。 - sactiw

5
请尝试以下方法:
cmd.exe /v /c "set foo=hello, world & echo !foo!"

参数/v启用了延迟变量扩展。这允许您在执行时间而不是解析时间(默认值)访问变量的值。打开此选项后,您可以通过编写!foo!而不是%foo%来实现。

除了传递/v之外,您还可以通过注册表永久性地打开延迟变量扩展,这显然会影响整个系统:

[HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)

谢谢,这让我想到了另一个解决方案,我很快就会发布。 - Andrew Cheong

1

我无法使用callcmd /v /c使IF命令正常工作,因此我想为那些可能需要的人提供另一种选项:使用FOR /F在单个命令中声明和使用变量。

例如:

FOR /F %i IN ('echo 123') DO (IF %i==123 echo done)
%i 是一个变量,其值由 IN 命令产生,本例中为 'echo 123'。

对于包含单引号或空格的值,请使用 "usebackq tokens=*" 标记将命令放在反引号中:
FOR /F "usebackq tokens=*" %i IN (`echo "hello, ' ' world!"`) DO (IF %i=="hello, ' ' world!" echo done)

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