简体   繁体   English

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

[英]How do I pass a literal double quote from PowerShell to a native command?

I'd like to print a string literal in AWK / gawk using the PowerShell command line (the specific program is unimportant). 我想使用PowerShell命令行在AWK / gawk中打​​印字符串文字(具体程序不重要)。 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. 但是,我认为我误解了引用规则 - PowerShell显然删除了本机命令的单引号内的双引号,但在将它们传递给命令行开关时却没有。

This works in Bash: 这适用于Bash:

bash$ awk 'BEGIN {print "hello"}'
hello    <-- GOOD

And this works in PowerShell -- but importantly I have no idea why the escaping is needed : 这适用于PowerShell - 但重要的是我不知道为什么需要转义

PS> awk 'BEGIN {print \"hello\"}'
hello    <-- GOOD

This prints nothing in PowerShell: 这在PowerShell中不打印任何内容:

PS> awk 'BEGIN {print "hello"}'
    <-- NOTHING IS BAD

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. 如果这确实是在PowerShell中执行此操作的唯一方法,那么我想了解解释原因的引用规则链。 According to the PowerShell quoting rules at About Quoting Rules , this shouldn't be necessary. 根据关于报价规则的PowerShell引用规则,这不应该是必要的。

BEGIN SOLUTION 开始解决方案

The punchline, courtesy of Duncan below, is that you should add this function to your PowerShell profile: 下面的Duncan提供的妙语是,您应该将此功能添加到PowerShell配置文件中:

 filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }

Or specifically for AWK: 或者特别针对AWK:

 filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }

END SOLUTION 结束解决方案

The quotes are properly passed to PowerShell's echo: 引号正确传递给PowerShell的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

Here's an even cleaner example, in case you're concerned that Cygwin might have something to do with this: 这是一个更清晰的例子,如果您担心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 EXAMPLE for passing to cmd, which does its own parsing (see Etan's comment below): DEPRECATED示例,用于传递给cmd,它执行自己的解析(请参阅下面的Etan的评论):

PS> cmd /c 'echo "hello"'
"hello"     <-- GOOD

DEPRECATED EXAMPLE for passing to Bash, which does its own parsing (see Etan's comment below): DEPRECATED示例传递给Bash,它自己进行解析(参见下面的Etan的评论):

PS> bash -c 'echo "hello"'
hello    <-- BAD, WHERE DID THE QUOTES GO

Any solutions, more elegant workarounds, or explanations? 任何解决方案,更优雅的解决方法或解释?

The problem here is that the Windows standard C runtime strips unescaped double quotes out of arguments when parsing the command line. 这里的问题是,在解析命令行时,Windows标准C运行时会从参数中删除未转义的双引号。 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. PowerShell通过在参数周围加上双引号将参数传递给本机命令,但它不会转义参数中包含的任何双引号。

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

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: 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 behaves a bit differently: 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

In PowerShell, any argument that contains spaces is enclosed in double quotes. 在PowerShell中,包含空格的任何参数都用双引号括起来。 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: 如果您想知道(我是),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

The only option since PowerShell doesn't escape the quotes for you seems to be to do it yourself: 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"}

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"

NB 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\"

Another edit: Backslash and quote handling in the Microsoft universe is even weirder than I realised. 另一个编辑: Microsoft Universe中的反斜杠和引用处理甚至比我意识到的还要怪。 Eventually I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes: 最后,我不得不去阅读C stdlib源代码,了解它们如何解释反斜杠和引号:

/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
          2N+1 backslashes + " ==> N backslashes + literal "
           N backslashes ==> N backslashes */

So that means run-native should be: 所以这意味着run-native应该是:

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\"') }

(Updated following @jhclark's comment: it needs to be a filter to allow piping into stdin.) (更新了@jhclark的评论:它需要是一个过滤器,允许管道进入标准输入。)

You get different behavior, because you're using 4 different echo commands, and in different ways on top of that. 您会得到不同的行为,因为您使用了4种不同的echo命令,并且以不同的方式使用它们。

PS> echo '"hello"' "hello"

echo is PowerShell's Write-Output cmdlet. echo是PowerShell的Write-Output cmdlet。

This works, because the cmdlet takes the given argument string (the text within the outer set of quotes, ie "hello" ) and prints that string to the success output stream. 这是有效的,因为cmdlet采用给定的参数字符串(外部引号集中的文本,即"hello" )并将该字符串输出到成功输出流。

PS> c:\cygwin\bin\echo '"hello"' hello

echo is Cygwin's echo.exe . echo是Cygwin的echo.exe

This doesn't work, because the double quotes are removed from the argument string (the text within the outer set of quotes, ie "hello" ) when PowerShell calls the external command. 这不起作用,因为当PowerShell调用外部命令时,双引号将从参数字符串(外部引号集中的文本,即"hello" )中删除。

You get the same result if for instance you call echo.vbs '"hello"' with WScript.Echo WScript.Arguments(0) being the content of echo.vbs . 如果您使用WScript.Echo WScript.Arguments(0)作为echo.vbs的内容调用echo.vbs '"hello"'得到相同的结果。

PS> cmd /c 'echo "hello"' "hello"

echo is CMD 's built-in echo command. echoCMD的内置echo命令。

This works, because the command string (the text within the outer set of quotes, ie echo "hello" ) is run in CMD , and the built-in echo command preserves the argument's double quotes (running echo "hello" in CMD produces "hello" ). 这是有效的,因为命令字符串(外部引号集中的文本,即echo "hello" )在CMD运行,内置的echo命令保留参数的双引号(在CMD运行echo "hello"产生"hello" )。

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

echo is bash 's built-in echo command. echobash的内置echo命令。

This doesn't work, because the command string (the text within the outer set of quotes, ie echo "hello" ) is run in bash.exe , and its built-in echo command does not preserve the argument's double quotes (running echo "hello" in bash produces hello ). 这不起作用,因为命令字符串(外部引号集中的文本,即echo "hello" )在bash.exe运行,并且其内置的echo命令不保留参数的双引号(运行echo "hello" bash echo "hello"产生hello

If you want Cygwin's echo to print outer double quotes you need to add an escaped pair of double quotes to your string: 如果你想要Cygwin的echo来打印外部双引号,你需要在字符串中添加一对转义的双引号:

PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"

I would've expected this to work for the bash -builtin echo es well, but for some reason it doesn't: 我希望这对bash -builtin echo很有用,但由于某些原因它没有:

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

Quoting rules can get confusing when you're calling commands directly from PowerShell. 当您直接从PowerShell调用命令时,引用规则会让您感到困惑。 Instead, I regularly recommend that people use the Start-Process cmdlet, along with its -ArgumentList parameter. 相反,我经常建议人们使用Start-Process cmdlet及其-ArgumentList参数。

Start-Process -Wait -FilePath awk.exe -ArgumentList 'BEING {print "Hello"}' -RedirectStandardOutput ('{0}\awk.log' -f $env:USERPROFILE);

I don't have awk.exe (does that come from Cygwin?), but that line should work for you. 我没有awk.exe (这是来自Cygwin吗?),但该行应该适合你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM