“cmd /s”是用来做什么的?

38
The Windows命令提示符(cmd.exe)具有可选的/s参数,它修改了/c(运行特定命令,然后退出)或/k(运行特定命令,然后显示shell提示符)的行为。这个/s参数显然与某些古怪的引号处理有关。 文档很令人困惑,但据我所知,当您执行cmd /csomething,并且something包含引号时,默认情况下cmd有时会剥离这些引号,而/s告诉它保留它们。
我不理解的是什么情况下去掉引号会导致错误,因为这是唯一需要使用“/s”(“抑制默认的引号去除行为”)的时候。它仅在某些神秘条件下去除引号,其中之一是在“/c”后的第一个字符必须是引号。因此,它不会删除参数周围的引号;它要么删除您正在运行的EXE文件的路径周围的引号,要么删除整个命令行周围的引号(或可能在命令行的前半部分周围的引号,这将很奇怪)。
  • 如果EXE文件的路径已经被引用,例如cmd /c "c:\tools\foo.exe" arg1 arg2,则引号是不必要的,如果cmd想要删除它们,那就没问题了。(如果路径中有空格,则不会删除它们——这是另一条晦涩的规则。)我无法想象有任何理由来压制引号的移除,因此/s似乎是不必要的。
  • 如果整个命令行都被引用,例如cmd /c "foo.exe arg1 arg2",那么引号的移除似乎是必要的,因为系统上没有名为foo.exe arg1 arg2的EXE文件;因此使用/s退出引号移除似乎会导致问题出现。(实际上,它确实不会出问题:cmd /s /c "foo.exe arg1 arg2"可以正常工作。)

/s是否有什么微妙之处我没有理解?它什么时候是必要的?它什么时候会有所区别呢?


2
我认为@HarryJohnston提供的原因很有用。此外,这是一个关于使用Windows CMD shell脚本编程的编程问题,所以不确定为什么人们要将其投票关闭为“离题”。 - Ben
一个相关的问答。https://dev59.com/D3RC5IYBdhLWcg3wROxQ - barlop
根据 https://ss64.com/nt/cmd.html ,你关于 /s 如何工作的描述是不正确的。然而,没有任何得到赞同的答案似乎能够纠正你的描述,所以我不确定该怎么想。 - cowlinator
Windows 7 x64 中,它的工作方式就像始终使用 /s 一样。 - Andry
请将以下与编程有关的内容从英语翻译成中文。只返回翻译后的文本:提示:使用@作为命令启动字符,以避免引号去除:cmd.exe /c @"..." ... - Andry
显示剩余2条评论
3个回答

23

/S非常有用,因为它可以让你不必担心“引号嵌套”的问题。记住,/C参数的意思是“像在提示符下输入一样执行此命令,然后退出”。

因此,如果您有一个复杂的命令要传递给CMD.exe,您要么必须记住CMD的参数引用规则并正确转义所有引号,要么使用/S,这会触发一个特殊的非解析规则,“去掉第一个和最后一个"”,并将所有其他字符视为要执行的命令而不做任何更改。

使用 /S

  • 始终删除第一个最后一个"
  • 从不删除任何其他",也不解释转义字符。

不使用 /S

  • 一套复杂的规则(为了兼容性)
  • 有时删除引号,有时不删除

如果你想利用CMD shell的功能而不是直接调用另一个程序,你可以使用它。例如环境变量扩展、输出或输入重定向,或者使用CMD.exe内置命令。

示例:

使用shell内置命令:这将执行与在提示符下键入DEL /Q/S "%TMP%\TestFile"相同的操作:

CMD.exe /S /C " DEL /Q/S "%TMP%\TestFile" "

这会执行SomeCommand.exe命令,并将标准输出重定向到临时文件,将标准错误信息也发送到同一位置:

CMD.exe /S /C " "%UserProfile%\SomeCommand.exe" > "%TMP%\TestOutput.txt" 2>&1 "

那么 /S 给你什么额外的东西呢?主要是它让你不必担心引用引号。它还有助于在你不确定环境变量是否包含引号字符的情况下使用。只需说 /S 并在开头和结尾加上一个额外的引号。

略微相关:Bourne Shell 中的 $*。

一些背景

请记住,main() 的参数列表是 C-ism 和 Unix-ism。Unix/Linux shell(例如 Bourne Shell 等)解释命令行,取消引号,扩展通配符(如 *)为文件列表,并将参数列表传递给被调用的程序。

因此,如果您说:

$ vi *.txt

vi命令例如看到这些参数:

vi
a.txt
b.txt
c.txt
d.txt

这是因为Unix/Linux在内部基于“参数列表”操作。

Windows最初源自CP/M和VAX,不在内部使用此系统。对于操作系统来说,命令行只是一个字符的字符串。调用程序有责任解释命令行、扩展文件glob(*等)并处理取消引用的引用参数。

因此,由C期望的参数必须由C运行时库进行修改。操作系统仅提供一个带有参数的单个字符串,如果您的语言不是C(即使是),它可能不会被解释为按照shell规则引号引起来的空格分隔的参数,而是作为完全不同的东西。


在“一些背景”之前,我不明白你所说的正确性。请参见此处 http://pastebin.com/raw.php?i=t60teCTk 尝试 cmd /s /c ""c:\program files\replace.exe" > "c:\temp\my folder\a.a"" 现在删除 /s,它仍然有效。那么 /S 的意义是什么? - barlop
@barlop 我同意;据我所知,在这些情况下,/S 不会有任何影响,因为有两个以上的引号。如果你在 /C 后面跟着 "包含空格的可执行文件名" 然后没有其他引号,那么引号将被保留在文件名周围,除非你使用 /S。我添加了一个答案来扩展这个问题。 - Tim Goodman
作为一个简单的测试,如果你创建一个名为 batch file.bat 的文件,那么 cmd /C "batch file" 就可以工作(因为它保留了引号),但是 cmd /S /C "batch file" 失败了,因为它剥离了引号并尝试执行一个名为 batch 的命令,带有参数 file。但是,如果你改为使用 cmd /C "batch file" "quoted_arg",那么它会失败,因为第一个和最后一个引号被剥离了(无论是否使用 /S)。 - Tim Goodman
@TimGoodman,你的时机真是太棒了。我最近刚研究了一下/S,并且在很大程度上弄明白了它!https://www.dostips.com/forum/viewtopic.php?f=3&t=10516,不过还有这个问题需要解决!https://www.dostips.com/forum/viewtopic.php?f=3&t=10518 - barlop

17

这是一个说明它如何产生影响的例子。

假设你有两个可执行文件:c:\Program.exec:\Program Files\foo.exe

如果你说

cmd /c "c:\Program Files\foo"

你需要运行 foo.exe(不带参数),而如果你输入:

cmd /s /c "c:\Program Files\foo"

您将使用Files\foo作为参数运行Program.exe

(令人奇怪的是,在第一个示例中,如果foo.exe不存在,则会运行Program.exe。)

补充:如果您键入

 c:\Program Files\foo

在命令提示符下,您将运行Program.exe(与cmd /s /c发生的情况相同),而不是foo.exe(与仅使用cmd /c发生的情况)。因此,使用/s的一个原因是确保命令以与在命令提示符上键入时完全相同的方式进行解析。这可能更有可能在Michael Burr提到的场景中是可取的,在该场景中,cmd.exe是通过CreateProcess启动而不是从批处理文件或命令行本身启动。

也就是说,如果你输入:

CreateProcess("cmd.exe", "cmd /s /c \"" MY_COMMAND "\"", ...)

如果你正在从用户处接收命令行输入,或者作为库处理应用程序提供的命令行,则将精确解析字符串MY_COMMAND,就好像在命令提示符下键入它一样。这可能是一个不错的选择。例如,C运行时库中的system()函数可能是以这种方式实现的。


@Ben 但正如Harry所指出的,而我也注意到并同意,可以查看我对那个问题的评论和答案https://dev59.com/D3RC5IYBdhLWcg3wROxQ 在那个问题中,/S似乎是多余的。你能展示一个/S有用并执行功能的例子吗?事实上,对于cmd.exe /s /c "c:\program files\blah.exe.."它是无用的并且执行功能(何时会引用路径而不想保留空格?)。但在哪些情况下它是有用和功能的呢?在添加外部引号的情况下,/S没有任何作用。 - barlop
@Ben 这是一个外部引号的例子,请注意在这种情况下 /S 没有任何作用。试一试:http://pastebin.com/raw.php?i=t60teCTk cmd /s /c ""c:\program files\replace.exe" > "c:\temp\my folder\a.a"",仍然带有外部引号,但去掉了 /S,即 cmd /c ""c:\program files\replace.exe" > "c:\temp\my folder\a.a""。 - barlop
@barlop,/S 的作用是如果您事先不知道命令是否嵌入引号。如果命令行上恰好有两个引号,则默认情况下会与有恰好两个引号字符的情况不同处理,而与有更多或更少的情况不同处理。/S 使其被视为相同。这已经有文档记录了:只需在命令行上键入“help cmd”。 - Ben
Harry,你的评论非常好,我会在你的回答下引用它:“(在外层引号中添加/S)并不会造成任何损害,这意味着无论命令(在外层引号内)如何改变,代码都将以相同的方式工作。” 这很有道理。现在我明白了,cmd /? 中的 Case 1 仅供方便之用。 - barlop
@Ben /S带有外引号,这样可以确保它始终被处理相同吗?还是仅使用/S就可以确保它始终被处理相同?我想你的意思是前者,带有外引号的/S会。如果是这样的话,那么去掉/S,什么情况下它不会起作用呢? - barlop
显示剩余5条评论

3

除了一个特定的情况外,/S 实际上不会有任何区别。

cmd.exe 的帮助文档是准确的,但有点复杂:

如果指定了 /C 或 /K,则在开关之后的命令行的其余部分将被处理为命令行,其中使用以下逻辑来处理引号 (") 字符:

  1. 如果满足以下所有条件,则保留命令行中的引号字符:

    • 没有 /S 开关
    • 恰好两个引号字符
    • 在两个引号字符之间没有特殊字符,其中特殊字符是:&<>()@^|
    • 在两个引号字符之间有一个或多个空格字符
    • 两个引号字符之间的字符串是可执行文件的名称。
  2. 否则,旧的行为是查看第一个字符是否为引号字符,如果是,则删除前导字符并删除命令行中的最后一个引号字符,保留最后一个引号字符之后的任何文本。

我会总结如下:

正常行为: 如果在/K/C之后的命令行中,以引号开头,则该引号和最后一个引号都将被删除。(见下面的例外情况。)除此之外,不会删除任何引号。

例外情况: 如果在/K/C之后的命令行中,以引号开头,接着是可执行文件的名称,然后是另一个引号,并且这些是唯一的两个引号,而且文件名包含空格但不包含特殊字符,那么这些引号就不会被删除(即使根据上述规则通常会被删除)。

/S的唯一作用是覆盖这个例外情况,因此在这种情况下仍然会删除这两个引号字符。


如果你总是使用 /S,你可以忘记异常并只记住“正常”情况。缺点是,cmd.exe /S /C "file name with spaces.exe" argument1 将无法正常工作,除非添加额外的引号,而没有 /S,它本来可以工作... 直到你决定用 "argument1" 替换 argument1

1
太棒了,终于我能理解cmd /s了,谢谢。 - Badr Elmers

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