问题描述
我想使用 PowerShell 命令行在 AWK/gawk 中打印字符串文字(具体程序并不重要).但是,我想我误解了沿线某处的引用规则——PowerShell 显然会删除本机命令的单引号内的双引号,但在将它们传递给命令行开关时不会.
I'd like to print a string literal in AWK / gawk using the PowerShell command line (the specific program is unimportant). However, I think I misunderstand the quoting rules somewhere along the line -- PowerShell apparently removes double quotes inside single quotes for native commands, but not when passing them to commandlets.
这适用于 Bash:
bash$ awk 'BEGIN {print "hello"}'
hello <-- GOOD
这在 PowerShell 中有效——但重要的是我不知道为什么需要转义:
And this works in PowerShell -- but importantly I have no idea why the escaping is needed:
PS> awk 'BEGIN {print \"hello\"}'
hello <-- GOOD
这在 PowerShell 中不打印任何内容:
This prints nothing in PowerShell:
PS> awk 'BEGIN {print "hello"}'
<-- NOTHING IS BAD
如果这真的是在 PowerShell 中执行此操作的唯一方法,那么我想了解解释原因的引用规则链.根据关于引用规则,这应该没有必要.
If this really is the only way of doing this in PowerShell, then I'd like to understand the chain of quoting rules that explains why. According to the PowerShell quoting rules at About Quoting Rules, this shouldn't be necessary.
开始解决方案
下面由 Duncan 提供的妙语是,您应该将此函数添加到您的 PowerShell 配置文件中:
The punchline, courtesy of Duncan below, is that you should add this function to your PowerShell profile:
filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }
或专门用于 AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
结束解决方案
引号已正确传递给 PowerShell 的 echo:
The quotes are properly passed to PowerShell's echo:
PS> echo '"hello"'
"hello" <-- GOOD
但是当调用外部本地"程序时,引号消失了:
But when calling out to an external "native" program, the quotes disappear:
PS> c:\cygwin\bin\echo.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
这是一个更简洁的示例,以防您担心 Cygwin 可能与此有关:
Here's an even cleaner example, in case you're concerned that Cygwin might have something to do with this:
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 的弃用示例,它会自行解析(请参阅下面的 Etan 评论):
DEPRECATED EXAMPLE for passing to cmd, which does its own parsing (see Etan's comment below):
PS> cmd /c 'echo "hello"'
"hello" <-- GOOD
传递给 Bash 的弃用示例,Bash 进行自己的解析(请参阅下面的 Etan 评论):
DEPRECATED EXAMPLE for passing to Bash, which does its own parsing (see Etan's comment below):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
任何解决方案、更优雅的解决方法或解释?
Any solutions, more elegant workarounds, or explanations?
推荐答案
这里的问题是 Windows 标准 C 运行时在解析命令行时从参数中去除了未转义的双引号.PowerShell 通过在参数周围放置双引号将参数传递给本机命令,但它不会转义参数中包含的任何双引号.
The problem here is that the Windows standard C runtime strips unescaped double quotes out of arguments when parsing the command line. PowerShell passes arguments to native commands by putting double quotes around the arguments, but it doesn't escape any double quotes that are contained in the arguments.
这是一个测试程序,它使用 C 标准库、来自 Windows 的原始"命令行和 Windows 命令行处理(其行为似乎与标准库相同)打印出它给出的参数:
Here's a test program that prints out the arguments it was given using the C stdlib, the 'raw' command line from Windows, and the Windows command line processing (which seems to behave identically to the stdlib):
C:\Temp> type t.c
#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 t.c "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.
t.c
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
中运行我们可以看到所有未转义的引号都被剥离了,并且空格仅在有偶数个未转义引号时才分隔参数:
Running this in cmd
we can see that all unescaped quotes are stripped, and spaces only separate arguments when there have been an even number of unescaped quotes:
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"b c"d e"
Arg[0]: t
Arg[1]: ab
Arg[2]: cd e
Command Line: t "a"b c"d e"
0: t
1: ab
2: cd e
PowerShell 的行为略有不同:
PowerShell behaves a bit differently:
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 不会转义出现在参数中的任何双引号.
In PowerShell, any argument that contains spaces is enclosed in double quotes. Also the command itself gets quotes even when there aren't any spaces. Other arguments aren't quoted even if they include punctuation such as double quotes, and and I think this is a bug PowerShell doesn't escape any double quotes that appear inside the arguments.
如果您想知道(我是),PowerShell 甚至不会费心引用包含换行符的参数,但参数处理也不会将换行符视为空格:
In case you're wondering (I was), PowerShell doesn't even bother to quote arguments that contain newlines, but neither does the argument processing consider newlines as whitespace:
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 不会为您转义引号,因此唯一的选择似乎是自己做:
The only option since PowerShell doesn't escape the quotes for you seems to be to do it yourself:
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"}
为了避免每次都这样做,你可以定义一个简单的函数:
To avoid doing that every time, you can define a simple function:
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"
注意您必须转义反斜杠和双引号,否则这不起作用(这不起作用,请参阅下面的进一步编辑):
N.B. You have to escape backslashes as well as double quotes otherwise this doesn't work (this doesn't work, see further edit below):
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\"
另一个 Microsoft 世界中的反斜杠和引号处理比我意识到的还要奇怪.最终,我不得不去阅读 C stdlib 源代码,以了解它们如何解释反斜杠和引号:
Another edit: Backslash and quote handling in the Microsoft universe is even weirder than I realised. Eventually I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes:
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal "
N backslashes ==> N backslashes */
所以这意味着 run-native
应该是:
So that means run-native
should be:
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
并且所有反斜杠和引号都将在命令行处理中保留下来.或者,如果您想运行特定命令:
and all backslashes and quotes will survive the command line processing. Or if you want to run a specific command:
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
(根据@jhclark 的评论更新:它需要是一个过滤器才能允许管道进入标准输入.)
(Updated following @jhclark's comment: it needs to be a filter to allow piping into stdin.)
这篇关于如何将文字双引号从 PowerShell 传递给本机命令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!