【问题标题】:Command line escaping single quote for PowerShell命令行转义 PowerShell 的单引号
【发布时间】:2014-01-24 08:29:47
【问题描述】:

我有一个 Windows 应用程序,在发生事件时,它会调用如下命令:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x' -data '%y'"

name 参数有时包含'。有没有可能以某种方式逃脱?

【问题讨论】:

  • 哪个版本的 PowerShell?

标签: powershell cmd escaping


【解决方案1】:

这实际上比您想象的要复杂得多。转义从 cmd 传递到 PowerShell 的字符串中的嵌套引号是一个令人头疼的问题。使这个特别棘手的是,您需要在传递给 PowerShell 脚本参数的单引号参数中传递给 powershell.exe 的带引号的参数中由 cmd 扩展的变量中进行替换。即使是基本的字符串替换,AFAIK cmd 也没有任何本机功能,因此您需要 PowerShell 为您进行替换。

如果 -data 参数的参数(包含在 cmd 变量 x 中的参数)不一定需要单引号,最简单的方法是做的是双引号,这样 x 值内的单引号根本不需要转义。我说“最简单”,但即使这样也有点棘手。正如 Vasili Syrakis 所指出的,^ 通常是 cmd 中的转义字符,但要转义(双)引号字符串中的双引号,您需要使用 \。所以,你可以这样写你的批处理命令:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name \"%x%\" -data '%y%'"

将以下命令传递给 PowerShell:

G:\test.ps1 -name "value of x, which may contain 's" -data 'value of y'

但是,如果 x 还可以包含 PowerShell 插值字符串中的特殊字符("$`),则它会变成 很多更复杂。问题是 %x 是一个 cmd 变量,它在 PowerShell 有机会接触到它之前被 cmd 扩展。如果您在传递给 powershell.exe 的命令中单引号它包含一个单引号,那么您将给 PowerShell 会话一个提前终止的字符串,因此 PowerShell 没有机会执行任何对其进行操作。以下显然不起作用,因为 -replace 运算符需要提供一个有效的字符串,然后才能替换任何内容:

'foo'bar' -replace "'", "''"

另一方面,如果你用双引号括起来,PowerShell 会在对字符串执行任何替换之前对字符串进行插值,因此如果它包含任何特殊字符,则会在它们被替换转义之前对其进行解释。我搜索了其他方法来内联引用文字字符串(相当于 perl 的 q//,其中除了您选择的分隔符之外什么都不需要转义),但似乎没有成为任何东西。

因此,剩下的唯一解决方案是使用此处的字符串,这需要多行参数。这在批处理文件中很棘手,但可以做到:

setlocal EnableDelayedExpansion
set LF=^


set pscommand=G:\test.ps1 -name @'!LF!!x!!LF!'@ -data '!y!'
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "!pscommand!"
  • 这假定 xy 已在批处理文件中较早设置。如果您的应用只能向 cmd 发送单行命令,那么您需要将上述内容放入一个批处理文件中,在开头添加以下两行:

    set x=%~1
    set y=%~2
    

    然后像这样调用批处理文件:

    path\test.bat "%x%" "%y%"
    

    ~ 去掉了命令行参数周围的引号。您需要引号才能在变量中包含空格,但引号也会添加到变量值中。批处理就是那样愚蠢。

  • set LF=^ 后面的两个空行是必需的。

这会处理单引号,单引号也会按字面意思解释 x 值中的所有其他字符,但有一个例外:双引号。不幸的是,如果双引号可能是您在评论中指出的值的一部分,我认为如果不使用第三方实用程序,这个问题是无法解决的。原因是,如上所述,批处理没有执行字符串替换的本机方式,并且 x 的值在 PowerShell 看到它之前就被 cmd 扩展了。

顺便说一句...好问题!


更新:

事实上,在 cmd 中执行静态字符串替换可能的。邓肯添加了一个答案,说明了如何做到这一点。这有点令人困惑,所以我将详细说明 Duncan 的解决方案中发生了什么。

想法是%var:hot=cold% 扩展为变量var 的值,所有出现的hot 都替换为cold

D:\Scratch\soscratch>set var=You're such a hot shot!

D:\Scratch\soscratch>echo %var%
You're such a hot shot!

D:\Scratch\soscratch>echo %var:hot=cold%
You're such a cold scold!

因此,在命令中(为了清楚起见,根据邓肯的回答修改以与 OP 的示例保持一致):

powershell G:\test.ps1 -name '%x:'=''%' -data '%y:'=''%'

变量xy中所有出现的'都替换为'',命令扩展为

powershell G:\test.ps1 -name 'a''b' -data 'c''d'

让我们分解其中的关键元素,'%x:'=''%'

  • 开头和结尾的两个's 是传递给PowerShell 以引用参数的显式外部引号,即OP 在%x 周围的单引号
  • :'=''是字符串替换,表示'应该替换成''
  • %x:'=''% 扩展为变量 x 的值,其中' 替换为'',即a''b
  • 因此,整个事情扩展为'a''b'

这个解决方案比我上面的解决方法更简单地转义变量值中的单引号。但是,OP 在更新中指出该变量也可能包含双引号,并且到目前为止,该解决方案仍然没有将 x 中的双引号传递给 PowerShell——这些仍然被 cmd 删除PowerShell 接收命令。

好消息是,使用 cmd 字符串替换方法,这变得可以克服。在x的初始值已经设置好后执行以下cmd命令:

  1. ' 替换为'',以转义PowerShell 的单引号:

    set x=%x:'=''%
    
  2. "替换为\",以转义cmd的双引号:

    set x=%x:"=\"%
    

    这两个任务的顺序无关紧要。

  3. 现在,可以使用 OP 最初使用的语法调用 PowerShell 脚本(删除 powershell.exe 的路径以将其全部放在一行中):

    powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x' -data '%y'"
    

再一次,如果app只能向cmd发送一行命令,这三个命令可以放在一个批处理文件中,app可以调用批处理文件并传递变量如上图(我的第一个子弹原始答案)。

需要注意的一个有趣的点是,如果将 " 替换为 \" 是内联完成的,而不是使用单独的 set 命令,您不要转义字符串替换中的"s,即使它们位于双引号字符串中,例如:

powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x:"=\"' -data '%y'"

...不是这样的:

powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x:\"=\\"' -data '%y'"

【讨论】:

  • 实际上,现在我想起来了,改变它来处理插值特殊字符是非常简单的。我会更新答案。
  • 嘿,谢谢你的信息! %x 基本上可以是任何字符串(它是文件夹名称)。字符串几乎总是只有字母和数字,但有时它也可以包含单引号或双引号。其余的并不重要,但这两个确实重要。它也应该与空格一起使用。
  • 哇,不像我最初想象的那么简单!我能够为您提出的原始问题制定出一个笨拙的解决方案。但是,您在这里说 %x 可能包含双引号,这会破坏您的原始命令和我的解决方法,而且我认为没有第三方实用程序就无法解决该问题。我会在我的答案更新中解释。
  • 谢谢!在那种情况下,虽然我可能会使用您已经发布的解决方案。单引号比双引号更常见,所以我将把它省略掉。
  • 好的,但请记住,如果 x 也包含 $`,我的第一个解决方案就会中断。我刚刚添加的复杂解决方法适用于除双引号之外的任何内容。现在我已经发布了它,是时候让其他人来发布一个非常简单的解决方案,让我把头撞到墙上。 ;)
【解决方案2】:

我不太清楚 %x%y 是否是 CMD 变量(在这种情况下,您应该使用 %x% 替换它,或者在您的其他应用程序中进行替换。

您需要通过在 CMD.EXE 命令行中将传递给 PowerShell 的单引号加倍来转义它。您可以通过用两个单引号替换变量中的任何引号来做到这一点。

例如:

C:\scripts>set X=a'b

C:\scripts>set Y=c'd

C:\scripts>powershell .\test.ps1 -name '%x:'=''%' '%y:'=''%'
Name is 'a'b'
Data is 'c'd'

其中test.ps1 包含:

C:\scripts>type test.ps1
param($name,$data)

write-output "Name is '$name'"
write-output "Data is '$data'"

如果您提供的命令行是在外部应用程序中生成的,您仍然可以通过先将字符串分配给变量并使用& 分隔命令来执行此操作(注意避免在set 命令)。

set X=a'b& powershell .\test.ps1 -name '%x:'=''%'

CMD shell 支持简单的替换形式,以及在替换变量时提取子字符串的方法。这些仅在替换变量时有效,因此如果您想同时进行多个替换,或者替换和子字符串提取,那么您需要一次做一个,为每一步设置变量。

Environment variable substitution has been enhanced as follows:

    %PATH:str1=str2%

would expand the PATH environment variable, substituting each occurrence
of "str1" in the expanded result with "str2".  "str2" can be the empty
string to effectively delete all occurrences of "str1" from the expanded
output.  "str1" can begin with an asterisk, in which case it will match
everything from the beginning of the expanded output to the first
occurrence of the remaining portion of str1.

May also specify substrings for an expansion.

    %PATH:~10,5%

would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result.  If the length is not specified, then it defaults to the
remainder of the variable value.  If either number (offset or length) is
negative, then the number used is the length of the environment variable
value added to the offset or length specified.

    %PATH:~-10%

would extract the last 10 characters of the PATH variable.

    %PATH:~0,-2%

would extract all but the last 2 characters of the PATH variable.

【讨论】:

  • 啊,我明白你在做什么了!起初我有点困惑,并认为您犯了与 Vasili 相同的错误,但现在它是有道理的——这就是我一直在寻找但找不到的方法,一种在本机 cmd 中的变量中进行字符串替换的方法。我将把它分解并详细说明它是如何工作的,因为它可能很难破译,尤其是在外部引号混淆了这个问题的情况下。不幸的是,这仍然不允许在变量值中传递双引号。看来它们是通过 cmd 或通过 powershell.exe 处理命令参数的方式被剥离的。 :(
  • 其实可以使用这个方法来同时转义单引号和将双引号传递给PowerShell!我只是尝试了一些东西,它就像一个魅力。评论太长了,但我会发布更新。
【解决方案3】:

我相信你可以用^ 来逃避它:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name ^'%x^' -data ^'%y^'"

【讨论】:

  • 但这不是他要问的。他不是在问如何转义周围的单引号——事实上,这没有意义,因为没有必要在 cmd 中的带引号的字符串中转义单引号(正如您使用 ^ 所暗示的那样),也没有需要在 PowerShell 中转义围绕字符串参数的单引号。他要问的是如何转义 cmd 变量 %x 的值中可能出现的任何单引号,这样如果例如 %x = foo'bar,他就是未将参数-name 'foo'bar' 传递给test.ps1 脚本,这将导致缺少终止符错误。
  • 这实际上比你想象的要复杂得多——在从 cmd 传递到 PowerShell 的字符串中转义嵌套引号是一个令人头疼的问题。让我试验几分钟,我应该能够想出一个解决方案。
【解决方案4】:

尝试将您的随机单引号变量封装在一对双引号中以避免该问题。

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name `"%x`" -data `"%y`""

出现问题是因为您使用了单引号,而单引号内出现的随机额外单引号会愚弄 PowerShell。如果您使用反引号进行双引号,则不应发生这种情况,因为单引号不会在双引号内抛出任何内容,并且反引号将允许您使用双引号/双引号。

【讨论】:

  • 这行不通,因为问题的关键在于命令正在由 cmd 调用,并且反引号不用于在 cmd 中转义(如果命令是从 PowerShell 调用的,解决方案就像-name "$($x -replace "'", "''")" 一样简单)。有关此解决方案的正确 cmd 语法,请参阅我的答案中的第一个代码块。但是,此解决方案的弱点(在我的回答中详细说明)在于它将 x 的全部内容视为插值字符串。
【解决方案5】:

仅供参考,我在 powershell 中使用 robocopy 命令遇到了一些问题,并且想要排除名称中带有单引号的文件夹(并且反引号没有帮助,并且 ps 和 robocopy 不适用于双引号);所以,我通过为文件夹创建一个带有引号的变量来解决它:

$folder="John Doe's stuff"
robocopy c:\users\jd\desktop \\server\folder /mir /xd 'normal folder' $folder

【讨论】:

    【解决方案6】:

    解决此问题的一个古怪方法是在 cmd 中使用 echo 将您的命令通过管道传递到 'powershell -c "-" ',而不是使用 powershell "arguments"

    例如:

    ECHO Write-Host "stuff'" | powershell -c "-"
    

    输出:

    stuff'
    

    几个笔记:

    1. 不要引用您正在回显的命令文本,否则它将不起作用

    2. 如果你想在 PowerShell 命令中使用任何管道,它们必须用克拉进行三次转义才能正常工作

      ^^^|

    【讨论】:

      猜你喜欢
      • 2023-04-02
      • 2013-03-12
      • 1970-01-01
      • 2015-07-16
      • 2023-03-09
      • 1970-01-01
      • 2014-08-21
      • 2021-06-05
      • 1970-01-01
      相关资源
      最近更新 更多