如何将PowerShell的文字双引号传递给本机命令?

时间:2014-01-23 16:58:45

标签: windows powershell

我想使用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

任何解决方案,更优雅的解决方法或解释?

3 个答案:

答案 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"

echoCMD的内置echo命令。

这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello")在CMD中运行,内置的echo命令保留了参数的双引号(在echo "hello"中运行CMD会产生"hello")。

PS> bash -c 'echo "hello"'
hello

echobash的内置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吗?),但该行应该适合你。