我想使用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"
echo
是CMD
的内置echo
命令。
这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello"
)在CMD
运行,内置的echo
命令保留了参数的双引号(在CMD
运行echo "hello"
"hello"
)。
PS> bash -c 'echo "hello"' hello
echo
是bash
的内置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?),但该行应该为你工作。