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

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$

我想使用PowerShell命令行在AWK/gawk中打印字符串文字(具体的程序并不重要)。然而,我想我误解了引用规则的某些地方——PowerShell显然会删除本机命令单引号中的双引号,但在将它们传递给commandlet时不会

这在Bash中起作用:

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