105

我将如何用转义的双引号替换批处理文件参数中的所有双引号?这是我当前的批处理文件,它在字符串中扩展了它的所有命令行参数:

@echo off
call bash --verbose -c "g++-linux-4.1 %*"

然后它使用该字符串调用 Cygwin 的 bash,执行 Linux 交叉编译器。不幸的是,我将这些参数传递到我的批处理文件中:

"launch-linux-g++.bat" -ftemplate-depth-128 -O3 -finline-functions 
-Wno-inline -Wall  -DNDEBUG   -c 
-o "C:\Users\Me\Documents\Testing\SparseLib\bin\Win32\LinuxRelease\hello.o" 
"c:\Users\Me\Documents\Testing\SparseLib\SparseLib\hello.cpp"

传入的第一个路径周围的第一个引号过早地结束了传递给 GCC 的字符串,并将其余参数直接传递给 bash(这非常失败。)

我想如果我可以将参数连接成一个字符串然后转义引号它应该可以正常工作,但我很难确定如何做到这一点。有人知道吗?

4

6 回答 6

111

批处理脚本中的转义字符是^. 但是对于双引号字符串,双引号:

"string with an embedded "" character"
于 2009-02-18T17:31:53.803 回答
101

eplawless 自己的回答简单有效地解决了他的具体问题:它将"整个参数列表中的所有实例替换为\",这就是 Bash 需要在双引号字符串中使用双引号来表示的方式。

要普遍回答如何使用Windows 命令行解释器在双引号字符串中转义双引号的问题(无论是在命令行上 - 通常仍被错误地称为“DOS 提示符” - 还是在批处理文件中):cmd.exe请参阅底部以了解PowerShell

tl;博士

答案取决于您调用的程序

  • 将参数传递给(另一个)批处理文件必须使用"",并且可以与使用Microsoft的 C/C++/.NET 编译器接受)创建的应用程序一起使用,在 Windows 上包括 Python、Node.jsPowerShell (核心)7+ 的CLI()但不是 Windows PowerShell 的():""\"pwshpowershell.exe

    • 例子:foo.bat "We had 3"" of rain."
  • 以下内容仅适用于定位批处理文件

    • ""是让命令解释器 ( cmd.exe) 将整个双引号字符串视为单个参数的唯一方法(尽管如果您只是将所有参数传递给另一个程序,%*使用

    • 然而,遗憾的是,不仅保留了封闭的双引号(像往常一样),而且双引号也保留了,因此获得预期的字符串是一个两步过程;例如,假设双引号字符串作为第一个参数传递,%1

      • set "str=%~1"删除封闭的双引号;set "str=%str:""="%"然后将双引号转换为单引号。
        请务必在分配部分周围使用封闭的双引号,以防止对值进行不必要的解释。
  • \"是许多其他程序(例如,Ruby、Perl、PHP 以及使用 CommandLineToArgvWindows API 函数解析其命令行参数的程序)所必需的 - 作为唯一选项,但它使用 fromcmd.exe并不健壮和安全

    • \"是许多可执行文件和解释器需要的——包括 Windows PowerShell——当从外部传递字符串时,在命令行上——或者,在微软的编译器的情况下,支持作为替代方案 ""——不过,最终取决于目标程序解析参数列表。
      • 例子:foo.exe "We had 3\" of rain."
    • 但是,使用\"can中断调用并至少假设会导致不必要的、任意执行命令和/或输入/输出重定向
      • 以下字符存在此风险:& | < >
      • 例如,以下结果会导致ver命令的意外执行;请参阅下面的进一步解释和解决方法的下一个要点:
        • foo.exe "3\" of snow" "& ver."
    • 对于调用Windows PowerShell CLIpowershell.exe和是强大但有限的替代方法\"""^""请参阅下面的“调用 PowerShell 的 CLI ...”部分)。
  • 如果你必须使用\"from cmd.exe,只有 3 种安全的方法 fromcmd.exe,但是很麻烦TS 致敬以寻求帮助。

    • 在批处理文件中使用(可能是选择性的)延迟变量扩展,您可以将文字存储\"变量中并使用语法在"..."字符串中引用该变量!var!- 请参阅T S 的有用答案

      • 上述方法尽管很麻烦,但其优点是您可以有条不紊地应用它,并且它可以在任何输入下稳健地工作。
    • 只有使用 LITERAL 字符串 - 不涉及 VARIABLES 的字符串 - 你会得到类似的有条不紊的方法: categorically ^-escape all cmd.exe metacharacters: " & | < >和 - 如果你还想抑制变量扩展 - %
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • 否则,您必须根据识别字符串的哪些部分由于被误解cmd.exe\"为关闭分隔符而被视为未引用来制定您的字符串:

      • 在包含 shell 元字符 的文字^部分中: -转义它们;使用上面的例子,它&必须是^-转义的:
        foo.exe "3\" of snow" "^& ver."

      • 带有%...%-style 变量引用的部分中:确保将cmd.exe它们视为"..."字符串的一部分,并且变量值本身没有嵌入的不平衡引号 -这甚至并不总是可能的


背景

注意:这是基于我自己的实验。如果我错了,请告诉我。

类 POSIX 的 shell,例如类 Unix 系统上的 Bash,在将参数单独传递给目标程序之前对参数列表(字符串)进行标记:在其他扩展中,它们将参数列表拆分为单个单词(分词)并从结果词(引号删除)。目标程序将收到一单独的逐字参数,即删除了句法引号

相比之下,Windows 命令解释器显然不会对参数列表进行标记,而只是传递包含所有参数的单个字符串- 包括引用字符。- 到目标程序。
但是,在将单个字符串传递给目标程序之前会进行一些^预处理:转义字符。双引号之外的字符串被删除(它们转义以下字符。),变量引用(例如,%USERNAME%)首先被插值

因此,与 Unix 不同,目标程序负责解析参数字符串并将其分解为去掉引号的单个参数。因此,不同的程序可能需要不同的转义方法,并且没有单一的转义机制可以保证适用于所有程序- https://stackoverflow.com/a/4094897/45375 包含关于 Windows 命令行解析的无政府状态的优秀背景.

在实践中,\"这是非常常见的,但不是安全的cmd.exe,如上所述:

由于cmd.exe它本身不能识别\"转义的双引号,因此它可能会将命令行上的后续标记误解为未引用,并可能将它们解释为命令和/或输入/输出重定向
简而言之:问题浮出水面,如果以下任何字符跟随一个开口或不平衡 \"& | < > ; 例如:

foo.exe "3\" of snow" "& ver."

cmd.exe看到以下标记,这是由于误解\"为常规双引号引起的:

  • "3\"
  • of
  • snow" "
  • 休息:& ver.

由于cmd.exe认为那& ver.unquoted,它将其解释为&(命令序列运算符),后跟要执行的命令的名称(ver.-.被忽略;ver报告cmd.exe的版本信息)。
整体效果是:

  • 首先,仅使用前3 个标记foo.exe调用。
  • 然后,执行命令ver

即使在意外命令没有伤害的情况下,您的整体命令也不会按设计工作,因为并非所有参数都传递给它。

许多编译器/解释器只识别\"- 例如,GNU C/C++ 编译器、Perl、Ruby、PHP,以及使用 CommandLineToArgvWindows API 函数解析其命令行参数的程序 - 对他们来说,没有简单的解决方案问题。
本质上,您必须事先知道命令行的哪些部分被误解为未引用,并有选择地^转义这些部分中的所有实例& | < >

相比之下,使用""is SAFE,但遗憾的是仅支持基于 Microsoft 编译器的可执行文件和批处理文件(在批处理文件的情况下,具有上面讨论的怪癖),值得注意的是不包括PowerShell - 请参阅下一节。


cmd.exe从类似 POSIX 的 shell调用 PowerShell 的 CLI :

注意:有关如何在PowerShell中处理引用,请参阅底部部分。

从外部调用时- 例如, from cmd.exe,无论是从命令行还是批处理文件:

  • PowerShell [Core] v6+现在可以正确识别""(除了\"),它既可以安全使用又可以保留空白

    • pwsh -c " ""a & c"".length "不会破坏并正确产生6
  • Windows PowerShell(最新和最终版本为 5.1 的旧版) 识别or \"""",后者是 , 形式中可靠的选择cmd.exe"^"""(即使内部PowerShell 使用`双引号字符串中的转义字符并且也接受""- 请参阅底部),如下所述:

/ 批处理文件调用Windows PowerShell : cmd.exe

  • "" break,因为它基本上不受支持:

    • powershell -c " ""ab c"".length "-> 错误“字符串缺少终止符”
  • \"原则上工作""" ,但不安全

    • powershell -c " \"ab c\".length "按预期工作:它输出5(注意2 个空格)
    • 但这并不安全,因为cmd.exe元字符会破坏命令,除非转义:
      powershell -c " \"a& c\".length " breaks,由于&,必须将其转义为^&
  • \""安全的,但规范化内部空白,这可能是不希望的:

  • powershell -c " \""a& c\"".length "输出4(!),因为 2 个空格被归一化为 1。

  • "^""特别是Windows PowerShell的最佳选择,它既安全又保留空白,但对于 PowerShell Core(在 Windows 上),它与 相同\"",即空白规范化(如上所述,只需""在此处使用)。归功于 Venryx发现了这种方法。

    • powershell -c " "^""a& c"^"".length " 作品:不会破坏 - 尽管&-输出5,即正确保留的空白。

    • PowerShell Core :pwsh -c " "^""a& c"^"".length " 有效,但输出4,即标准化空白,就像\""这样。

类 Unix 平台(Linux、macOS)上,当从类 POSIX 的 shell 调用PowerShell [Core]的 CLI 时,pwsh例如bash

必须使用\",但是它既安全又保留空白

$ pwsh -c " \"a&  c|\".length" # OK: 5

相关信息

  • ^只能用作不带引号的字符串中的转义字符- 在双引号字符串中,特殊^并被视为文字。

    • CAVEAT传递给语句的 in 参数的使用^call被破坏(这适用于以下两种用法call:调用另一个批处理文件或二进制文件,以及调用同一批处理文件中的子例程):
      • ^双引号中的实例被莫名其妙地加倍,改变了传递的值:例如,如果变量%v%包含文字值a^b,则将(!)call :foo "%v%"分配给子例程中的(第一个参数)。"a^^b"%1:foo
      • 不带引号^with使用完全call破坏,因为^它不能再用于转义特殊字符:例如,call foo.cmd a^&b悄悄地中断(而不是传递文字a&b就像foo.cmd没有 的情况一样call) -foo.cmd甚至从未调用(!),至少在 Windows 上7.
  • 不幸的是,转义文字%是一种特殊情况,它需要不同的语法,具体取决于字符串是在命令行还是在批处理文件中指定的;见https://stackoverflow.com/a/31420292/45375

    • 简而言之:在批处理文件中,使用%%. 在命令行中,%不能进行转义,但如果将 a放在不带引号的字符串(例如, )^的开头、结尾或变量名内部,可以防止变量扩展(插值);命令行上不属于变量引用的实例被视为文字(例如,)。echo %^foo%%100%
  • 通常,要安全地使用可能包含空格和特殊字符的变量值

    • 赋值将变量名和值括在一引号中;例如,set "v=a & b"将文字值分配a & b给变量%v%(相比之下,set v="a & b"会使双引号成为值的一部分)。将文字%实例转义为%%(仅适用于批处理文件 - 见上文)。
    • 参考双引号变量引用以确保它们的值没有被插值;例如,echo "%v%"不会对 的值%v%进行插值和打印"a & b"(但请注意,双引号也总是会打印)。相比之下,echo %v%将文字传递aecho,解释&为命令序列运算符,因此尝试执行名为 的命令b
      还要注意上面的警告重用^withcall语句。
      • 外部程序通常负责删除参数周围的双引号,但如前所述,在批处理文件中您必须自己做(例如,%~1从第一个参数中删除双引号),遗憾的是,没有直接的我知道在没有封闭双引号的情况下echo忠实地打印变量值的方式。
        • Neil提供了一个for基于 - 的解决方法,只要该值没有嵌入双引号就可以使用;例如:
          set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe引号识别为字符串分隔符 ( '...') - 它们被视为文字,通常不能用于分隔带有嵌入空格的字符串;同样,紧靠单引号的标记和介于两者之间的任何标记都被视为未引用cmd.exe并相应地解释。

    • 然而,鉴于目标程序最终会执行自己的参数解析,一些程序(例如 Ruby)即使在 Windows 上也能识别单引号字符串。相比之下,C/C++ 可执行文件和 Perl无法识别它们。
      但是,即使目标程序支持,也不建议使用单引号字符串,因为它们的内容不受cmd.exe.

PowerShell中引用:

Windows PowerShell是一个比 PowerShell 高级得多的 shell cmd.exe,它多年来一直是 Windows 的一部分(PowerShell Core也将 PowerShell 体验带到了 macOS 和 Linux)。

PowerShell在引用方面始终在内部工作:

  • 在双引号字符串中,使用`"""转义双引号
  • 在单引号字符串中,用于''转义单引号

这适用于 PowerShell 命令行以及从PowerShell 中将参数传递给 PowerShell 脚本或函数时。

(如上所述,将转义的双引号从外部传递给 PowerShell需要\",或者更稳健地\""- 没有其他方法)。

遗憾的是,当从 PowerShell 调用外部程序时,您既需要适应 PowerShell 自己的引用规则,又需要为目标程序转义:

  • 此答案中也讨论和总结了这种有问题的行为;PowerShell Core 7.2.0-preview.5 中引入的实验性 功能 - 假设它成为官方功能 - 将至少为那些接受.PSNativeCommandArgumentPassing \"

引号内的引号字符串

考虑 string "3`" of rain",它在 PowerShell 内部转换为 literal 3" of rain

如果要将此字符串传递给外部程序,除了PowerShell 的;之外,还必须应用目标程序的转义。假设您想将字符串传递给 C 程序,该程序期望嵌入的双引号被转义为\"

foo.exe "3\`" of rain"

请注意- 让 PowerShell 快乐 - -让目标程序快乐 - 必须存在。`"\

相同的逻辑适用于调用批处理文件,其中""必须使用:

foo.bat "3`"`" of rain"

相比之下,引号字符串中嵌入引号根本不需要转义。

引号字符串中的引号不需要额外转义;考虑'2'' of snow',这是 PowerShell 的2' of snow.

foo.exe '2'' of snow'
foo.bat '2'' of snow'

PowerShell 在将单引号字符串传递给目标程序之前将其转换为双引号字符串。

但是,引号字符串中的引号,对于PowerShell不需要转义,但对于目标程序仍然需要转义:

foo.exe '3\" of rain'
foo.bat '3"" of rain'

PowerShell v3引入了魔术--%选项,称为停止解析符号,它通过将未解释的任何内容传递给目标程序来减轻一些痛苦,除了扩展的-stylecmd.exe环境变量引用(例如,%USERNAME%);例如:

foo.exe --% "3\" of rain" -u %USERNAME%

请注意,仅对目标程序(而不是 PowerShell as )转义嵌入式"as就足够了。\"\`"

但是,这种方法:

  • 不允许转义 %字符以避免环境变量扩展。
  • 禁止直接使用 PowerShell 变量和表达式;相反,命令行必须在第一步中构建在字符串变量中,然后Invoke-Expression在第二步中调用 with。

解决此问题的另一种解决方法* 是使用包含整个命令行cmd /c的单个参数调用 via :

cmd /c "foo.exe `"3\`" of rain`" -u $env:USERNAME"

因此,尽管 PowerShell 取得了许多进步,但在调用外部程序时并没有让转义变得更容易——相反。但是,它引入了对单引号字符串的支持。

如果您不介意安装第三方模块(由我编写),该Native模块(视窗:Install-Module Nativeie

# Simply prepend 'ie' to your external-program calls.
ie foo.exe '3" of rain' -u $env:USERNAME
于 2015-07-14T17:45:13.877 回答
24

谷歌最终想出了答案。批量替换字符串的语法是这样的:

set v_myvar=replace me
set v_myvar=%v_myvar:ace=icate%

这会产生“复制我”。我的脚本现在看起来像这样:

@echo off
set v_params=%*
set v_params=%v_params:"=\"%
call bash -c "g++-linux-4.1 %v_params%"

它替换了"with的所有实例,\"为 bash 正确转义。

于 2009-02-18T17:48:17.680 回答
10

作为mklement0 出色答案的补充:

几乎所有可执行文件都接受\"转义的". 然而,在 cmd 中的安全使用几乎只有使用 DELAYEDEXPANSION 才能实现。要显式地向某个进程
发送文字,请分配给环境变量,然后在需要传递引号时使用该变量。例子:"\"

SETLOCAL ENABLEDELAYEDEXPANSION
set q=\"
child "malicious argument!q!&whoami"

注意SETLOCAL ENABLEDELAYEDEXPANSION似乎只在批处理文件中起作用。要在交互式会话中获得 DELAYEDEXPANSION,请启动cmd /V:ON.

如果您的批处理文件不适用于 DELAYEDEXPANSION,您可以暂时启用它:

::region without DELAYEDEXPANSION

SETLOCAL ENABLEDELAYEDEXPANSION
::region with DELAYEDEXPANSION
set q=\"
echoarg.exe "ab !q! & echo danger"
ENDLOCAL

::region without DELAYEDEXPANSION

如果您想从包含转义引号的变量中传递动态内容,""您可以在展开时替换""\"

SETLOCAL ENABLEDELAYEDEXPANSION
foo.exe "danger & bar=region with !dynamic_content:""=\"! & danger"
ENDLOCAL

%...%这种替换对于样式扩展是不安全的!

OP bash -c "g++-linux-4.1 !v_params:"=\"!"的情况下是安全版本。


如果出于某种原因甚至暂时无法启用 DELAYEDEXPANSION,请继续阅读:

\"如果总是需要转义特殊字符,而不是有时需要转义,则在 cmd 中使用from 会更安全一些。(如果它是一致的,它不太可能忘记插入符号......)

为了实现这一点,在任何引用之前都带有插入符号 ( ^"),应该作为文字到达子进程的引用必须另外使用反斜杠 ( \^") 进行转义。所有shell 元字符也必须转义^,例如&=> ^&; |=> ^|; >=> ^>; 等等

例子:

child ^"malicious argument\^"^&whoami^"

资料来源:每个人都以错误的方式引用命令行参数,请参阅“更好的引用方法”


要传递动态内容,需要确保以下几点:
包含变量的命令部分必须被视为“引用” cmd.exe(如果变量可以包含引号,这是不可能的 -不要写%var:""=\"%)。为了实现这一点,"变量之前的最后一个和变量"之后的第一个不会被^转义。"不能转义这两者之间的 cmd 元字符。例子:

foo.exe ^"danger ^& bar=\"region with %dynamic_content% & danger\"^"

这是不安全的,如果%dynamic_content%可以包含不匹配的引号。

于 2017-05-24T23:19:05.407 回答
0

如果字符串已经在引号内,则使用另一个引号来取消其操作。

echo "Insert tablename(col1) Values('""val1""')" 
于 2020-08-20T08:35:47.253 回答
0

在 Windows 10 21H1。

.bat如果我想从批处理 ( ) 文件运行 Everything 应用程序,我"""在双引号内使用参数:

"C:\Program Files\Everything\Everything.exe" -search "<"""D:\My spaced folder""" | """Z:\My_non_spaced_folder"""> <*.jpg | *.jpeg | *.avi | *.mp4>"

希望能帮助到你。

于 2021-09-29T22:07:33.333 回答