这实际上比您想象的要复杂得多。转义从 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!"
-
这假定 x 和 y 已在批处理文件中较早设置。如果您的应用只能向 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:'=''%'
变量x和y中所有出现的'都替换为'',命令扩展为
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命令:
-
将' 替换为'',以转义PowerShell 的单引号:
set x=%x:'=''%
-
将"替换为\",以转义cmd的双引号:
set x=%x:"=\"%
这两个任务的顺序无关紧要。
-
现在,可以使用 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'"