繁体   English   中英

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

[英]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"'得到相同的结果。

echoCMD的内置echo命令。

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

echobash的内置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.

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