我想使用PowerShell命令行在AWK / gawk中打印字符串文字(具体程序不重要)。但是,我认为我误解了引用规则 - 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中执行此操作的唯一方法,那么我想了解解释原因的引用规则链。根据 About Quoting Rules 中的PowerShell引用规则,这不是必需的。
开始解决方案
下面Duncan提供的妙语是,您应该将此功能添加到PowerShell配置文件中:
filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }
或专门针对AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
结束解决方案
引号正确传递给PowerShell的echo:
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
DEPRECATED示例,用于传递给cmd,它自己进行解析(参见下面的Etan的评论):
PS> cmd /c 'echo "hello"'
"hello" <-- GOOD
DEPRECATED EXAMPLE传递给Bash,它自己解析(参见下面的Etan的评论):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
任何解决方案,更优雅的解决方法或解释?
答案 0 :(得分:7)
这里的问题是Windows标准C运行时在解析命令行时从参数中删除未转义的双引号。 PowerShell通过在参数周围加上双引号将参数传递给本机命令,但它不会转义参数中包含的任何双引号。
这是一个测试程序,它打印出使用C stdlib给出的参数,来自Windows的'raw'命令行,以及Windows命令行处理(它似乎与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
中运行此操作,我们可以看到所有未转义的引号都被剥离,并且当存在偶数个未转义的引号时,空格只会分隔参数:
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的行为略有不同:
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"
N.B。您必须转义反斜杠以及双引号,否则这不起作用(这不起作用,请参阅下面的进一步编辑):
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 Universe中的反斜杠和引用处理甚至比我意识到的还要怪。最后我不得不去阅读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的评论:它需要是一个过滤器,允许管道进入标准输入。)
答案 1 :(得分:2)
您会得到不同的行为,因为您使用了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"
)中删除。
如果您调用echo.vbs '"hello"'
且WScript.Echo WScript.Arguments(0)
为echo.vbs
的内容,则会得到相同的结果。
的
PS> cmd /c 'echo "hello"'
"hello"
echo
是CMD
的内置echo
命令。
这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello"
)在CMD
中运行,内置的echo
命令保留了参数的双引号(在echo "hello"
中运行CMD
会产生"hello"
)。
的
PS> bash -c 'echo "hello"'
hello
echo
是bash
的内置echo
命令。
这不起作用,因为命令字符串(外部引号集中的文本,即echo "hello"
)在bash.exe
中运行,并且其内置echo
命令不保留参数的双引号(在echo "hello"
中运行bash
生成hello
)。
如果您希望Cygwin的echo
打印外部双引号,则需要在字符串中添加一对转义的双引号:
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
我希望这适用于bash
- 内置echo
,但由于某种原因,它不会:
PS> bash -c 'echo "\"hello\""'
hello
答案 2 :(得分:1)
当您直接从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吗?),但该行应该适合你。