![](/img/trans.png)
[英]How do I pass an arbitrary command line argument to a native Windows program from a MinGW shell?
[英]How do I pass a literal double quote from PowerShell to a native command?
我想使用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中执行此操作的唯一方法,那么我想了解解释原因的引用规则链。 根据关于报价规则的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示例传递给Bash,它自己进行解析(参见下面的Etan的评论):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
任何解决方案,更优雅的解决方法或解释?
这里的问题是,在解析命令行时,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"
注意你必须转义反斜杠和双引号,否则这不起作用( 这不起作用,请参阅下面的进一步编辑 ):
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的评论:它需要是一个过滤器,允许管道进入标准输入。)
您会得到不同的行为,因为您使用了4种不同的echo
命令,并且以不同的方式使用它们。
echo
是PowerShell的Write-Output
cmdlet。
这是有效的,因为cmdlet采用给定的参数字符串(外部引号集中的文本,即"hello"
)并将该字符串输出到成功输出流。
echo
是Cygwin的echo.exe
。
这不起作用,因为当PowerShell调用外部命令时,双引号将从参数字符串(外部引号集中的文本,即"hello"
)中删除。
如果您使用WScript.Echo WScript.Arguments(0)
作为echo.vbs
的内容调用echo.vbs '"hello"'
得到相同的结果。
echo
是CMD
的内置echo
命令。
这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello"
)在CMD
运行,内置的echo
命令保留参数的双引号(在CMD
运行echo "hello"
产生"hello"
)。
echo
是bash
的内置echo
命令。
这不起作用,因为命令字符串(外部引号集中的文本,即echo "hello"
)在bash.exe
运行,并且其内置的echo
命令不保留参数的双引号(运行echo "hello"
bash
echo "hello"
产生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吗?),但该行应该适合你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.