如何将PowerShell的文字双引号传递给本机命令?

我想使用Powershell命令行(特定程序不重要)在awk / gawk中打​​印string文字。 但是,我认为我误解了引用规则的某处–PowerShell显然删除了双引号内的本机命令,而不是将它们传递给命令行。

这在bash中工作:

bash$ awk 'BEGIN {print "hello"}' hello <-- GOOD 

这在PowerShell中有效 – 但重要的是我不知道为什么需要转义

 PS> awk 'BEGIN {print \"hello\"}' hello <-- GOOD 

这在PowerShell中不会打印任何内容:

 PS> awk 'BEGIN {print "hello"}' <-- NOTHING IS BAD 

如果这真的是在PowerShell中做到这一点的唯一方法,那么我想了解引用规则链,解释了原因。 根据http://technet.microsoft.com/en-us/library/hh847740.aspx中的PowerShell引用规则,这不应该是必要的。

BEGIN解决scheme

Duncan下面的提示是,您应该将此函数添加到您的PowerShellconfiguration文件中:

  filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') } 

或专门为awk:

  filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') } 

解决scheme

引号被正确地传递给PowerShell的回声:

 PS> echo '"hello"' "hello" <-- GOOD 

但是当调用外部“本地”程序时,引号消失:

 PS> c:\cygwin\bin\echo.exe '"hello"' hello <-- BAD, POWERSHELL REMOVED THE QUOTES 

这里有一个更清晰的例子,如果你担心Cygwin可能会有这样的事情:

 echo @" >>> // program guaranteed not to interfere with command line parsing >>> public class Program >>> { >>> public static void Main(string[] args) >>> { >>> System.Console.WriteLine(args[0]); >>> } >>> } >>> "@ > Program.cs csc.exe Program.cs .\Program.exe '"hello"' hello <-- BAD, POWERSHELL REMOVED THE QUOTES 

弃权的例子,传递给CMD,它自己的parsing(见下面的Etan的评论):

 PS> cmd /c 'echo "hello"' "hello" <-- GOOD 

弃用示例传递给bash,它自己parsing(请参阅下面的Etan的评论):

 PS> bash -c 'echo "hello"' hello <-- BAD, WHERE DID THE QUOTES GO 

任何解决scheme,更优雅的解决方法或解释?

这里的问题是,Windows标准C运行时在解析命令行时会剥离出未引用的双引号。 Powershell通过在参数周围加双引号将参数传递给本地命令,但是它不会转义任何包含在参数中的双引号。

下面是一个测试程序,它打印出使用C stdlib,Windows的'raw'命令行和Windows命令行处理(这看起来与stdlib的行为相同)的参数:

 C:\Temp>type tc #include <stdio.h> #include <windows.h> #include <ShellAPI.h> int main(int argc,char **argv){ int i; for(i=0; i < argc; i++) { printf("Arg[%d]: %s\n", i, argv[i]); } LPWSTR *szArglist; LPWSTR cmdLine = GetCommandLineW(); wprintf(L"Command Line: %s\n", cmdLine); int nArgs; szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); if( NULL == szArglist ) { wprintf(L"CommandLineToArgvW failed\n"); return 0; } else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]); // Free memory allocated for CommandLineToArgvW arguments. LocalFree(szArglist); return 0; } C:\Temp>cl tc "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib" Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. tc Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:t.exe t.obj "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib" 

cmd运行这个cmd我们可以看到,所有未转义的引号都被剥离了,并且当有偶数的未引号的引号时,

 C:\Temp>t "a"b" "\"escaped\"" Arg[0]: t Arg[1]: ab "escaped" Command Line: t "a"b" "\"escaped\"" 0: t 1: ab "escaped" C:\Temp>t "a"bc"de" Arg[0]: t Arg[1]: ab Arg[2]: cd e Command Line: t "a"bc"de" 0: t 1: ab 2: cd e 

Powershell的行为有点不同:

 C:\Temp>powershell Windows PowerShell Copyright (C) 2012 Microsoft Corporation. All rights reserved. C:\Temp> .\t 'a"b' Arg[0]: C:\Temp\t.exe Arg[1]: ab Command Line: "C:\Temp\t.exe" a"b 0: C:\Temp\t.exe 1: ab C:\Temp> $a = "string with `"double quotes`"" C:\Temp> $a string with "double quotes" C:\Temp> .\t $a nospaces Arg[0]: C:\Temp\t.exe Arg[1]: string with double Arg[2]: quotes Arg[3]: nospaces Command Line: "C:\Temp\t.exe" "string with "double quotes"" nospaces 0: C:\Temp\t.exe 1: string with double 2: quotes 3: nospaces 

在Powershell中,任何包含空格的参数都用双引号引起来。 即使没有空格,命令本身也会被引用。 其他参数即使包含双引号等标点符号也不被引用,而且我认为这是一个错误 Powershell不会转义出现在参数内部的任何双引号。

如果您想知道(我是),Powershell甚至不会引用包含换行符的参数,但参数处理也不会将换行符视为空格:

 C:\Temp> $a = @" >> a >> b >> "@ >> C:\Temp> .\t $a Arg[0]: C:\Temp\t.exe Arg[1]: a b Command Line: "C:\Temp\t.exe" a b 0: C:\Temp\t.exe 1: a b 

自从Powershell不会为您引用引号之后,唯一的选择似乎是自己做:

 C:\Temp> .\t 'BEGIN {print "hello"}'.replace('"','\"') Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" 0: C:\Temp\t.exe 1: BEGIN {print "hello"} 

为了避免每次都这样做,你可以定义一个简单的函数:

 C:\Temp> function run-native($command) { & $command $args.replace('\','\\').replace('"','\"') } C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And "another"' Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Arg[2]: And "another" Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" "And \"another\"" 0: C:\Temp\t.exe 1: BEGIN {print "hello"} 2: And "another" 

NB你必须避免反斜杠以及双引号,否则这是行不通的( 这是行不通的,请参阅下面的进一步编辑 ):

 C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And \"another\"' Arg[0]: C:\Temp\t.exe Arg[1]: BEGIN {print "hello"} Arg[2]: And \"another\" Command Line: "C:\Temp\t.exe" "B EGIN {print \"hello\"}" "And \\\"another\\\"" 0: C:\Temp\t.exe 1: BEGIN {print "hello"} 2: And \"another\" 

另一个编辑:在微软的宇宙中的反斜杠和引号处理甚至比我意识到的更为奇怪。 最后,我不得不阅读C stdlib源代码,以了解它们如何解释反斜杠和引号:

 /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote 2N+1 backslashes + " ==> N backslashes + literal " N backslashes ==> N backslashes */ 

所以这意味着run-native应该是:

 function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') } 

并且所有的反斜杠和引号将在命令行处理中幸存下来。 或者如果你想运行一个特定的命令:

 filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') } 

(更新后@ jhclark的评论:它需要是一个过滤器,允许管道输入标准输入)。

你会得到不同的行为,因为你正在使用4个不同的echo命令,并以不同的方式。

 PS> echo '"hello"' "hello" 

echo是PowerShell的Write-Output cmdlet。

这是有效的,因为cmdlet接受给定的参数字符串(外部引号集中的文本,即"hello" ),并将该字符串输出到成功输出流。

 PS> c:\cygwin\bin\echo '"hello"' hello 

echo是Cygwin的echo.exe

这是行不通的,因为当PowerShell调用外部命令时,双引号将从参数字符串(外部引号集中的文本,即"hello" )中被删除。

例如,如果您使用WScript.Echo WScript.Arguments(0)作为echo.vbs的内容来调用echo.vbs '"hello"' ,您将得到相同的结果。

 PS> cmd /c 'echo "hello"' "hello" 

echoCMD的内置echo命令。

这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello" )在CMD运行,内置的echo命令保留了参数的双引号(在CMD运行echo "hello" "hello" )。

 PS> bash -c 'echo "hello"' hello 

echobash的内置echo命令。

这是行不通的,因为在bash.exe运行的是命令字符串(外部引号集中的文本,即echo "hello" ),而其内置的echo命令不保留参数的双引号(运行echo "hello"bash产生hello )。

如果你想Cygwin的echo打印外部双引号,你需要添加一个双引号转义字符串到你的字符串:

 PS> c:\cygwin\bin\echo '"\"hello\""' "hello" 

我会期望这个工作的bash -builtin echo好,但由于某种原因,它不:

 PS> bash -c 'echo "\"hello\""' hello 

当您直接从PowerShell调用命令时,引用规则可能会引起混淆。 相反,我经常建议人们使用Start-Process cmdlet及其-ArgumentList参数。

 Start-Process -Wait -FilePath awk.exe -ArgumentList 'BEING {print "Hello"}' -RedirectStandardOutput ('{0}\awk.log' -f $env:USERPROFILE); 

我没有awk.exe (这是来自Cygwin?),但该行应该为你工作。