在批处理脚本中转义双引号


91

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

@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(这很失败)。

我想如果可以将参数连接成单个字符串,然后转义引号就可以了,但我很难确定如何执行此操作。有人知道吗?

Answers:


103

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

"string with an embedded "" character"

5
加倍引号并没有为我工作,但工作^如飞。
davenpcj

25
^是仅在未加引号的字符串中的转义字符;用双引号括起来的字符串,将其视为文字。与Unix(类似于POSIX)外壳程序不同,cmd.exe它没有对双引号字符串内的双引号提供标准化的外壳处理,并且解释由所调用的程序决定(在下一条注释中续)。
mklement0

9
(接上一条评论)在实践中,大多数可执行文件/脚本解释器都采用C约定的预期"字符。可以\"在双引号字符串内进行转义(至少适用于:C / C ++,Python,Perl,Ruby)。相比之下,""只承认在少数情况下:在传递给批处理文件的参数,"" 公认的嵌入式双引号,而是原样保留在相应的%<n>参数,即使去掉后封闭用双引号%~<n>。Python 亲切地认识到"",可以作为Python 的替代方案\"
mklement0 2015年

89

eplawless自己的答案可以简单有效地解决他的特定问题:它将替换"整个参数列表中的所有实例\",这就是Bash要求在双引号字符串中使用双引号表示的原因。

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

tl; dr

  • 必须使用""合格的字符串时,以(另)一个批处理文件,您可以使用""与创建应用微软的C / C ++ / NET编译器。(它接受\"),它在Windows 包括Python和Node.js的

    • 例: foo.bat "We had 3"" of rain."

    • 以下内容仅适用于批处理文件:

      • ""这是使命令解释器(cmd.exe)将整个双引号字符串视为单个参数的唯一方法

      • 但是,令人遗憾的是,不仅保留了双引号(照常),而且保留了转义的双引号,因此,获得所需的字符串是一个两步过程;例如,假设将双引号字符串作为第一个参数传递,%1

      • set "str=%~1"删除封闭的双引号;set "str=%str:""="%"然后将双精度双引号转换为单引号。
        确保在赋值部分周围使用双引号,以防止不必要地解释这些值。

  • \"是许多其他程序(例如Ruby,Perl甚至Microsoft自己的Windows PowerShell(!))必需的 -唯一的选择,但是它的使用并不安全

    • \"从外部传递字符串时,许多可执行文件和解释器都需要 -包括Windows PowerShell- 或(对于Microsoft的编译器而言)是替代的支持 -最终,由目标程序来解析参数列表。 ""
      • 例: foo.exe "We had 3\" of rain."
    • 但是,使用\"CAN可能会导致不必要的命令任意执行和/或输入/输出重定向
      • 以下字符存在此风险: & | < >
      • 例如,以下结果导致ver命令的意外执行;有关说明,请参见下文,有关变通方法,请参见下一个要点:
        • foo.exe "3\" of snow" "& ver."
    • 对于Windows PowerShell\"""^""是健壮的,但替代品有限(请参见下面的“调用PowerShell的CLI ...”部分)。
  • 如果您必须使用\",则只有3种安全的方法,但是这非常麻烦TS求助。

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

      • 上述方法,尽管是繁琐的,有可以应用它的优势有条不紊,它的工作原理强劲,与任一输入。
    • 仅使用LITERAL字符串-不涉及变量的字符串-您会得到一种类似的系统方法:类别- ^转义所有 cmd.exe元字符: " & | < > -如果您也想抑制变量扩展- %
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • 否则,您必须根据识别错误解释cmd.exe\"为结束定界符认为字符串的哪些部分未被引用的公式来制定字符串

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

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

有关背景信息,请继续阅读。


背景

注意:这是基于我自己的实验。让我知道我是否错了。

类似于POSIX的外壳(例如,类似Unix的系统上的Bash )在将参数分别传递到目标程序之前将参数列表(字符串)标记化:在其他扩展中,它们将参数列表拆分为单个单词(单词拆分),并从结果单词(引号删除)。目标程序被切换的阵列个别参数,与句法引号除去

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

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

实际上,这\"是很常见的,但不是安全的,如上所述:

由于cmd.exe自身无法识别\"转义的双引号,因此它可能会将命令行上的后续标记误解为未引号,并有可能将其解释为命令和/或输入/输出重定向
概括地说:这个问题面,如果以下任意字符的遵循开口或不平衡 \"& | < > ; 例如:

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

cmd.exe看到以下由错误解释\"为常规双引号引起的标记:

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

由于cmd.exe认为& ver.不带引号的,它解释为&(命令排序操作),然后执行的命令的名称(ver.-将.被忽略; ver报告cmd.exe的版本信息)。
总体效果是:

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

即使在意外命令无害的情况下,由于并非所有参数都传递给您,所以您的总体命令也不会按设计工作。

许多编译器/解释器只能识别\" -例如,从中调用GNU C / C ++编译器,Python,Perl,Ruby,甚至是Microsoft自己的Windows PowerShell,cmd.exe并且,除了(有限制)使用的Windows PowerShell之外\"",因为它们没有简单的解决方案这个问题。
本质上,您必须事先知道命令行的哪些部分被误解为未引用,并有选择地- ^逃避& | < >这些部分中的所有实例。

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


cmd.exe或类似于POSIX的外壳调用PowerShell的CLI :

注意:有关 PowerShell中如何处理引用的信息,请参见底部。

从外部调用时(例如,cmd.exe从命令行或批处理文件从):

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

    • pwsh -c " ""a & c"".length " 不会破裂并正确屈服 6
  • Windows PowerShell中(传统版,其最新版本为5.1) 承认只有 \"同时在windows上也"""与更强大的\""/"^""(即使内部 PowerShell使用`在双引号字符串转义字符,也接受""-见底部):

/批处理文件调用Windows PowerShellcmd.exe

  • "" 中断,因为从根本上来说,它不受支持:

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

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

    • powershell -c " \""a& c\"".length "输出4(!),因为2个空格被标准化为1。
  • "^""是专门用于Windows PowerShell的最佳选择,因为Windows PowerShell既安全又保留空白,但是对于PowerShell Core(在Windows上),它与\""空白标准化相同。幸得Venryx发现此方法。

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

    • PowerShell的核心pwsh -c " "^""a& c"^"".length " 工作,但输出4,即规格化的空白,为\""做。

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

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

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

相关信息

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

    • CAVEAT传递给该语句^in参数的使用call已损坏(这适用于以下两种用法call:调用另一个批处理文件或二进制文件,并在同一批处理文件中调用子例程):
      • ^双引号括起来的实例会莫名其妙地加倍,从而改变了传递的值:例如,如果变量%v%包含文字值a^b,则将(!)call :foo "%v%"赋给subroutine中的(第一个参数)。"a^^b"%1:foo
      • ^with的无引号使用call被完全破坏,因为^它不再可用于转义特殊字符:例如,至少在Windows上,甚至从未调用过call foo.cmd a^&b安静的中断(而不是传递文字(如没有的情况))-甚至从未被调用(!)。 7a&bfoo.cmdcallfoo.cmd
  • 逸出一个文字%是一种特殊情况下,不幸的是,这需要根据是否在指定的字符串不同句法的命令行一个批处理文件内 ; 参见https://stackoverflow.com/a/31420292/45375

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

    • 分配包围两个变量的名称和在该值单一对双引号的 ; 例如,set "v=a & b"将文字值分配a & b给变量%v%(相比之下,set v="a & b"将使双引号成为值的一部分)。将文字%实例转义为%%(仅在批处理文件中有效-参见上文)。
    • Reference双引号变量引用,以确保其值未插值;例如,echo "%v%"不对值%v%进行插值和打印"a & b"(但请注意,双引号也总是打印)。相反,echo %v%将文字传递aecho,将其解释&为命令排序操作符,因此尝试执行名为的命令b
      还要注意以上声明^call语句的重用。
    • 外部程序通常负责删除参数周围的双引号,但是,如前所述,在批处理文件中,您必须自己进行操作(例如,%~1从第一个参数中删除双引号),可悲的是,没有直接我所知道的echo一种忠实地打印变量值而不用双引号引起来的方式
      • Neil提供了一个for基于方法的解决方法,只要该值没有嵌入的双引号即可;例如:
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe不能识别单个 -quotes作为字符串分隔符-它们被视为文字和一般不能被用来与嵌入空白界定字符串; 同样,紧跟着单引号和它们之间的任何标记的标记被视为未标记,cmd.exe并据此进行解释。

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

PowerShell中引用:

Windows PowerShell是比Windows更高级的外壳程序cmd.exe,并且它已经成为Windows的一部分多年了(PowerShell Core也将PowerShell的经验也带到了macOS和Linux中)。

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

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

这在PowerShell命令行上以及从PowerShell 内部将参数传递给PowerShell脚本或函数时都有效

(如上所述,将转义的双引号从外部传递到PowerShell 需要,\"或者更可靠地,\""-其他无效)。

可悲的是,当从PowerShell 调用外部程序时,您既需要适应PowerShell自己的报价规则,需要针对目标程序进行转义:

这个有问题的行为也在此答案中进行了讨论和总结

-quotes内 -quoted字符串

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

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

foo.exe "3\`" of rain"

注意如何 `" -使PowerShell的幸福- \-使目标程序高兴-必须存在。

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

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

相比之下,嵌入在-quotes -quoted字符串需要在所有没有逃脱。

内-quotes -quoted字符串不会需要额外的逃逸; 考虑'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的推出了魔法--%选项,叫做停止解析符号,这减轻一些痛苦,通过传递东西后,不解释到目标程序,保存cmd.exe风格的环境变量引用(例如,%USERNAME%),这扩大; 例如:

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

注意如何逃避嵌入"作为\"目标程序只(不也为PowerShell的作为\`")就足够了。

但是,这种方法:

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

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

我不知道,如果它基本上可以在Windows世界永远开关,放开的Unix模式的外壳做的所有标记化和引用的去除可以预见在前面不论目标程序,然后调用目标程序通过传递所产生的令牌。


不错的描述!但是... Literal use of ^ in double-quoted strings can, be problematic when applying ~ ...不是真的。代字号仅在出现时删除外部引号。插入标记的松动是处理本身的问题。通常,a set "param=%~1"可以解决此问题。
2015年

谢谢@jeb,问题确实不是特定于使用的~-我已经更新了答案以反映我的新理解-如果您发现问题,请发表评论。您是否有解释,^当您将参数传递给call语句时会自动将其加倍?
mklement0

3
我只能猜测,MS的某人认为它很棒。在第二个解析阶段中将自动删除加倍的插入号。但这是一个很大的失败,因为它不能用引号引起来,并且可以有效地防止转义任何特殊字符。就像call echo cat^^&dog单独使用任何数量的
插入符号都无法解决一样

谢谢@jeb,我什至没有考虑过with 的无引号用法,正如您所指出的那样,它已被严重破坏。似乎在(正确地对进行了转义的单个目标中)目标命令()甚至从未被调用(!)-整个命令都会安静地失败。我已经相应地更新了答案。^callcall echo cat^&dog^&echo
mklement0 2015年

好答案。但是,我不建议您使用""Escape "而是始终\"使用它(有关在cmd中使用它的危险性较小的方法,请参见我的回答)。我不知道任何官方文件定义""为一个转义报价,但至少2提及\".NETVS。尽管记录错误,但Win32 API也遵循这些规则。
TS TS

23

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

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%"

它取代的所有实例"\",正确转义为庆典。


9

作为mklement0出色答案的补充:

几乎所有可执行文件都接受\"为转义符"。但是,几乎只能使用DELAYEDEXPANSION在cmd中安全使用。
要将文字显式发送"给某个进程,\"请在需要传递引号时将其分配给一个环境变量,然后使用该变量。例:

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内部使用会更安全一些。(如果保持一致,则不太可能忘记插入符号...)

为此,在任何引号之前加一个脱字符号(^"),应到达子进程的引号,因为文字必须另外加反斜杠(\^")进行转义。所有的 shell元字符也必须使用转义符^,例如&=> ^&|=> ^|; >=> ^>; 等等

例:

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

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


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

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

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


明白了,谢谢。是的,对所有元字符进行^转义绝对有效,并且可以有条不紊地应用(但显然,这是您所选择的身体部位的皇家痛苦)。我已经相应地更新了我的答案(并给了您荣誉)。
mklement0

@ mklement0谢谢!是的,真的很痛苦。这很烦人,而且忘记元字符仍然太容易了(因此我大多使用这种!q!方式)。注意:你的回答是你最后一次修改后小幅不一致:靠近你说上:“你不能^"”。稍后,您将使用它^"作为解决方法的一部分。也许您可以解释两种方式?(1)将所有元字符转义(更系统地)/(2)在“未引用”区域中选择性转义元字符(有时需要传递动态内容,例如foo.exe ^"danger ^& bar=\"%dynamic_content%\"^"-用这种方式为cmd引用变量)
TS

好点,谢谢-答案已更新。我也更清楚地指出MS编译器接受\"""。我已链接到您的答案,以获取更多涉及基于变量的方法。让我知道现在是否有意义。
mklement0

1
@ mklement0您的回答总是很有意义的:-)我只是想提出一些可能的改进建议。我也将示例添加%dynamic_content%到我的答案中。您是否认为它足够详细,还是需要解释更多?
TS

3
太好了,谢谢你让我知道。本地化的好主意setlocal delayedexpansion,但您应使用endlocal(无参数)结束该块。老实说,我的头开始旋转,看着你的吉斯特。我们确实在这里处理极端情况,我认为未来的读者会在我们的两个答案之间找到他们所需的一切。
mklement0

0

如果字符串已经在引号中,则使用另一个引号使它的操作无效。

echo "Insert tablename(col1) Values('""val1""')" 

-3

例如从批处理文件运行的虚幻引擎自动化工具-这对我有用

例如:-cmdline =“ -Messaging” -device = device -addcmdline =“-SessionId = session -SessionOwner ='owner'-SessionName ='Build'-dataProviderMode = local -LogCmds ='LogCommodity OFF'-execcmds ='自动化列表; runtests tests + separated + by + T1 + T2;退出'-run

希望这可以帮助某人,为我工作。


尝试将您所学到的概念传播给一个清晰的示例,而不是长期以来几乎没有相关内容复制到您的作品中。
run_the_race
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.