Windows 如何将文字双引号从PowerShell传递给本机命令?
我想使用PowerShell命令行在AWK/gawk中打印字符串文字(具体的程序并不重要)。然而,我想我误解了引用规则的某些地方——PowerShell显然会删除本机命令单引号中的双引号,但在将它们传递给commandlet时不会 这在Bash中起作用:Windows 如何将文字双引号从PowerShell传递给本机命令?,windows,powershell,Windows,Powershell,我想使用PowerShell命令行在AWK/gawk中打印字符串文字(具体的程序并不重要)。然而,我想我误解了引用规则的某些地方——PowerShell显然会删除本机命令单引号中的双引号,但在将它们传递给commandlet时不会 这在Bash中起作用: bash$ awk 'BEGIN {print "hello"}' hello <-- GOOD 或特别针对AWK: filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$
bash$ awk 'BEGIN {print "hello"}'
hello <-- GOOD
或特别针对AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
最终解决方案
报价已正确传递给PowerShell的echo:
PS> echo '"hello"'
"hello" <-- GOOD
PS>echo“你好”
“你好”c:\cygwin\bin\echo.exe“你好”
你好,Program.cs
csc.exe程序.cs
.\Program.exe“你好”
你好bash-c'echo“你好”
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提供的吗?),但这行代码应该适合您。您会有不同的行为,因为您使用了4个不同的echo
命令,并且在这上面使用了不同的方式
echo
是Cygwin的echo.exe
这不起作用,因为当PowerShell调用外部命令时,双引号将从参数字符串(外部引号集中的文本,即“hello”
)中删除
例如,如果使用WScript.echo WScript.Arguments(0)
作为echo.vbs
的内容调用echo.vbs的“hello”
,则会得到相同的结果
echo
是bash
的内置echo
命令
这不起作用,因为命令字符串(外部引号集中的文本,即echo“hello”
)在bash.exe中运行,其内置的echo
命令不保留参数的双引号(在bash
中运行echo“hello”
)
如果希望Cygwin的echo
打印外部双引号,则需要在字符串中添加一对转义双引号:
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
这里的问题是,在解析命令行时,Windows标准C运行时会从参数中去掉未加scaped的双引号。PowerShell通过在参数周围加双引号将参数传递给本机命令,但不会转义参数中包含的任何双引号
下面是一个测试程序,它使用C stdlib、Windows中的“raw”命令行和Windows命令行处理(其行为似乎与stdlib相同)打印给出的参数:
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中,任何包含空格的参数都用双引号括起来。即使没有空格,命令本身也会得到引号。其他参数即使包含双引号之类的标点符号也不会被引用,我认为这是一个bug,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 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的评论更新:它需要是一个过滤器,以允许管道进入标准DIN。)cmd/c
有复杂而奇怪的引用规则,我不确定这是一个好的测试用例。之所以解释bash测试用例,是因为您正在运行的shell正常处理该命令(并因此执行变量扩展、引号删除等)。请将bash-xc'echo“hello”
的输出与bash-c'echo\“hello”的输出进行比较“
了解我的意思。作为进一步的数据点,请尝试通过powershell-command
获取内部引号。我根本无法做到这一点。很好的呼叫Etan,我已经用更干净的测试用例更新了上面的示例——这有助于将责任完全归咎于PowerShell。可能会有重复的情况在另一个窗口中运行该进程,因此如果您想在PowerShell窗口中查看输出,它将不起作用。即使使用-nonewindow
选项,它仍然无法自动转义参数中的引号。实际上,由于它包含-RedirectStandardOutput
参数,所有对Get Content
的简单调用都将检索命令的输出。它也可以从cmd.exe运行。因此,我将把这归因于powershell中的另一个引号删除奇怪之处。这确实解释了我的困惑。来自Linux世界,我习惯于一个内核级API调用,它实际上知道一个合适的argv数组:--但在Windows中不是这样,因为用于创建新进程的内核API函数只接受一个命令行字符串,强制每种语言的基本库处理此任务:请参阅和@jhclark我已经更新了我的答案,结果表明,命令行处理比转义引号和反斜杠更复杂
PS> c:\cygwin\bin\echo '"hello"'
hello
PS> cmd /c 'echo "hello"'
"hello"
PS> bash -c 'echo "hello"'
hello
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
PS> bash -c 'echo "\"hello\""'
hello
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"
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
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
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
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\"
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal "
N backslashes ==> N backslashes */
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }