![](/img/trans.png)
[英]How do I pass an arbitrary command line argument to a native Windows program from a MinGW shell?
[英]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"'
得到相同的結果。
echo
是CMD
的內置echo
命令。
這是有效的,因為命令字符串(外部引號集中的文本,即echo "hello"
)在CMD
運行,內置的echo
命令保留參數的雙引號(在CMD
運行echo "hello"
產生"hello"
)。
echo
是bash
的內置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.