代码之家  ›  专栏  ›  技术社区  ›  eplawless

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

  •  75
  • eplawless  · 技术社区  · 15 年前

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

    @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(这会发生惊人的失败)。

    我想象一下,如果我能将参数连接成一个字符串,然后转义引号,它会很好地工作,但是我很难确定如何做到这一点。有人知道吗?

    5 回复  |  直到 6 年前
        1
  •  86
  •   Salman A    15 年前

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

    "string with an embedded "" character"
    
        2
  •  64
  •   mklement0    6 年前

    eplawless's own answer 简单有效地解决了他的特定问题:它取代了所有 " 整个参数列表中的实例 \" ,这就是bash需要在双引号字符串中使用双引号来表示的方式。

    一般回答 如何在双引号字符串中转义双引号,使用 cmd.exe ,Windows命令行解释器(无论是在命令行上,还是在批处理文件中,通常仍错误地称为“DOS提示符”): 查看底部查看 动力壳 .

    DR :

    • 必须 使用 "" 传递字符串时 对(某) 批处理文件 你呢? 可以 使用 使用创建的应用程序 微软 的C/C++/.NET编译器 (哪个) 接受 ,在Windows上 包括python和node.js :

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

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

        • 是获取命令解释器的唯一方法( 命令提示符 )将整个双引号字符串视为 单一的 争论。

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

        • set "str=%~1" 删除括起来的双引号; set "str=%str:""="%" 然后将双引号转换为单引号。
          请确保在分配部分周围使用括起来的双引号,以防止对值进行不必要的解释。

    • 必修的 -作为唯一的选择-由许多其他程序 ,(例如,Ruby、Perl,甚至微软自己的PowerShell!!)但是 它的使用不安全 :

      • 很多可执行文件和解释程序 要求 -在传递字符串时包括Microsoft自己的PowerShell 从外面 -或者,在 微软的编译器,支持 作为替代 -但最终, 由目标程序来解析参数列表。
      • 例子: foo.exe "We had 3\" of rain."
      • 然而,使用 可能导致不必要的、任意的命令执行和/或输入/输出重定向 :
        • 以下字符表示此风险: & | < >
        • 例如,以下结果导致 ver 命令;有关解决方法的解释和下一个要点,请参阅下面的进一步内容:
          • foo.exe "3\" of snow" "& ver."
      • 为了 动力壳 在Windows上 只有 , \"" 是一个强有力的选择。
    • 如果你必须使用 ,只有3个 安全的 方法 但是,这是 相当麻烦 : 帽子的尖端 T S 为了他的帮助。

      • 使用(可能) 选择性的 )延迟变量扩展 在批处理文件中,可以 存储文字 在一个 变量 并引用 "..." 使用字符串 !var! 句法 -见 T S's helpful answer .

        • 尽管上述方法很繁琐,但它的优点是您可以应用它 有条不紊地 它起作用了 强壮地 ,任何输入。
      • 只有文字字符串(不涉及变量的字符串)才能得到类似的方法:分类 ^ -逃生 全部的 命令提示符 元字符: " & | < > 如果还想抑制变量展开- % :
        foo.exe ^"3\^" of snow^" ^"^& ver.^"

      • 否则,你必须 根据识别字符串的哪些部分来构造字符串 命令提示符 考虑 未引用的 由于误解 作为结束分隔符:

        • 在里面 字面意义的 包含外壳元字符的部分: ^ -避开它们;使用上面的示例,它是 & 那一定是 ^ -逃脱:
          foo.exe "3\" of snow" "^& ver."

        • 部分地 具有 %...% -样式变量引用 确保: 命令提示符 认为它们是 “……” 一串 变量值本身没有嵌入的不平衡引号- 这并不总是可能的 .

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


    背景

    注:这是基于我自己的实验。如果我错了一定要告诉我。

    类POSIX的shell,如类Unix系统上的bash,在传递参数之前标记参数列表(字符串)。 个别地 对于目标程序:在其他扩展中,它们将参数列表拆分为单个单词(分词),并从结果单词中删除引用字符(删除引号)。目标程序在概念上是一个去掉(语法要求)引号的单个参数数组。

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

    因此,与Unix不同,目标程序的职责是解析参数字符串,并将其分解为各个参数,去掉引号。 因此, 不同的程序可能需要不同的逃逸方法 没有单一的逃跑机制 放心 使用所有程序 - https://stackoverflow.com/a/4094897/45375 包含有关无政府状态的优秀背景,即Windows命令行分析。

    在实践中, 很常见,但不安全 ,如上所述:

    自从 命令提示符 自己不认识 作为一个 逃脱 双引号,它可以将命令行上的稍后标记错误地解释为 未引用的 并可能将其解释为 命令 和/或 输入/输出重定向 .
    简而言之:如果以下任何字符跟在 打开或不平衡 : && &; 例如:

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

    命令提示符 看到以下标记,这是由于误解 作为普通双引号:

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

    自从 命令提示符 认为 &版本。 未引用的 它解释为 & (命令排序运算符),后跟要执行的命令的名称( ver. - . 被忽视; 版本 报告 命令提示符 的版本信息)。
    总体效果是:

    • 第一, foo.exe 用第一个调用 令牌。
    • 然后,命令 版本 执行。

    即使在意外命令不会造成损害的情况下,如果不是所有参数都传递给了整体命令,那么它也不会像设计的那样工作。

    许多编译程序/翻译程序只识别 例如,当调用GNU C/C++编译器、Python、Perl、Ruby、甚至微软自己的PerfS壳时 命令提示符 -并且,除了PowerShell ,对他们来说 这个问题没有简单的解决办法。
    基本上,您必须提前知道命令行的哪些部分被错误地解释为未加引号,并且有选择地 ^ -退出的所有实例 && &; 在这些部分。

    相比之下, 使用 是安全的 但是 遗憾的是,只有基于Microsoft编译器的可执行文件和批处理文件才支持 (对于批处理文件,上面讨论了一些奇怪之处)。

    相比之下, 动力壳 ,当被调用时 从外面 -例如,来自 命令提示符 ,无论是来自命令行还是批处理文件- 认识到 只有 而且,在窗户上,更坚固 即使 内部的 动力壳使用 ` 作为双引号字符串中的转义符,也接受 例如:

    • powershell -c " \"ab c\".length" 作品 (输出 4 )和更坚固的
      powershell -c " \""ab c\"".length" ,

    • 但是 powershell -c " ""ab c"".length" 打破 .


    相关信息

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

      • 告诫 : 使用 ^ 传入的参数 call 语句已中断 (这适用于 呼叫 :调用另一个批处理文件或二进制文件,并在同一批处理文件中调用子例程):
        • ^ 实例 双引号 价值观是 莫名其妙地翻倍了 ,更改正在传递的值:例如,if变量 %v% 包含文字值 a^b , call :foo "%v%" 赋值 "a^^b" (!)到 % 1 (第一个参数)在子程序中 :foo .
        • 未引用的 使用 ^ 具有 呼叫 完全破碎 在那 ^ 不能再用于转义特殊字符 例如: call foo.cmd a^&b 安静地中断(而不是传递文字 a&b foo.cmd 如果没有 呼叫 - CMD 从未被调用过!!)至少在Windows 7上。
    • 转义文字 % 是个特例 不幸的是, 需要不同的语法,具体取决于是否在 命令行 VS 在批处理文件中 https://stackoverflow.com/a/31420292/45375

      • 它的缺点:在批处理文件中,使用 %% . 在命令行上, % 无法逃脱,但如果您将 ^ 在变量名的开始、结束或内部 未引用的 字符串(例如, echo %^foo% )可以防止变量扩展(插值); % 命令行上不属于变量引用的实例被视为文本(例如, 100% )
    • 一般来说, 安全地使用可能包含空格和特殊字符的变量值 :

      • 转让 : 围住 二者都 变量名和中的值 单一的 双引号对 例如 set "v=a & b" 指定文字值 a & b %V% (相比之下, set v="a & b" 将使双引号成为值的一部分)。转义文字 % 实例作为 %% (仅适用于批处理文件-请参见上文)。
      • 参考文献 : 双引号变量引用 以确保其值不被插值;例如, echo "%v%" 不受 %V% 插值和打印 "a & b" (但请注意,双引号也总是打印出来的)。相比之下, echo %v% 传递文字 a echo 解释 & 作为命令序列运算符,因此尝试执行名为 b .
        同时请注意上述重新使用的注意事项 ^ 呼叫 语句。
      • 外部的 程序通常会注意删除参数周围的双引号,但是,如前所述,在批处理文件中,您必须自己执行(例如, %~1 从第一个参数中删除括起来的双引号),遗憾的是, 我知道没有直接的方法 回声 如实打印变量值 没有 括起来的双引号 .
        • Neil 提供 for -基于有效的解决方案 只要值没有嵌入双引号 例如:
          set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
    • 命令提示符 认出 单一的 -引号 作为字符串分隔符-它们被视为文本,通常不能用于用嵌入的空白来分隔字符串;而且,接下来,相邻单引号的标记和中间的任何标记都被视为不带引号的 命令提示符 并据此解释。

      • 然而,给定目标程序最终执行自己的参数解析,一些程序(如Ruby DO)甚至在Windows上也识别单个引用的字符串;相比之下,C/C++可执行文件、Perl和Python DO 认出他们。
        然而,即使目标程序支持,也不建议使用单引号字符串,因为它们的内容不受以下可能不需要的解释的保护: 命令提示符 .

    动力壳

    Windows PowerShell 是一个比 命令提示符 它已经成为Windows的一部分很多年了(和 PowerShell Core 同时也为MacOS和Linux带来了PowerShell体验)。

    PowerShell始终工作 内部的 关于报价:

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

    这在PowerShell命令行上工作,并在从以下位置向PowerShell脚本或函数传递参数时工作: 在内部 电源外壳。

    (如上所述,将转义双引号传递给PowerShell 从外面 要求 或者,更坚定地说, -其他都不行)。

    不幸的是,当调用 外部的 程序,您将面临同时满足PowerShell自己的报价规则的需要 为了逃避 目标 程序:

    这种有问题的行为也在 this GitHub docs issue

    双重的 -内部报价 双重的 引文字符串 :

    考虑字符串 "3`" of rain" ,PowerShell内部将其转换为文本 3" of rain .

    如果要将此字符串传递给外部程序, 你必须应用目标程序的转义 此外 对动力壳牌公司 ;假设您希望将字符串传递给C程序,该程序希望将嵌入的双引号转义为 :

    foo.exe "3\`" of rain"
    

    注意如何 二者都 -让PowerShell高兴- 这个 \ -要使目标程序满意-必须在场。

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

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

    相比之下,嵌入 单一的 -在A中的引号 双重的 引号串 根本不需要逃跑。

    单一的 -内部报价 单一的 引文字符串 要求 额外的 逃避;考虑 '2'' of snow' ,这是PowerShell对 2' of snow .

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

    PowerShell将单引号字符串转换为双引号字符串,然后将其传递给目标程序。

    然而, 双重的 -内部报价 单一的 引文字符串 不需要逃跑 动力壳 仍然需要为 目标程序 :

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

    动力壳 V3 介绍了魔法 --% 选项 称为 停止分析符号 减轻了一些疼痛 未解释的 到目标程序,保存为 命令提示符 -样式环境变量引用(例如, %用户名% ) 扩大;例如:

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

    注意如何转义嵌入的 作为 仅适用于目标程序(也不适用于PowerShell \`" )足够了。

    但是,这种方法:

    • 不允许 逃逸 % 字符以避免环境变量扩展。
    • 排除 直接的 使用PowerShell变量和表达式;相反,命令行必须在第一步中构建在字符串变量中,然后使用调用 Invoke-Expression 一会儿。

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

    我想知道,在Windows世界中,是否有可能从根本上切换到让 做所有的标记化技术和报价删除 可预见地 , 正面 , 不考虑目标程序 ,然后通过传递生成的令牌来调用目标程序。

        3
  •  22
  •   eplawless    15 年前

    谷歌最终给出了答案。成批替换字符串的语法如下:

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

    它替换了 " 具有 \" ,正确地为bash逃走。

        4
  •  8
  •   T S    7 年前

    作为对 mklement0's excellent answer :

    几乎所有可执行文件都接受 \" 作为逃犯 " . 但是,在cmd中的安全使用几乎只能使用delayedxpansion。
    明确地发送文字 对于某个进程,分配 到环境变量,然后在需要传递引号时使用该变量。例子:

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

    注释 SETLOCAL ENABLEDELAYEDEXPANSION 似乎只在批处理文件中工作。要在交互式会话中获取DelayedExpansion,请启动 cmd /V:ON .

    如果您的批处理文件不能与delayedxpansion一起使用,则可以临时启用它:

    ::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内部进行转义会更安全一些。(如果一致,就不太可能忘记插入符号…)

    要实现这一点,任何引号前面都要有一个插入符号( ^" )引用应该作为文本到达子进程,必须另外避免出现反作用。( \^" ) 所有 必须用转义外壳元字符 ^ 同样,例如 & = & gt; ^& ; | = & gt; ^| ; > = & gt; ^> 等。

    例子:

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

    来源: Everyone quotes command line arguments the wrong way 参见“更好的报价方法”


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

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

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

        5
  •  -2
  •   XRarach    6 年前

    例如,对于从批处理文件运行的不真实的引擎自动化工具-这对我很有用

    如: -cmdline=“-messaging”-device=device-addCmdline=“-sessionid=session-session owner='owner'-sessionname='build'-dataProviderMode=local-logCmds='logCommodity off'-execcmds='automation list;run tests tests+separated+by+t1+t2;quit'“-run

    希望这能帮助别人,为我工作。