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

jhc*_*ark 8 windows powershell

我想使用Powershell命令行在awk/gawk中打​​印字符串文字(具体程序不重要).但是,我认为我误解了引用规则 - PowerShell显然删除了本机命令的单引号内的双引号,但在将它们传递给命令行开关时却没有.

这适用于bash:

bash$ awk 'BEGIN {print "hello"}'
hello    <-- GOOD
Run Code Online (Sandbox Code Playgroud)

这适用于PowerShell - 但重要的是我不知道为什么需要转义:

PS> awk 'BEGIN {print \"hello\"}'
hello    <-- GOOD
Run Code Online (Sandbox Code Playgroud)

这在PowerShell中不打印任何内容:

PS> awk 'BEGIN {print "hello"}'
    <-- NOTHING IS BAD
Run Code Online (Sandbox Code Playgroud)

如果这确实是在PowerShell中执行此操作的唯一方法,那么我想了解解释原因的引用规则链.根据http://technet.microsoft.com/en-us/library/hh847740.aspx上的PowerShell引用规则,这不是必需的.

开始解决方案

下面的Duncan提供的妙语是,您应该将此功能添加到您的powershell配置文件中:

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

或者特别是对于awk:

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

结束解决方案

引号正确传递给PowerShell的echo:

PS> echo '"hello"'
"hello"    <-- GOOD
Run Code Online (Sandbox Code Playgroud)

但是当呼唤外部"本地"程序时,引号会消失:

PS> c:\cygwin\bin\echo.exe '"hello"'
hello    <-- BAD, POWERSHELL REMOVED THE QUOTES
Run Code Online (Sandbox Code Playgroud)

这是一个更清晰的例子,如果您担心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
Run Code Online (Sandbox Code Playgroud)

DEPRECATED示例,用于传递给cmd,它执行自己的解析(请参阅下面的Etan的评论):

PS> cmd /c 'echo "hello"'
"hello"     <-- GOOD
Run Code Online (Sandbox Code Playgroud)

DEPRECATED示例,用于传递给bash,它执行自己的解析(请参阅下面的Etan的评论):

PS> bash -c 'echo "hello"'
hello    <-- BAD, WHERE DID THE QUOTES GO
Run Code Online (Sandbox Code Playgroud)

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

Dun*_*can 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"
Run Code Online (Sandbox Code Playgroud)

运行此操作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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

自从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"}
Run Code Online (Sandbox Code Playgroud)

为避免每次都这样做,您可以定义一个简单的函数:

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"
Run Code Online (Sandbox Code Playgroud)

注意你必须转义反斜杠和双引号,否则这不起作用(这不起作用,请参阅下面的进一步编辑):

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\"
Run Code Online (Sandbox Code Playgroud)

另一个编辑: Microsoft Universe中的反斜杠和引用处理甚至比我意识到的还要怪.最后,我不得不去阅读C stdlib源代码,了解它们如何解释反斜杠和引号:

/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
          2N+1 backslashes + " ==> N backslashes + literal "
           N backslashes ==> N backslashes */
Run Code Online (Sandbox Code Playgroud)

这意味着run-native应该是:

function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
Run Code Online (Sandbox Code Playgroud)

并且所有反斜杠和引号都将在命令行处理中继续存在.或者,如果要运行特定命令:

filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
Run Code Online (Sandbox Code Playgroud)

(更新后@jhclark的评论:它需要是一个过滤器,允许管道进入标准输入).

  • 这确实解释了我的困惑.来自Linux世界,我习惯于内核级API调用,实际上知道一个正确的argv数组:http://man7.org/linux/man-pages/man3/exec.3.html - 但是在Windows中不是这种情况,因为用于创建新进程的内核API函数只接受单个命令行字符串,强制每个语言的基本库来处理此任务:请参阅http://msdn.microsoft.com/en-us/ library/17w5ykft%28v = vs.85%29.aspx和http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx (2认同)