簡體   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