Powershell: 外部可执行文件的参数未正确转义

创建于 2016-08-21  ·  170评论  ·  资料来源: PowerShell/PowerShell

重现步骤

  1. 编写一个获取ARGV的C程序native.exe
  2. 运行native.exe "`"a`""

    预期行为

ARGV [1] == "a"

实际行为

ARGV [1] == a

环境数据

Windows 10 x64

Name                           Value
----                           -----
PSVersion                      5.1.14393.0
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14393.0
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Committee-Reviewed WG-Engine

最有用的评论

为此,为命令行shell设置一个特殊的运算符根本没有意义,因为它的主要工作是启动程序并传递参数。 引入一个新的运算符为该工作执行system()就像Matlab引入了一种调用calc.exe因为它在算术上存在错误。 相反,应该做的是:

  • pwsh团队正在准备一个新的主要发行版,该发行版修复了命令行问题,并将当前行为移至内置cmdlet后面。
  • 作为权宜之计,即将发布的pwsh版本获得了一个内置cmdlet,该cmdlet使用新的正确行为进行命令行传递。

Start-Process 。 (实际上,它是“新” cmdlet的不错选择,其中包括-QuotingBehavior Legacy ...等选项)。请参阅#13089。

所有170条评论

您为什么认为"\"a\""是预期的行为? 我对PowerShell转义的理解是,实际行为是正确的和预期的行为。 """是一对引号,将一对转义的引号引起来,将a包围起来,因此PowerShell将外部未转义的对解释为”这是一个字符串参数“,并且因此删除它们,然后将转义的对解释为转义的引号,并保留它们,从而给您留下"a" ,这时根本不会在字符串中添加\

Bash使用\作为转义字符这一事实是无关紧要的。 在PowerShell中,转义字符是反引号。 请参阅PowerShell转义字符

如果您想直接传递"\"a\"" ,我相信您会使用:

> echo `"\`"a\`"`"
"\"a\""

@andschwa
是的,转义对于内部cmdlet可以正常工作,但是在与本机二进制文件(尤其是Windows)通信时,事情变得很奇怪。
运行native.exe " “ a "" ,ARGV [1]应该为

"a"

(三个字符)

代替

a

(一个字符)。

当前,要使native.exe正确接收带有两个引号和a字符的ARGV,您必须使用以下奇怪的调用:

native.exe "\`"a\`""

知道了重新开放。

出于好奇,如果尝试使用#1639构建,会发生什么?

@andschwa一样。 您必须加倍努力才能满足PowerShell和CommandLineToArgvW 。 这行:

native.exe "`"a`""

结果一个StartProcess等于cmd

native.exe ""a""

@ be5invis @douglaswth是通过https://github.com/PowerShell/PowerShell/pull/2182解决的

不,我们还需要在反引号转义的双引号之前添加反斜杠吗? 这不能解决双重转义问题。 (也就是说,我们必须对PowerShell和CommandLineToArgvW都使用双引号。)

由于"""等于'"a"' ,您是否建议native.exe '"a"'应该导致"\"a\""

这似乎是一项功能请求,如果实施该功能,可能会破坏大量使用必需的两次转义的PowerShell脚本,因此任何解决方案都需要格外小心。

@vors是的。
@douglaswth双重转义真的很傻:为什么我们需要DOS时代创建的“内部”转义符?

@vors @douglaswth
这是用于显示GetCommandLineW和CommandLineToArgvW结果的C代码:

#include <stdio.h>
#include <wchar.h>
#include <Windows.h>

int main() {
  LPWSTR cmdline = GetCommandLineW();
  wprintf(L"Command Line : %s\n", cmdline);

  int nArgs;
  LPWSTR *szArglist = CommandLineToArgvW(cmdline, &nArgs);
  if (NULL == szArglist) {
    wprintf(L"CommandLineToArgvW failed\n");
    return 0;
  } else {
    for (int i = 0; i < nArgs; i++) {
      wprintf(L"argv[%d]: %s\n", i, szArglist[i]);
    }
  }
  LocalFree(szArglist);
}

这是结果

$ ./a "a b"
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a b'
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a"b'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: ab

$ ./a 'a"b"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: abc

$ ./a 'a\"b\"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a\"b\"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a"b"c

@ be5invis对于双重转义令人讨厌,我并不反对,但是我只是说,对此的更改需要与现有PowerShell脚本使用的向后兼容。

他们有几个? 我认为没有脚本编写者知道这种双引号。 这是一个错误,而不是功能,也没有记录。

???? 苹果手机

? 2016?9?21 ?? 01:58?Douglas Thrift < [email protected] [email protected] > ???

@ be5i nvishttps://github.com/be5invis对于双重转义令人讨厌,我并不反对,但是我只是说,对此的更改需要与现有PowerShell脚本使用的向后兼容。

您收到此邮件是因为有人提到您。
直接回复此电子邮件,在Gi tHub上查看它阅读的https://github.com/notifications/unsubscribe-auth/AAOp20f_W0mTl2YiJKi_flQBJUKaeAnLks5qsB7ZgaJpZin

PowerShell已经存在9年了,因此很可能有很多脚本。 当我遇到对StackOverflow和其他来源的双重转义的需求时,我发现了很多信息,因此我不知道我是否同意您关于没人知道是否需要它或没有记录的说法。 。

对于其他情况,我想谈一谈实现。
PowerShell调用.NET API生成一个新进程,该进程调用Win32 API(在Windows上)。

PS在这里创建了StartProcessInfo
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs#L1063

提供的API使用单个字符串作为参数,然后将其重新解析为参数数组以执行。
重新解析的规则不受PowerShell的控制。 这是一个Win32 API(幸运的是,它在dotnet核心和Unix规则中保持一致)。
特别是,此合同描述了\"行为。

尽管PowerShell可能会尝试变得更聪明并提供更好的体验,但是当前行为与cmd和bash一致:您可以从它们中复制本机可执行行,并在powershell中使用它,并且工作原理相同。

@ be5invis如果您知道一种以不间断的方式增强体验的方法,请排列详细信息。 对于重大更改,我们将需要使用RFC流程,如https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md中所述

这适用于Windows,但是在Linux或Unix上运行命令时,奇怪的是,需要将转义引号加倍。

在Linux上,进程没有单个命令行,而是一个参数数组。
因此,powershell中的参数应该与传递给可执行文件的参数相同,而不是合并所有参数然后重新分割。

即使在Windows上,当前行为也不一致:
如果参数不包含空格,则原样传递。
如果参数包含空格,则将其括在引号中,以通过CommandLineToArgvW调用将其保持在一起。 =>参数已更改以满足CommandLineToArgvW要求。
但是,如果参数包含引号,则不会转义这些引号。 =>尽管CommandLineToArgvW需要此参数,但参数未更改。

我认为参数应该永远不改变,或者总是为了满足CommandLineToArgvW要求而改变,但在一半情况下不应该改变。

关于违约:
由于我找不到有关双重转义的任何官方文档,因此我将其视为“类别2:合理的灰色区域”类别,因此有机会对此进行更改,还是我错了?

@vors如果您的参数是变量或其他变量,这将非常烦人:在将其发送到本机应用程序之前,必须手动对其进行转义。
“自动转义”运算符可能会有所帮助。 像^"a" -> "a\ ”“`

我认为@TSlivede纠正了行为上的不一致。

我认为参数不应该更改,或者总是更改以满足CommandLineToArgvW的要求,但在一半情况下不应该更改。

我不确定存储桶,但是即使是“明显突破性的变化”存储桶也有可能被更改。 我们希望使PowerShell更好,但是向后兼容是我们的首要任务之一。 这就是为什么它不那么容易的原因。
我们有一个很棒的社区,我相信我们能找到共识。

有人要启动RFC流程吗?

如果可以避免使用PowerShell为参数添加引号的情况,那么值得研究使用P / Invoke而不是.Net来启动进程。

据我所知, @ lzybkr对PInvoke毫无帮助。
这是unix和Windows API不同的地方:

https://msdn.microsoft.com/zh-cn/library/20y988d2.aspx (将空格作为分隔符)
https://linux.die.net/man/3/execvp (不将空格视为分隔符)

我并不是在建议更改Windows实现。

我会尽量避免在这里出现特定于平台的行为。 它将损害脚本的可移植性。
我认为我们可以考虑以不间断的方式更改Windows行为。 即具有偏好变量。 然后我们可以有不同的默认值或类似的东西。

我们正在谈论调用外部命令-无论如何都依赖于平台。

好吧,我认为它不能真正独立于平台,因为Windows和Linux只是有不同的调用可执行文件的方式。 在Linux中,进程获取参数数组,而在Windows中,进程仅获取单个命令行(一个字符串)。
(比较比较基本
CreateProcess ->命令行(https://msdn.microsoft.com/library/windows/desktop/ms682425)

execve ->命令数组(https://linux.die.net/man/2/execve)

当Powershell在参数中有空格时添加这些引号时,在我看来,powershell尝试**以某种方式传递参数,即CommandLineToArgvW将命令行拆分为最初在powershell中给出的参数。 (通过这种方式,典型的C程序在其argv数组中获得的参数与Powershell函数在$ args中获得的参数相同。)
这与仅将参数传递给linux系统调用(如通过p / invoke所建议的)完全匹配。

**(并且失败,因为它不能转引号)

PS:启动RFC流程需要什么?

确实-PowerShell会尝试确保CommandLineToArgvW生成正确的命令,然后_after_重新解析PowerShell已解析的内容。

这一直是Windows上长期存在的痛点,我认为有理由将这一困难转移给* nix。

在我看来,这就像实现细节,实际上并不需要RFC。 如果我们更改了Windows PowerShell中的行为,则可能需要RFC,但即使这样,正确的更改也可能被认为是(可能有风险的)错误修复。

是的,我也认为,在Linux上更改它以使用直接系统调用将使每个人都感到更加高兴。

我仍然认为也应该在Windows上进行更改,
(也许通过为那些不想更改其脚本的用户添加首选项变量)
因为现在这是错误的-这是一个错误。 如果更正了这一点,那么甚至不需要在Linux上进行直接syscall,因为任何参数都将不变地到达下一个进程。

但是由于存在可执行文件,它们以某种方式拆分了命令行,与CommandLineToArgvW不兼容,所以我喜欢@ be5invis的自变量运算符的想法-但我不会创建自动转义运算符(应该是所有参数的默认值),而是添加一个运算符以不对参数进行转义(不添加引号,不对任何内容进行转义)。

今天,当有人在PowerShell中尝试以下命令,并在无法运行而CMD却无法运行PowerShell时,就出现了此问题:

wmic useraccount where name='username' get sid

从PSCX echoargs,wmic.exe看到以下内容:

94> echoargs wmic useraccount where name='tso_bldadm' get sid
Arg 0 is <wmic>
Arg 1 is <useraccount>
Arg 2 is <where>
Arg 3 is <name=tso_bldadm>
Arg 4 is <get>
Arg 5 is <sid>

Command line:
"C:\Users\hillr\Documents\WindowsPowerShell\Modules\Pscx\3.2.2\Apps\EchoArgs.exe" wmic useraccount where name=tso_bldadm get sid

那么CMD.exe使用什么API来调用进程/形成命令行? 因此,-%使该命令起作用了吗?

@rkeithhill CreateProcessW 。 直接致电。 真。

为什么Powershell在这两种情况下的行为不同? 具体来说,它不一致地将包含空格的args括在双引号中。

# Desired argv[1] is 4 characters: A, space, double-quote, B
$ .\echoargs.exe 'A \"B'
<"C:\test\echoargs.exe" "A \"B">
<A "B>
# Correct!

# Desired argv value is 4 characters: A, double-quote, space, B
$ .\echoargs.exe 'A\" B'
<"C:\test\echoargs.exe" A\" B>
<A"> <B>
# Wrong...

似乎没有任何押韵或理由。 在第一种情况下,它用双引号将我的arg换行,但在第二种情况下,则没有。 我需要确切地知道何时使用双引号将其换行,以便可以在脚本中手动换行(或不自动换行)。

通过使用cl echoargs.c编译以下内容来创建。\ echoargs.exe

// echoargs.c
#include <windows.h>
#include <stdio.h>
int wmain(int argc, WCHAR** argv) {
    wprintf(L"<%ls>\n", GetCommandLineW());
    for(int i = 1; i < argc; i++) {
        wprintf(L">%s< ", argv[i]);
    }
    wprintf(L"\n");
}

编辑:这是我的$ PSVersionTable:

Name                           Value
----                           -----
PSVersion                      5.1.15063.296
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.296
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

关于引号的行为已多次更改,因此,我建议使用如下所示的内容:

编辑:下面更新了表格
旧版本:

# call helper

function Run-Native($command) {
    $env:commandlineargumentstring=($args | %{'"'+ ($_ -replace '(\\*)"','$1$1\"' -replace '(\\*)$','$1$1') + '"'}) -join ' ';
    & $command --% %commandlineargumentstring%
}

# some test cases

Run-Native .\echoargs.exe 'A "B' 'A" B'
Run-Native .\echoargs.exe 'A "B'
Run-Native .\echoargs.exe 'A" B'
Run-Native .\echoargs.exe 'A\" B\\" \'

输出:

<"C:\test\echoargs.exe"  "A \"B" "A\" B">
<A "B> <A" B>

<"C:\test\echoargs.exe"  "A \"B">
<A "B>

<"C:\test\echoargs.exe"  "A\" B">
<A" B>

<"C:\test\echoargs.exe"  "A\\\" B\\\\\" \\">
<A\" B\\" \>

第一个-replace会将引号前面的反斜杠加倍,并添加一个额外的反斜杠以逃避qoute。
第二个-replace在参数末尾加倍反斜杠,以使结束引号不转义。

这使用--% (PS v3及更高版本),这是AFAIK将引号传递给本机可执行文件的唯一可靠方法。


编辑:

Run-Native更新版本,现在称为Invoke-NativeCommand建议

function Invoke-NativeCommand() {
    $command, [string[]] $argsForExe = $args
    if($argsForExe.Length -eq 0){
        & $command
    } else {
        $env:commandlineargumentstring=($argsForExe | %{
            if($_ -match '^[\w\d\-:/\\=]+$'){
                $_ #don't quote nonempty arguments consisting of only letters, numbers, or one of -:/\=
            } else {
                $_ <# double backslashes in front of quotes and escape quotes with backslash #> `
                    -replace '(\\*)"','$1$1\"' `
                   <# opening quote after xxx= or after /xxx: or at beginning otherwise #> `
                    -replace '^([\w\d]+=(?=.)|[/-][\w\d]+[:=](?=.)|^)','$1"' `
                   <# double backslashes in front of closing quote #> `
                    -replace '(\\*)$','$1$1' `
                   <# add closing quote #> `
                    -replace '$','"'
            }
        }) -join ' ';
        & $command --% %commandlineargumentstring%
    }
}

(从iep得到一些启发)

  • 没有引用简单的开关
  • 仍然适用于空参数
  • 如果不存在args则工作
  • 应该主要与msiexec,cmdkey等一起使用...
  • 仍然对遵循通用规则的程序始终有效
  • 不使用非标准""作为转义的" -因此,对于.bat或msiexec参数中的嵌入式引号仍将不起作用

谢谢,我不知道--% 。 有什么方法可以做到而又不会将环境变量泄漏给本地进程? (以及它可能调用的任何进程)

是否有一个PowerShell模块实现了供每个人使用的Run-Native Cmdlet? 这听起来像应该在Powershell Gallery中。 如果足够好,它可能是RFC的基础。

“泄漏”听起来像您在担心安全性。 但是请注意,命令行对子进程仍然可见。 (例如:在Windows上gwmi win32_process |select name,handle,commandline|Format-Table ,在Linux上ps -f

如果仍然要避免使用环境变量,则可以使用invoke-expression构造某些内容。

关于RFC:
我认为这样的命令行开关不是必需的,而是默认行为:

https://github.com/PowerShell/PowerShell-RFC/issues/90

我同意应该修复PowerShell的默认行为。 我一直很悲观地认为,由于向后兼容的原因,它永远不会改变,这就是为什么我建议编写模块的原因。 但是,我真的很喜欢RFC允许通过首选项变量重新启用旧的转义行为的方式。

让我用适当的观点总结一下讨论:

  • 显然,我们有一个向后兼容的问题,因此旧行为必须继续保持可用。

  • @TSlivedeRFC提案说明了这一点,同时值得称赞的是指向未来的道路。
    不幸的是,在撰写本文时,他的提案已成为_PR_,甚至还没有被接受为RFC _draft_。


未来的意思是:

  • PowerShell本身就是一个外壳,有望很快摆脱与cmd.exe相关的负担,因此,在调用外部实用程序((通常是控制台/终端应用程序的可执行程序))时,唯一要考虑的因素是:

    • 应该通过_PowerShell_的参数模式解析_only_的规则来指定要传递的参数。

    • 无论该过程产生的_literals_结果如何,都必须将_as-is_作为_individual_参数传递给目标可执行文件。

    • 换句话说:作为用户,您需要重点关注的是_PowerShell_的解析结果将是什么,并且能够依靠该结果按原样传递,而PowerShell会处理所有幕后操作。 -场景编码-如有必要。


_实现未来:

  • 在_Windows_上:

    • 由于_historical_的原因,Windows不允许_not_允许将参数作为文字数组传递给目标可执行文件。 相反,需要使用_pseudo shell语法_编码_all_参数的_single_字符串。 更糟糕的是,最终只能由单个目标可执行文件来解释单个string_并将其拆分为参数。

    • 最好的PowerShell可以以_predictable标准化的方式将单个字符串-在幕后执行了_own_的命令行拆分成各个参数后,在幕后形成。

    • @TSlivedeRFC提议通过建议PowerShell合成伪Shell命令行,从而使Windows C / C ++运行时在执行其解析时按原样恢复输入参数,从而提出了这一建议:

      • 鉴于最终要由每个目标可执行文件来解释命令行,因此没有_guarantee_可以在所有情况下都起作用,但是说规则是最明智的选择,因为_most_现有实用程序使用这些约定。

      • 唯一值得注意的例外是_batch files_,它可以接受特殊处理,如RFC提案所建议的。

  • 在_Unix_平台上:

    • 严格来说,困扰Windows参数解析_need的问题永远不会出现_,因为平台本机要求创建新进程_accept参数作为文字数组_-PowerShell在执行_own_解析后最终以何种参数应仅在_as-is_上传递。
      引用@lzybkr的话:“我认为没有理由将困难转移给* nix。”

    • 遗憾的是,由于.NET Core(CoreFX)当前的局限性,这些问题_do_起作用了,因为CoreFX API甚至通过要求使用伪命令行,也不必要地迫使_Windows_参数的无政府状态传递到Unix世界。 Unix。

    • 我已经创建了此CoreFX问题,要求解决该问题。

    • 同时,鉴于CoreFX根据上述C / C ++规则将伪命令行拆分回参数, @ TSlivede的建议也应在Unix平台上运行。

由于https://github.com/PowerShell/PowerShell/issues/4358已作为与此副本的副本被关闭,因此该问题的简短摘要:

如果带有后缀反斜杠的外部可执行文件的参数包含空格,则当前将其天真地加引号(在参数前后添加引号)。 遵循通常规则的任何可执行文件都将这样解释:
来自@ mklement0评论

".\test 2\"的第二个"由于前面带\,因此被解释为转义的“,导致字符串的其余部分-尽管随后缺少闭包”也被解释为同样的论点。

例:
(来自@akervinen评论

PS X:\scratch> .\ps-args-test.exe '.\test 2\'
收到的参数: .\test 2"

由于PSReadLine在目录的自动完成中添加了反斜杠,因此该问题经常发生。

由于corefx似乎可以生产我们需要的api,因此我将其推迟到6.1.0。 对于6.0.0,我将看看是否可以修复#4358

@TSlivede我接受了您的函数,将其重命名为Invoke-NativeCommand (因为Run不是有效的动词),并添加了别名^并将其作为模块发布在PowerShellGallery上:

install-module NativeCommand -scope currentuser
^ ./echoargs 'A "B' 'A" B'

@ SteveL-MSFT:

暂时有一个权宜之计,但是麻烦一点的是-在我们等待CoreFX解决方案的同时-自行实施@TSlivede的RFC提案中详细介绍的定义明确的官方引号/参数解析规则-这没有听起来很难做。

如果仅解决\"问题,则即使在诸如以下这样的简单情况下,参数传递仍然从根本上被破坏:

PS> bash -c 'echo "hi there"'
hi    # !! Bash sees the following tokens:  '-c', 'echo hi', 'there'

我认为在这一点上,对于行为应该有足够的共识,因此我们不需要完整的RFC流程,对吗?

唯一悬而未决的决定是如何处理_Windows_中的向后兼容性问题。

@ mklement0 @ SteveL-MSFT
我们已经破坏了兼容性吗?

唯一的决定是如何处理Windows中的向后兼容性问题。

是的,但这是最困难的部分,对吧?

@ be5invis “已经破坏兼容性”是什么意思?

另外,如果CoreFX即将在其层中进行修复,那么我宁愿不要在他们之前在层中创建权宜之计。

就像上面线程中的某人所说的那样,这很烦人,但是在社区中也有很好的记录。 我不确定我们是否应该在接下来的两个版本中将其破坏两次

@joeyaiello

#4358的解决方案对于已经通过将最终\加倍的方法来解决该问题的人员来说,已经不是一个巨大的改变; 例如, "c:\tmp 1\\" ? 换句话说:如果将更改限制在此修订范围内,则可以保证_two_的重大更改:现在是一个,在切换到将来的CoreFx API之后是另一个。 如果现在要实施一个完全的权宜之计,这种情况也会发生,但是鉴于我们对这一即将到来的变化的了解,这种可能性很小。

相反,如果常见的引用方案(例如,
bash -c 'echo "hi there"'无法正常工作。

我确实意识到解决此问题是一个更大的突破性变化。

@ PowerShell / powershell-committee对此进行了讨论,并同意使用--%至少应具有与bash相同的行为,因为转义了引号,以便本机命令可以接收引号。 仍然存在争议的是,这是否应该是不使用--%的默认行为

注意:

  • 我假设在_Unix_上使用--%时需要对实际的shell可执行文件进行调用,而不是尝试_emulate_ shell行为,这是在_Windows_上发生的。 在Windows上进行仿真并不难,但在Unix上则要困难得多,因为还需要仿真更多的功能。

  • 然后,使用实际的shell会出现一个问题:要使用哪种shell: bash普遍存在,但其默认行为不符合POSIX,也不是POSIX要求的,因此为了可移植性,其他脚本语言要求/bin/sh ,由POSIX递减的shell可执行文件(可以是在兼容模式下运行的Bash(例如,在macOS上),但不必(例如,在Ubuntu上的Dash))。

可以说,我们也应该以/bin/sh为目标-但是,这意味着某些Bash功能-特别是括号扩展,某些自动变量...-将不可用


--%

我将在下面的示例中使用命令echoargs --% 'echo "hi there"'

与bash相同的行为是,转义了引号,以便本机命令接收它们。

一旦扩展了CoreFX API,将来的做法是完全不进行转义,而是执行以下操作:

  • 创建一个过程,如下所示:

    • /bin/sh作为可执行文件,(有效地)分配给ProcessStartInfo.FileName

    • 以下数组_literal_和_individual_参数标记为ProcessStartInfo.ArgumentList

    • -c作为第一个参数

    • echoargs 'echo "hi there"'作为第二个参数-即,原始命令行使用_literally_,完全按照指定的方式使用,只是删除了--%

实际上,命令行是通过_as-is_传递给​​Shell可执行文件的,该可执行文件随后可以执行_its_解析。

我了解,在当前缺乏基于数组的传递文字参数的方法的情况下,我们需要将-cechoargs 'echo "hi there"'合并到带有escaping_的_single_字符串中,遗憾的是_仅出于CoreFX API_,当需要创建实际流程时,然后_reverses_此步骤并将单个字符串拆分回文字标记中-并确保这种反转始终导致原始的文字标记列表是具有挑战性的部分。

再说一遍:涉及转义的唯一原因是当前的CoreFX限制。
要使用此限制,必须将以下单个转义的字符串分配给ProcessStartInfo实例的.Arguments属性,并按解析C ++命令行参数的指定执行转义:

  • /bin/sh作为可执行文件,(有效地)分配给ProcessStartInfo.FileName
  • 以下单个转义的字符串作为ProcessStartInfo.Arguments
    -c "echoargs 'echo \"hi there\"'"

##默认行为

仍然存在争议的是,这是否应为不使用-%的默认行为

Unix上的默认行为应该有很大的不同:

  • 没有任何逃避的注意事项_除了PowerShell的own_之外,别无其他考虑(可悲的是_Windows_除外,但_Windows_除外;但是MS C ++规则是可以使用的方法,_要在幕后应用_;否则, --%提供逃生舱口)。

  • 无论参数_PowerShell_最终以何种形式结束,在对其自身进行解析之后,都必须通过即将到来的ProcessStartInfo.ArgumentList属性作为literal_的数组传递

应用于不带--%的示例: echoargs 'echo "hi there"'

  • PowerShell执行其常规解析,并以以下两个参数结尾:

    • echoargs
    • echo "hi there" (单引号-仅对_PowerShell_具有语法功能,已删除)
  • 然后以如下方式填充ProcessStartInfo ,并准备好即将推出的CoreFX扩展:

    • echoargs作为(有效) .FileName属性值
    • _Literal_ echo "hi there"作为唯一的元素添加到Collection<string>通过暴露实例.ArgumentList

再次,在没有.ArgumentList ,不是选项,而是在过渡期间,可以采用如上所述的与MS C ++兼容的辅助转义。

@ SteveL-MSFT
正如我在使停止解析符号(-%)在Unix(#3733)上已经提到的那样,我强烈建议不要更改--%的行为。

如果需要/bin/sh -c某些特殊功能,使用其他符号,并按原样保留--%

@TSlivede

_If_有些--% -_like_是在Unix上实现的-并且在Unix上具有本机遍历和通常更精通命令行的人群,我认为对它的需求较少-然后选择_不同的符号_-例如--$ -可能是有道理的(对不起,我忘了这场冗长的,多问题的辩论的所有方面)。

不同的符号还将充当视觉上明显的提醒,即正在调用不可移植的_platform-specific_行为。

这使得当它遇到什么样的PowerShell应该做些什么的问题--%在Unix和--$在Windows上。

我可以按原样保留--% 。 引入类似--$来调用/ bin / sh,我猜想Windows上的cmd.exe可能是解决此问题的好方法。

没有机会为这些行为创建cmdlet吗?

@iSazonov您是否建议类似Invoke-Native ? 不确定我是否喜欢这个。

是的,例如Start-Native
开个玩笑:-),您不喜欢PowerShell中的cmdlet吗?

在Build.psm1中,我们有Start-NativeExecution链接到https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/

@ SteveL-MSFT

我可以按原样离开-%。

我认为我们都同意--%必须继续在_Windows_上发挥作用。

相比之下,在_Unix_上,此行为没有任何意义,正如我试图在此处进行演示-简而言之:

  • 单引号处理不正确
  • 引用环境变量的唯一方法是_cmd.exe-style_( %var%
  • 重要的本地功能(例如,通行和分词)不起作用。

如果我理解正确,引入--%的主要动机是按原样启用_use $$$ cmd.exe命令行。

  • 因此, --%在Unix上由于其当前行为是无用的。
  • 如果我们想要在Unix上使用_analogous_功能,则它必须基于/bin/sh -c ,如上所述,可能使用了不同的符号。

我认为Windows上不需要基于cmd /c的功能,因为--%涵盖了_mostly_,可以说是足够好了。
@TSlivede指出并非所有的shell功能都在被仿真,但实际上似乎并%envVar:old=new%类的变量值替换, ^不是转义字符,并且--%的使用仅限于_single_命令-不使用cmd.exe的重定向和控制运算符;也就是说,我认为--%曾经用来模拟整个命令_lines_)。

这样,像--$这样的东西(如果实现的话)将是Unix的--%

无论如何,至少about_Parsing帮助主题值得一提的是,在少数情况下

@iSazonov可能需要Start-Native处理某些特定情况,但是我们应该尝试改进PowerShell,以便使用本机exe更加自然和可预测

@ PowerShell / powershell-committee对此进行了审查,并同意--%应该意味着在相关平台上对待参数,这意味着它们在Windows和Linux上的行为不同,但在Windows内和Linux之间是一致的。 引入新的标记将使用户更加困惑。 我们将把实现方法留给工程师。

跨平台脚本必须意识到这种行为上的差异,但是用户似乎不太可能实现这一点。 如果用户的反馈是由于需要更多的跨平台使用,那么我们可以重新考虑引入新的标识。

这是我第二次被这些native vs cmdlet字符串参数咬伤,因为它们在使用ripgrep时解析差异。

这是Powershell调用的结果(echo.exe来自“ C:\ Program Files \ Git \ usr \ bin \ echo.exe”)

现在我知道我应该注意这个"怪癖:

> echo.exe '"test'
test

但是这个怪癖超出了我...

echo.exe '^\+.+;'
^\+.+;
echo.exe '^\+.*;'
^+.*;

在第二种情况下,我需要加倍\来将\传递给本机命令,在第一种情况下,我不需要这样做

我知道这是更改此行为的重大更改,因此cmlet和本机命令之间不会有任何区别。 但是,我认为像这样的怪异之处使人们无法使用Powershell作为默认Shell。

@mpawelski我刚刚在我的Windows机器上使用git 2.20.1.vfs.1.1.102.gdb3f8ae尝试了这一点,而对于6.2-RC.1来说,它对我来说不是正确的。 多次运行它并始终回显^\+.+;

@ SteveL-MSFT,我认为@mpawelski偶然两次指定了同一命令。 例如,如果您传递'^\"+.*;' ,则会看到问题-注意\"部分-具有-合理的-期望单引号字符串的内容将按原样传递,以便外部目标程序将^\"+.*;视为参数的值:

# Note how the "\" char. is eaten.
PS> bash -c 'printf %s "$1"' - '^\"+.*;'
^"+.*; 

#"# Running the very same command from Bash does NOT exhibit the problem:
$ bash -c 'printf %s "$1"' - '^\"+.*;'
^\"+.*; 

-%,如果我理解正确的话,是按原样启用现有cmd.exe命令行的重用。

不完全是。 引入--%是因为许多Windows命令行实用程序都使用由PowerShell解释的args,从而完全阻止了该实用程序的调用。 如果他们不再能够轻松使用自己喜欢的本机utils,这会使人们很快感到不适。 如果我每次有四分之一的时间都回答了SO以及其他由于该问题而无法正常运行的RE本机exe命令的问题,那么我可能可以带全家人去Qoba吃晚饭。 :-)例如, tf.exe允许;作为指定工作区的一部分。 Git允许{}@~1等,等等,等等。

--%来告诉PowerShell不要解析命令行的其余部分-只需按原样将其发送到本机exe。 一擦,使用cmd env var语法允许变量。 它有点丑陋,但是,在Windows上,确实可以派上用场。

RE将其作为cmdlet,我不确定是否知道它是如何工作的。 --%是向解析器发出的信号,使解析器哑巴直到EOL。

坦率地说,作为PowerShell的长期用户,尤其是使用此功能,对我来说,在其他平台上使用相同的运算符只是意味着-简化解析直到EOL对我来说是有意义的。 存在如何允许某种形式的变量替换的问题。 即使感觉有些难看,在macOS / Linux上,您也可以使用%envvar%并替换任何相应env var的值。 然后,它可以在平台之间移植。

关键是,如果您不这样做,那么您将获得条件代码-不完全是我所说的可移植代码:

$env:Index = 1
if ($IsWindows) {
    git show --% @~%Index%
}
else {
    git show --$ @~$Index
}

我更喜欢在所有平台上进行这项工作:

$env:Index = 1
git show --% @~%Index%

由于兼容性,Windows上的行为必须保持原样。

@rkeithhill

不完全是。

通过消除过程,在Windows世界中早于PowerShell的命令行是为cmd.exe -编写的,而这正是--%打算模仿的东西,如以下所示:

  • --%扩展了cmd.exe -style %...%环境变量引用,例如%USERNAME% (如果不涉及任何shell且没有特殊逻辑,则将传递此类令牌_逐字_)

  • 直接从PowerShell的口中(如果您愿意,可以添加重点):

Web上充满了为Cmd.exe编写的命令行。 这些命令行在PowerShell中通常可以正常工作,但是当它们包含某些字符(例如,分号(;),美元符号($)或花括号)时,您必须进行一些更改,可能添加一些引号。 这似乎是许多轻微头痛的根源。

为了帮助解决这种情况,我们添加了一种新的方法来“逃避”命令行的解析。 如果您使用魔术参数-%,我们将停止对命令行的常规分析,并切换到更简单的命令。 我们不匹配报价。 我们不止于分号。 我们不扩展PowerShell变量。 如果您使用Cmd.exe语法(例如%TEMP%),我们会扩展环境变量。 除此之外,直到行(或管道,如果您是管道)末尾的参数都按原样传递。 这是一个例子:

请注意,这种方法不适合_Unix_世界(从上面回顾):

  • 诸如*.txt类的未加引号的参数将无法正常工作。

  • Unix世界中的传统(类似于POSIX的)shell不会使用%...%来引用环境变量。 他们期望使用$...语法。

  • 不幸的是,Unix世界中没有_raw_命令行:任何外部可执行文件都必须传递_literals_的_array_,因此仍然是PowerShell或CoreFx必须首先将命令行解析为参数。

  • Unix世界中的传统(类似于POSIX的)shell接受'...' (单引号)字符串,而--%无法识别-参见#10831。

但是,即使在Windows世界中, --%也有严格的,非显而易见的限制

  • 最明显的是,如果使用--% ,则不能在命令行中直接引用PowerShell变量; 唯一麻烦的解决方法是临时定义_environment_变量,然后必须使用%...%进行引用。
  • 您不能将命令行包含在(...) -因为结束的)被解释为命令行的文字部分。
  • 您不能在命令行后加上;和另一条语句-因为;被解释为命令行的文字部分。
  • 您不能在单行脚本块内使用--% -因为结尾的}被解释为命令行的文字部分。
  • 您不能使用重定向-因为它们被视为命令行的文字部分-但是,您可以使用cmd --% /c ... > file ,让cmd.exe处理重定向。
  • 您不能使用行继续字符-PowerShell的( ` )或cmd.exe的( ^ )-它们都将被视为_literals_。

    • --%仅解析(最多)到行尾。


幸运的是,我们已经拥有一个跨平台的语法:_PowerShell自己的语法_。

是的,使用该方法需要您知道_PowerShell_认为元字符是什么,这是cmd.exe和类似POSIX的外壳(例如Bash)所考虑的元字符的_superset_,但这就是为更丰富的平台所付出的代价-不可知的命令行经验。

那么,PowerShell处理"字符的引用是如此糟糕是多么不幸的-这是本期的主题,本文档期中对此进行了总结。

@rkeithhill ,我开始一点切线; 让我尝试关闭它:

  • 从PowerShell Core 6.2.0开始实际上已经实现了--% ; 例如,
    /bin/echo --% %HOME%打印环境变量HOME ; 相比之下,
    /bin/ls --% *.txt将无法按预期方式工作,因为*.txt是作为文字传递的。

  • 最终,当不使用--% ,我们需要帮助用户诊断在幕后“最终”构造的命令行/参数数组的外观(这使我们回到了古老的#1761):

    • 您有用的echoArgs.exe就是这样做的,并且在链接的问题中,您明智地要求将此类功能作为PowerShell本身的一部分。
    • @ SteveL-MSFT会考虑将结果命令行包含在错误记录中。

最后,将我先前的参数(使用PowerShell自身的语法作为固有可移植的语法)应用于您的示例:

# Works cross-platform, uses PowerShell syntax 
# Note: No need for an aux. *environment* variable (which should be cleaned up afterward)
$Index = 1
git show "@~$Index"

# Alternative, quoting just the '@'
git show `@~$Index

是的,它要求您知道令牌初始化的@是元字符,因此必须引用它,但是随着PowerShell的使用越来越广泛,人们对此类要求的认识也应越来越广泛。

仅供参考,这个问题在Windows和类似Unix的系统上应该几乎相同。 Unix SDProcess的CoreFx实现有一个叫做ParseArgumentsIntoList ,它几乎完全实现CommandLineToArgvW而没有_setargv开关(并且引号中没有记录的"""功能)。 Unix不应该再为此感到痛苦,因为在目前的情况下,它的破坏方式与Windows相同。

毕竟不是每个程序都使用_setargv ,并且可能不值得考虑,因为,好吧,它在CRT版本之间的行为更改上有点不足。 我们能做的最好的事情是用双引号将所有内容引起来,并添加一些不错的后缀,仅此而已。

另一个无法正确解析args的示例:

az "myargs&b"

在这种情况下,az将获得myargs并且尝试将b作为新命令执行。

解决方法是:az-%“ myargs&b”

@ SteveL-MSFT,我们可以删除Waiting - DotNetCore标签,因为从.NET Core 2.1开始,必需的功能-基于集合的ProcessStartInfo.ArgumentList属性已经可用

@TSlivede知道这种新方法并计划使用它,但是不幸的是,相关的RFC https://github.com/PowerShell/PowerShell-RFC/pull/90一直处于衰落状态。

我建议我们在此继续进行_implementation_讨论。

但是,在RFC讨论中, @ joeyaiello讨论了使更改成为实验功能,但对我而言,越来越清楚的是,如果不大规模破坏现有代码,就无法解决引用行为:

任何必须解决的人:

  • 无法传递空字符串参数( foo.exe ""当前传递_no_参数)
  • 由于缺少自动转义嵌入的双引号引起的对双引号的意外有效删除( foo.exe '{ "foo": "bar" }'作为不正确的转义"{ "foo": "bar" }"被传递)
  • 不接受某些参数的某些CLI(例如msiexec的怪癖将_整体上双引号( foo.exe foo="bar none"作为"foo=bar none"传递)。
    注意:这是msiexec责任,并且在应用了建议的更改后,传递所需的foo="bar none"报价形式将需要--%

将会遇到麻烦,因为解决方法将无法应用建议的更改。

因此,另一个问题是:

  • 我们如何至少将_opt-in_功能用作正确的行为?

    • 如果使用Start-Process引用报价被破坏,我们至少可以选择为正确的行为引入一个新参数,但是直接调用显然不是一个选择(除非我们引入另一个“魔术符号”,例如作为--% )。
  • 这种机制的一个基本问题-PowerShell的动态作用域;通常是按首选项变量,但也越来越多地由using语句增加。 也就是说,默认情况下,选择加入的行为也会应用于称为_from_一个人的选择加入的代码的代码,这是有问题的。

    • 也许现在是时候一般地介绍_lexical_功能范围了,这是@lzybkr编写的-同样令人生厌的-词法严格模式RFC中对词法范围的using strict提议的概括。

    • 像按词法作用的using preference ProperArgumentQuoting ? (姓名显然可以商议,但我正在努力想出一个)。

考虑到所有这些注意事项,并且这也是直接调用的问题,我们不能简单地添加新参数,因此我坚决赞成打破旧的行为。

是的,它可能会破裂很多。 但实际上,仅仅是因为它一开始就被彻底打破了。 我认为优先考虑维护对破坏行为的大量解决方法而不是具有实际上起作用的功能不是特别可行。

作为新的主要版本,我认为v7确实是我们在相当长的一段时间内正确纠正这种情况的唯一机会,我们应该抓住这个机会。 只要我们能使用户意识到,我认为总体上不会很好地接受这种过渡。

如果我们认为突破性变化是不可避免的,那么也许我们应该设计理想的解决方案,同时考虑到它应该易于在交互式会话中进行打印,并且可能有一个脚本版本可以在所有平台上更好地工作。

同意, @ vexx32 :即使仅默认情况下,保留现有行为仍将是一个长期的痛点。 用户不希望选择加入,并且一旦这样做,他们很可能会偶尔忘记和/或对每次使用它的需求感到不满。

无法可靠地将参数传递给外部程序的shell失败了其核心任务之一。

您当然会投票赞成做出重大更改,但我担心其他人会有所不同,尤其是因为吹捧v7允许长期WinPS用户迁移到PSCore。


@iSazonovhttps :

概括其精神:

作为外壳,PowerShell需要根据_its_规则解析参数,然后将生成的扩展后的参数值_verbatim_传递给目标程序-用户永远不必考虑PowerShell是如何实现的; 他们所需要担心的就是正确使用_PowerShell_语法。

  • 在类似Unix的平台上, ProcessStartInfo.ArgumentList现在为我们提供了一种完美实现此方法的方法,因为可以将_array_扩展参数值_as-is_传递给​​目标程序,因为这是合理地传递参数的方式在这世上。

  • 在Windows上,我们必须处理Windows命令行解析这一无政府状态的不幸现实,但是,作为外壳程序,它应该尽可能地使其仅在后台工作,这是RFC所需要的。描述-尽管我刚刚发现仅使用ProcessStartInfo.ArgumentList的皱纹还不够好,但是(由于@ steveL-MSFT的az[.cmd]证明了,由于_batch files_作为CLI的入口点很普遍) --%

PSSA可能会通过警告用户使用将要更改的参数格式来帮助缓解重大变化。

我认为我们需要考虑采用“可选功能”之类的东西,以便与其他一些东西一起在这一重大变化上向前发展。

维持现有行为真的有任何价值吗? 除了向后兼容之外,我的意思是。

我认为为此保留两条代码路径并不值得,只是保留一个残破的实现,因为某些旧代码可能偶尔需要它。 我认为期望folx偶尔更新其代码不是不合理的。 😅

令人怀疑的是,如果我们期望PS7能够替代WinPS,那么它将会是什么? 我能看到保留v7行为的唯一原因是,如果我们希望人们使用相同的脚本在5.1和7上运行命令,(希望)如果PS7可以很好地替代5.1,这是非常罕见的情况。

即便如此,用户同时考虑这两者也不会太困难。 如果我们不更改实际的语言语法,则执行以下操作应该很容易:

if ($PSVersionTable.PSVersion.Major -lt 7) {
    # use old form
}
else {
    # use new form
}

如果我们让用户意识到它们之间的区别,那么我认为从现在为止处理PS中奇怪的本机可执行文件的痛苦中,这将是令人欣慰的缓解。 😄

正如@TylerLeonhardt在可选功能的讨论中提到的那样-实现意味着您现在维护_multiple_个不同的实现,每个实现都需要维护和测试,以及维护和测试可选功能框架。 看来,这确实不值得,TBH。

@ vexx32向后兼容性在这里是一个大问题。 这不是将args传递给本机可执行文件的唯一问题。 我想将它们全部打包,并全部作为一个“可选功能”。 例如, https://github.com/PowerShell/PowerShell/issues/1761https://github.com/PowerShell/PowerShell/issues/10675。 我想针对vNext解决与本地命令的“修复”互操作性。 因此,如果有人看到此类别中的任何现有或新问题,请抄送我,我将对其进行适当的标记(或者,如果您具有分类权限,则像其他标记一样对其进行标记)。

可选功能是模块:-)在Engine中具有可选功能对于支持(尤其是Windows支持)非常麻烦。 我们可以通过减少内部依赖关系并用公共替代内部API来对Engine进行模块化-此后,我们可以轻松地在Engine中实现可选功能。

@iSazonov我的团队将在vNext中研究的一件事是使引擎更具模块化:)

对于最终用户,这里推荐的解决方案是什么?

这是人为设计的,但这是从.NET框架本身进行正确的ArgumentList处理的最直接方法:

Add-Type -AssemblyName "System"
function startProcess([string] $FileName, [string[]] $ArgumentList) {
    $proc = ([System.Diagnostics.Process]::new())
    $proc.StartInfo.UseShellExecute = $false
    $proc.StartInfo.FileName = $FileName
    $proc.StartInfo.CreateNoWindow = $true
    foreach ($a in $ArgumentList) { $proc.StartInfo.ArgumentList.Add($a) }
    $proc.Start()
    return $proc
}

startProcess -FileName 'C:\Program Files\nodejs\node.exe' -ArgumentList '-e','console.log(process.argv.join(''\n''))','--','abc" \" messyString'

当然,您可以通过使用位置参数和一些get-command技巧来减少在此处的使用。

*免责声明:在Windows上没有唯一正确的方法来解析cmdline。 “正确”是指从/到argv转换的cmdline的MSVCRT样式,由.NET在所有平台上实现,用于ArgumentList处理, main (string[] args)处理以及在Unix上的外部派生调用。 本示例按原样提供,不能保证一般的互操作性。 另请参阅提议的NodeJS child_process文档的“ Windows命令行”部分

@ Artoria2e5这正是我得出的结论。 System.Diagnostics.Process是运行外部可执行文件的唯一可靠方法,但是由于stdlib规则,转义参数会变得棘手:

2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal " 
N backslashes ==> N backslashes

结果,我想出了以下逻辑来转义参数,将它们包装在双引号"以执行过程:
https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L244

在运行外部可执行文件时实时获取STDOUT和STDERR也可能会很棘手,所以我创建了这个包

https://github.com/choovick/ps-invoke-externalcommand

到目前为止,我在Windows,Linux和Mac上都大量使用,到目前为止没有问题,我可以在其中传递带有换行符和其他特殊字符的参数。

的GitHub
通过在GitHub上创建一个帐户,为choovick / ps-invoke-externalcommand开发做出贡献。
的GitHub
通过在GitHub上创建一个帐户,为choovick / ps-invoke-externalcommand开发做出贡献。

@choovick ,整个stdio重定向功能都很棒!

但是我在转义部分略有不同,因为已经有一个可以为您完成的工作称为ArgumentList。 我确实知道这是一个相对最近的(?)添加,并且确实令人失望,因为MS忘记为SDProcessStartInfo放置String,String []初始化程序。 (这些……NET接口提案有地方吗?)


闲聊

我从该NodeJS示例获得的转义功能与您的有所不同:它使用未记录的(但在.NET Core和MSVCRT中找到) ""引号引起来。 这样做简化了反斜杠的挑选工作。 我这样做主要是因为它被用于所有强大的cmd,后者不了解\"不应取消引用字符串的其余部分。 我没有为\^"而苦苦挣扎,而是想到了从开始就一直秘密使用的东西会更好。

不幸的是, @ Artoria2e5 ArgumentList在Windows的PowerShell 5.1中不可用,使用您的示例,我得到:

```您不能在空值表达式上调用方法。
在C:Users \ yser \ dev \ test.ps1:7 char:37

  • ... oreach($ ArgumentList中的$ a){$ proc.StartInfo.ArgumentList.Add($ a)}
  • ~~ ~~~~

    • CategoryInfo:InvalidOperation:(:) [],RuntimeException

    • FullyQualifiedErrorId:InvokeMethodOnNull

      ```

由于自定义参数转义逻辑...。

闲聊

关于NodeJS中的“ \ ^”`,我想几年前就必须这样做:)

System.Diagnostics.Process是运行外部可执行文件的唯一可靠方法

问题在于您将无法与PowerShell的输出流集成,也不会在管道中获得流式传输行为。

如果您仍想让PowerShell执行调用(绝对可取),这是必需的解决方法的摘要

  • 如果您需要传递带有_embedded_ "字符的参数,则在Windows上将它们加倍_double_,或者使用\转义它们:

    • 在Windows上,当调用_batch files_并且如果您知道目标程序将""理解为转义的" ,请使用$arg -replace '"', '""'

      • 在Windows上最好使用"" (它避免了Windows PowerShell问题,并与将批处理文件用作_stubs_的CLI一起使用,例如Node.js和Azure),但并非所有可执行文件都支持它(尤其是Ruby和Perl)。
    • 否则(始终在Unix上)使用$arg -replace '"', '\"'

    • 注意:在_Windows PowerShell_中,如果该值还包含_spaces_,则仍然不能始终正常工作,因为在值中存在文字\"确实会导致_not_触发将双引号引起来,这与PowerShell Core不同。 例如,传递'3\" of snow'休息时间。

    • 此外,之前上面逸出,必须用双\前一个实例" ,如果他们被视为文字:

      • $arg = $arg -replace '(\\+)"', '$1$1"'
  • 如果需要传递_empty_参数,则传递'""'

    • '' -eq $arg ? '""' : $arg (WinPS替代: ($arg, '""')['' -eq $arg]
  • 仅Windows PowerShell,请勿在PS Core(已解决问题的地方)中执行以下操作:

    • 如果您的参数_containsspaces和_ends in_(一个或多个) \ ,则将尾随的\实例加倍。

      • if ($arg -match ' .*?(\\+)$') { $arg = $arg + $Matches[1] }
  • 如果cmd /批处理文件使用不具有空格的参数(因此_not_触发PowerShell自动双引号)但包含&|<>^,;任何一个(例如, a&b ),请使用_embedded括上double-quoting_以确保PowerShell传递双引号令牌,因此不会中断cmd /批处理文件调用:

    • $arg = '"' + $arg + '"'
  • 如果您需要处理行为不佳的可执行文件,例如msiexec.exe ,请单引号参数:

    • 'foo="bar none"'

如前所述,一旦解决了基本问题


下面是简单的(非高级)函数iep (用于“调用外部程序”),其中:

  • 执行上述所有描述的转义,包括自动特殊壳体为msiexec和宁愿""逸出超过\"根据目标程序。

    • 这个想法是,您可以通过只关注_PowerShell_的字符串语法来传递任何参数,并依靠该函数执行必要的转义,以便目标程序也可以看到PowerShell看到的逐字值。

    • 在PowerShell _Core_中,这应该非常可靠。 在Windows PowerShell中,如果必须使用\"转义(如上所述),您仍然会遇到带有嵌入双引号的情况,这些情况会中断。

  • 保留shell命令调用语法。

    • 只需在命令行中添加iep 即可。

  • 就像直接调用一样,它:

    • 与PowerShell的流集成

    • 通过管道逐行发送输出

    • 根据外部程序的退出代码设置$LASTEXITCODE ; 但是,不能依赖$?

注意:该函数的用途是极简主义的(没有参数声明,没有命令行帮助,短(不规则)名称),因为它的意思是尽可能不引人注目:只需在命令行中加上iep应该管用。

使用EchoArgs.exe示例调用(可通过Chocolatey从带有choco install echoargs -y的_elevated_会话中安装):

PS> iep echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'
Arg 0 is <>
Arg 1 is <a&b>
Arg 2 is <3" of snow>
Arg 3 is <Nat "King" Cole>
Arg 4 is <c:\temp 1\>
Arg 5 is <a \" b>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "" a&b "3\" of snow" "Nat \"King\" Cole" "c:\temp 1\\" "a \\\" b"

上面显示了PowerShell Core输出。 请注意,PowerShell逐字逐句地正确地传递了所有参数,包括空参数。

在Windows PowerShell中,不会正确传递3" of snow参数,因为使用\"转义是由于调用了未知可执行文件(如上所述)。

要验证批处理文件正确传递了参数,可以创建echoargs.cmd作为echoargs.exe的包装器:

'@echoargs.exe %*' | Set-Content echoargs.cmd

调用为iep .\echoargs.cmd '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'

由于现在调用了批处理文件,因此使用""转义,这可以解决从Windows PowerShell调用时的3" of snow问题。

该功能同样适用于类Unix平台,您可以通过创建名为echoargssh shell脚本来进行验证:

@'
#!/bin/sh
i=0; for a; do printf '%s\n' "\$$((i+=1))=[$a]"; done
'@ > echoargs; chmod a+x echoargs

调用为iep ./echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'


重要说明此功能的更完整的版本已经被发布为ienvoke(外部)电子xecutable)在我刚刚出版了一本模块Native ,我鼓励你使用代替。 安装模块
Install-Module Native -Scope CurrentUser
该模块还包含一个insInvoke-NativeShell )命令,该命令解决了#13068中讨论的用例-请参见https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -671572939有关详细信息。

函数iep的源代码(改用模块Native -参见上文):

function iep {

  Set-StrictMode -Version 1
  if (-not (Test-Path Variable:IsCoreClr)) { $IsCoreCLR = $false }
  if (-not (Test-Path Variable:IsWindows)) { $IsWindows = $env:OS -eq 'Windows_NT' }

  # Split into executable name/path and arguments.
  $exe, [string[]] $argsForExe = $args

  # Resolve to the underlying command (if it's an alias) and ensure that an external executable was specified.
  $app = Get-Command -ErrorAction Stop $exe
  if ($app.ResolvedCommand) { $app = $app.ResolvedCommand }
  if ($app.CommandType -ne 'Application') { Throw "Not an external program, non-PS script, or batch file: $exe" }

  if ($argsForExe.Count -eq 0) {
    # Argument-less invocation
    & $exe
  }
  else {
    # Invocation with arguments: escape them properly to pass them through as literals.
    # Decide whether to escape embedded double quotes as \" or as "", based on the target executable.
    # * On Unix-like platforms, we always use \"
    # * On Windows, we use "" where we know it's safe to do. cmd.exe / batch files require "", and Microsoft compiler-generated executables do too, often in addition to supporting \",
    #   notably including Python and Node.js
    #   However, notable interpreters that support \" ONLY are Ruby and Perl (as well as PowerShell's own CLI, but it's better to call that with a script block from within PowerShell).
    #   Targeting a batch file triggers "" escaping, but in the case of stub batch files that simply relay to a different executable, that could still break
    #   if the ultimate target executable only supports \" 
    $useDoubledDoubleQuotes = $IsWindows -and ($app.Source -match '[/\\]?(?<exe>cmd|msiexec)(?:\.exe)?$' -or $app.Source -match '\.(?<ext>cmd|bat|py|pyw)$')
    $doubleQuoteEscapeSequence = ('\"', '""')[$useDoubledDoubleQuotes]
    $isMsiExec = $useDoubledDoubleQuotes -and $Matches['exe'] -eq 'msiexec'
    $isCmd = $useDoubledDoubleQuotes -and ($Matches['exe'] -eq 'cmd' -or $Matches['ext'] -in 'cmd', 'bat')
    $escapedArgs = foreach ($arg in $argsForExe) {
      if ('' -eq $arg) { '""'; continue } # Empty arguments must be passed as `'""'`(!), otherwise they are omitted.
      $hasDoubleQuotes = $arg.Contains('"')
      $hasSpaces = $arg.Contains(' ')
      if ($hasDoubleQuotes) {
        # First, always double any preexisting `\` instances before embedded `"` chars. 
        # so that `\"` isn't interpreted as an escaped `"`.
        $arg = $arg -replace '(\\+)"', '$1$1"'
        # Then, escape the embedded `"` chars. either as `\"` or as `""`.
        # If \" escaping is used:
        # * In PS Core, use of `\"` is safe, because its use triggers enclosing double-quoting (if spaces are also present).
        # * !! In WinPS, sadly, that isn't true, so something like `'foo="bar none"'` results in `foo=\"bar none\"` -
        #   !! which - due to the lack of enclosing "..." - is seen as *2* arguments by the target app, `foo="bar` and `none"`.
        #   !! Similarly, '3" of snow' would result in `3\" of snow`, which the target app receives as *3* arguments, `3"`, `of`, and `snow`.
        #   !! Even manually enclosing the value in *embedded* " doesn't help, because that then triggers *additional* double-quoting.
        $arg = $arg -replace '"', $doubleQuoteEscapeSequence
    }
      elseif ($isMsiExec -and $arg -match '^(\w+)=(.* .*)$') { 
        # An msiexec argument originally passed in the form `PROP="value with spaces"`, which PowerShell turned into `PROP=value with spaces`
        # This would be passed as `"PROP=value with spaces"`, which msiexec, sady, doesn't recognize (`PROP=valueWithoutSpaces` works fine, however).
        # We reconstruct the form `PROP="value with spaces"`, which both WinPS And PS Core pass through as-is.
        $arg = '{0}="{1}"' -f $Matches[1], $Matches[2]
      }
      # As a courtesy, enclose tokens that PowerShell would pass unquoted in "...", 
      # if they contain cmd.exe metachars. that would break calls to cmd.exe / batch files.
      $manuallyDoubleQuoteForCmd = $isCmd -and -not $hasSpaces -and $arg -match '[&|<>^,;]'
      # In WinPS, double trailing `\` instances in arguments that have spaces and will therefore be "..."-enclosed,
      # so that `\"` isn't mistaken for an escaped `"` - in PS Core, this escaping happens automatically.
      if (-not $IsCoreCLR -and ($hasSpaces -or $manuallyDoubleQuoteForCmd) -and $arg -match '\\') {
        $arg = $arg -replace '\\+$', '$&$&'
      }
      if ($manuallyDoubleQuoteForCmd) {
        # Wrap in *embedded* enclosing double quotes, which both WinPS and PS Core pass through as-is.
        $arg = '"' + $arg + '"'
      }
      $arg
    }
    # Invoke the executable with the properly escaped arguments.
    & $exe $escapedArgs
  }
}

@ mklement0令人印象深刻,但这对夫妇在Windows上对我不起作用:

iep echoargs 'somekey="value with spaces"' 'te\" st'

Arg 0 is <somekey="value>
Arg 1 is <with>
Arg 2 is <spaces">
Arg 3 is <te\">
Arg 4 is <st>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" somekey=\"value with spaces\" te\\\" st

这是我的参数测试数组:)

$Arguments = @(
    'trippe slash at the end \\\',
    '4 slash at the end \\\\',
    '\\servername\path\',
    'path=\\servername\path\',
    'key="\\servername\pa th\"',
    '5 slash at the end \\\\\',
    '\\" double slashed double quote',
    'simple',
    'white space',
    'slash at the end \',
    'double slash at the end \\',
    'trippe slash at the end \\\',
    'trippe slash at the end with space \\\ ',
    '\\" double slashed double quote',
    'double slashed double quote at the end \\"',
    '\\\" triple slashed double quote',
    'triple slashed double quote at the end \\\"',
    # slash
    'single slashes \a ^ \: \"',
    'path="C:\Program Files (x86)\test\"'
    # quotes
    'double quote " and single quote ''',
    # windows env var syntax
    "env var OS: %OS%",
    # utf16
    ('"utf16 ETHIOPIC WORDSPACE: \u1361"' | ConvertFrom-Json),
    # special chars
    "newLine`newLine"
    "tab`tab"
    "backspace`bbackspace"
    "carriage`rafter",
    "formFeed`fformFeed",
    # JSON Strings
    @"
[{"_id":"5cdab57e4853ea7b5a707070","index":0,"guid":"25319946-950e-4fe8-9586-ddd031cbb0fc","isActive":false,"balance":"`$2,841.15","picture":"http://placehold.it/32x32","age":39,"eyeColor":"blue","name":{"first":"Leach","last":"Campbell"},"company":"EMOLTRA","email":"[email protected]","phone":"+1 (864) 412-3166","address":"127 Beadel Street, Vivian, Vermont, 1991","about":"Ex labore non enim consectetur id ullamco nulla veniam Lorem velit cillum aliqua amet nostrud. Occaecat ipsum do est qui sint aliquip anim culpa laboris tempor amet. Aute sint anim est sint elit amet nisi veniam culpa commodo nostrud cupidatat in ex.","registered":"Monday, August 25, 2014 4:04 AM","latitude":"-12.814443","longitude":"75.880149","tags":["pariatur","voluptate","sint","Lorem","eiusmod"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Lester Bender"},{"id":1,"name":"Concepcion Jarvis"},{"id":2,"name":"Elsie Whitfield"}],"greeting":"Hello, Leach! You have 10 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e8cd0ac577ab534a4","index":1,"guid":"0be10c87-6ce7-46c4-8dd6-23b1d9827538","isActive":false,"balance":"`$1,049.56","picture":"http://placehold.it/32x32","age":33,"eyeColor":"green","name":{"first":"Lacey","last":"Terrell"},"company":"XSPORTS","email":"[email protected]","phone":"+1 (858) 511-2896","address":"850 Franklin Street, Gordon, Virginia, 4968","about":"Eiusmod nostrud mollit occaecat Lorem consectetur enim pariatur qui eu. Proident aliqua sunt incididunt Lorem adipisicing ea esse do ullamco excepteur duis qui. Irure labore cillum aliqua officia commodo incididunt esse ad duis ea. Occaecat officia officia laboris veniam id dolor minim magna ut sit. Aute quis occaecat eu veniam. Quis exercitation mollit consectetur magna officia sit. Irure ullamco laborum cillum dolore mollit culpa deserunt veniam minim sunt.","registered":"Monday, February 3, 2014 9:19 PM","latitude":"-82.240949","longitude":"2.361739","tags":["nostrud","et","non","eiusmod","qui"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Meyers Dillard"},{"id":1,"name":"Jacobson Franco"},{"id":2,"name":"Hunt Hernandez"}],"greeting":"Hello, Lacey! You have 8 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57eae2f9bc5184f1768","index":2,"guid":"3c0de017-1c2a-470e-87dc-5a6257e8d9d9","isActive":true,"balance":"`$3,349.49","picture":"http://placehold.it/32x32","age":20,"eyeColor":"green","name":{"first":"Knowles","last":"Farrell"},"company":"DAYCORE","email":"[email protected]","phone":"+1 (971) 586-2740","address":"150 Bath Avenue, Marion, Oregon, 991","about":"Eiusmod sint commodo eu id sunt. Labore esse id veniam ea et laborum. Dolor ad cupidatat Lorem amet. Labore ut commodo amet commodo. Ipsum reprehenderit voluptate non exercitation anim nostrud do. Aute incididunt ad aliquip aute mollit id eu ea. Voluptate ex consequat velit commodo anim proident ea anim magna amet nisi dolore.","registered":"Friday, September 28, 2018 7:51 PM","latitude":"-11.475201","longitude":"-115.967191","tags":["laborum","dolor","dolor","magna","mollit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Roxanne Griffith"},{"id":1,"name":"Walls Moore"},{"id":2,"name":"Mattie Carney"}],"greeting":"Hello, Knowles! You have 8 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e80ff4c4085cd63ef","index":3,"guid":"dca20009-f606-4b99-af94-ded6cfbbfa38","isActive":true,"balance":"`$2,742.32","picture":"http://placehold.it/32x32","age":26,"eyeColor":"brown","name":{"first":"Ila","last":"Hardy"},"company":"OBLIQ","email":"[email protected]","phone":"+1 (996) 556-2855","address":"605 Hillel Place, Herald, Delaware, 9670","about":"Enim eiusmod laboris amet ex laborum do dolor qui occaecat ex do labore quis sunt. Veniam magna non nisi ipsum occaecat anim ipsum consectetur ex laboris aute ut consectetur. Do eiusmod tempor dolore eu in dolore qui anim non et. Minim amet exercitation in in velit proident sint aliqua Lorem reprehenderit labore exercitation.","registered":"Friday, April 21, 2017 6:33 AM","latitude":"64.864232","longitude":"-163.200794","tags":["tempor","eiusmod","mollit","aliquip","aute"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Duncan Guy"},{"id":1,"name":"Jami Maxwell"},{"id":2,"name":"Gale Hutchinson"}],"greeting":"Hello, Ila! You have 7 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57ef1556326f77730f0","index":4,"guid":"f2b3bf60-652f-414c-a5cf-094678eb319f","isActive":true,"balance":"`$2,603.20","picture":"http://placehold.it/32x32","age":27,"eyeColor":"brown","name":{"first":"Turner","last":"King"},"company":"DADABASE","email":"[email protected]","phone":"+1 (803) 506-2511","address":"915 Quay Street, Hinsdale, Texas, 9573","about":"Consequat sunt labore tempor anim duis pariatur ad tempor minim sint. Nulla non aliqua veniam elit officia. Ullamco et irure mollit nulla do eiusmod ullamco. Aute officia elit irure in adipisicing et cupidatat dolor in sint elit dolore labore. Id esse velit nisi culpa velit adipisicing tempor sunt. Eu sunt occaecat ex pariatur esse.","registered":"Thursday, May 21, 2015 7:44 PM","latitude":"88.502961","longitude":"-119.654437","tags":["Lorem","culpa","labore","et","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Leanne Lawson"},{"id":1,"name":"Jo Shepard"},{"id":2,"name":"Effie Barnes"}],"greeting":"Hello, Turner! You have 6 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57e248f8196e1a60d05","index":5,"guid":"875a12f0-d36a-4e7b-aaf1-73f67aba83f8","isActive":false,"balance":"`$1,001.89","picture":"http://placehold.it/32x32","age":38,"eyeColor":"blue","name":{"first":"Petty","last":"Langley"},"company":"NETUR","email":"[email protected]","phone":"+1 (875) 505-2277","address":"677 Leonard Street, Ticonderoga, Utah, 1152","about":"Nisi do quis sunt nisi cillum pariatur elit dolore commodo aliqua esse est aute esse. Laboris esse mollit mollit dolor excepteur consequat duis aute eu minim tempor occaecat. Deserunt amet amet quis adipisicing exercitation consequat deserunt sunt voluptate amet. Ad magna quis nostrud esse ullamco incididunt laboris consectetur.","registered":"Thursday, July 31, 2014 5:16 PM","latitude":"-57.612396","longitude":"103.91364","tags":["id","labore","deserunt","cillum","culpa"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Colette Mullen"},{"id":1,"name":"Lynnette Tanner"},{"id":2,"name":"Vickie Hardin"}],"greeting":"Hello, Petty! You have 9 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57e4df76cbb0db9be43","index":6,"guid":"ee3852fe-c597-4cb6-a336-1466e8978080","isActive":true,"balance":"`$3,087.87","picture":"http://placehold.it/32x32","age":33,"eyeColor":"brown","name":{"first":"Salas","last":"Young"},"company":"PLAYCE","email":"[email protected]","phone":"+1 (976) 473-2919","address":"927 Elm Place, Terlingua, North Carolina, 2150","about":"Laborum laboris ullamco aliquip occaecat fugiat sit ex laboris veniam tempor tempor. Anim quis veniam ad commodo culpa irure est esse laboris. Fugiat nostrud elit mollit minim. Velit est laborum ut quis anim velit aute enim culpa amet ipsum.","registered":"Thursday, October 1, 2015 10:59 AM","latitude":"-57.861212","longitude":"69.823065","tags":["eu","est","et","proident","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Day Solomon"},{"id":1,"name":"Stevens Boyd"},{"id":2,"name":"Erika Mayer"}],"greeting":"Hello, Salas! You have 10 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57ed3c91292d30e141d","index":7,"guid":"ef7c0beb-8413-4f39-987f-022c4e8ec482","isActive":false,"balance":"`$2,612.45","picture":"http://placehold.it/32x32","age":36,"eyeColor":"brown","name":{"first":"Gloria","last":"Black"},"company":"PULZE","email":"[email protected]","phone":"+1 (872) 513-2364","address":"311 Guernsey Street, Hatteras, New Mexico, 2241","about":"Laborum sunt exercitation ea labore ullamco dolor pariatur laborum deserunt adipisicing pariatur. Officia velit duis cupidatat eu officia magna magna deserunt do. Aliquip cupidatat commodo duis aliquip in aute dolore occaecat esse ad. Incididunt est magna in pariatur ut do ex sit minim cupidatat culpa. Voluptate eu veniam cupidatat exercitation.","registered":"Friday, June 26, 2015 7:59 AM","latitude":"38.644208","longitude":"-45.481555","tags":["sint","ea","anim","voluptate","elit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Abby Walton"},{"id":1,"name":"Elsa Miranda"},{"id":2,"name":"Carr Abbott"}],"greeting":"Hello, Gloria! You have 5 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57edc91491fb70b705d","index":8,"guid":"631ff8a0-ce4c-4111-b1e4-1d112f4ecdc7","isActive":false,"balance":"`$2,550.70","picture":"http://placehold.it/32x32","age":25,"eyeColor":"brown","name":{"first":"Deirdre","last":"Huber"},"company":"VERBUS","email":"[email protected]","phone":"+1 (871) 468-3420","address":"814 Coles Street, Bartonsville, Tennessee, 7313","about":"Ipsum ex est culpa veniam voluptate officia consectetur quis et irure proident pariatur non. In excepteur est aliqua duis duis. Veniam consectetur cupidatat reprehenderit qui qui aliqua.","registered":"Monday, April 1, 2019 2:33 AM","latitude":"-75.702323","longitude":"45.165458","tags":["labore","aute","nisi","laborum","laborum"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Genevieve Clarke"},{"id":1,"name":"Black Sykes"},{"id":2,"name":"Watson Hudson"}],"greeting":"Hello, Deirdre! You have 8 unread messages.","favoriteFruit":"strawberry"}]
"@
)

System.Diagnostics.Process在使用时确实有限制,这就是为什么我必须编写自己的运行程序,使其具有捕获STDOUT STDERR的能力,然后将它们组合成变量,这对于我的用例是足够的,但并不完美。

编辑:就像您说的那样,在Windows Poweshell 5中似乎有些破例。我在Powershell 6上进行了测试,效果很好! 不幸的是,在Windows情况下,我必须处理Poweshell 5直到7接管它。

令人印象深刻,但这对夫妇在Windows上对我不起作用:

是的,这些限制适用于_Windows PowerShell_,无法假定其可执行文件支持""转义嵌入式" ,如我先前的评论中所述。
如果您愿意在所有调用中都支持转义"" (_most_,但并非Windows上的所有可执行文件都支持),则可以轻松调整该功能。

并且,确认您在编辑中所说的内容:在PowerShell _Core_中:

  • iep echoargs 'somekey="value with spaces"' 'te\" st'工作正常。
  • 您的参数测试数组似乎也可以正常工作。

如果您愿意在所有调用中都支持转义"" (_most_,但并非Windows上的所有可执行文件都支持),则可以轻松地对该功能进行调整。

谢谢,我会在不久的将来尝试一下。 我有时会处理sqlcmd,它肯定不支持"" 。 在这种情况下,很容易提供一个选项,以跳过特定参数的转义逻辑。

Windows解析命令行参数的方式可以在解析C ++命令行参数中找到

Microsoft C / C ++启动代码在解释操作系统命令行上给定的参数时使用以下规则:

  • 参数由空格分隔,空格可以是空格或制表符。

  • 插入符号(^)无法识别为转义字符或定界符。 在传递给程序中的argv数组之前,字符完全由操作系统中的命令行解析器处理。

  • 用双引号引起来的字符串(“

  • 在双引号之前加反斜杠(\“)的解释为字面双引号字符(”)。

  • 反斜杠将按字面意义进行解释,除非它们紧接在双引号之前。

  • 如果偶数个反斜杠后跟一个双引号,则每对反斜杠在argv数组中放置一个反斜杠,并且双引号被解释为字符串定界符。

  • 如果奇数个反斜杠后跟一个双引号,则每对反斜杠在argv数组中放置一个反斜杠,并且双引号被其余的反斜杠“转义”,从而导致字面量放在argv双引号(“)。

_exec,_wexec函数中

字符串中嵌入的空格可能会导致意外行为; 例如,传递_exec字符串"hi there"将导致新进程获得两个参数"hi""there" 。 如果要让新进程打开一个名为“ hi there”的文件,则该进程将失败。 您可以通过引用字符串"\"hi there\""来避免这种情况。

Python如何调用本地可执行文件

Python的subprocess.Popen可以通过将args转换为字符串来正确处理转义,如在Windows中将subprocess.list2cmdline

希望这可以阐明PowerShell如何以更优雅的方式处理此问题,而不是使用--%或两次转义(遵循PowerShell和CMD语法)。 当前的解决方法确实会给Azure CLI(基于python.exe)客户造成错误。

我们仍然没有一个清晰简洁的方法来了解如何处理此问题。 如果使用v7进行简化,Powershell团队中的任何人都可以阐明吗?

好消息是,PS 7.1的重点之一是使调用本机命令更容易。 从有关7.1投资

大多数本机命令在PowerShell中都可以正常工作,但是,在某些情况下,参数解析并不理想(例如正确处理引号)。 目的是使用户能够为任何流行的本机工具剪切示例命令行,将其粘贴到PowerShell中,并且无需使用PowerShell特定的转义就可以正常工作。

因此,也许(希望如此)将在7.1中解决

谢谢@rkeithhill ,但不:

目的是使用户能够为任何流行的本机工具剪切示例命令行,将其粘贴到PowerShell中,并且无需使用PowerShell特定的转义就可以正常工作。

听起来像是对本质上有问题的--% (停止分析符号)的另一种理解?

@ SteveL-MSFT,您能告诉我们更多有关此即将推出的功能的信息吗?


概括地说,这里提出的修补程序是您需要集中精力满足_PowerShell_的语法要求,并且PowerShell会处理所有在幕后转义的操作(在Windows上;在Unix上,现在甚至都不需要这样做了,因为.NET允许我们将一系列逐字标记传递给目标程序)-但毫无疑问,如果必须保持向后兼容性,则必须选择采用此修复程序。

有了此修复程序,仍然需要_some_修饰其他shell的命令行,但是它将更加直接-与--% ,您保留了PowerShell语法和变量扩展的全部功能(带有(...)表达式

  • 您需要将\"替换`" ,但是_only只在"..." _内。

  • 您需要注意,涉及到_no(其他)shell_,以便Bash样式的环境变量引用(例如$USER将_not_起作用(它们将被解释为_PowerShell_变量),除非您将它们替换为等效的PowerShell语法$env:USER

    • 顺便说一句: --%试图通过-尤其是_invariably_-扩展cmd.exe -样式化的环境变量引用(例如%USERNAME%来弥补这一点,但请注意,它不仅不会t支持将Bash样式的引用( $USER )_verbatim_传递给目标程序,但也会意外地在类似Unix的平台上扩展cmd.exe -样式引用,并且无法识别'...'引用。
    • 请参见下文,了解_does_涉及各个平台本机shell的替代方法。
  • 您需要注意,PowerShell具有_additional_元字符,需要逐引号引用/转义; 这些是(请注意, @仅作为参数的_first_ char有问题。):

    • 对于类似POSIX的外壳(例如Bash): @ { } ` (和$ ,如果您想阻止PowerShell进行前期扩展)
    • cmd.exe( ) @ { } # `
    • 个别`转义此类字符。 就足够了(例如printf %s `@list.txt )。

一个人为的例子:

使用以下Bash命令行:

# Bash
$ printf '"%s"\n' "3\" of snow"
"3" of snow"        # output

有了建议修复,所有需要的就是更换\"内部情况"..."与-enclosed参数`"

# PowerShell - WISHFUL THINKING
PS> printf '"%s"\n' "3`" of snow"
"3" of snow"        # output

也就是说,您不必担心'...'内嵌的" ,而在"..."则只需要转义它们即可使_PowerShell_高兴(您也可以这样做与""在这里)。

上面的iep函数实现了此修复(作为权宜之计),因此iep printf '"%s"\n' "3`" of snow"可以正常工作。


将此与当前破坏的行为进行对比,在这种情况下,您需要跳过以下内容才能使命令像Bash一样工作(莫名其妙地需要使用\进行_additional_转义):

# PowerShell - messy workaround to compensate for the current, broken behavior.
PS> printf '\"%s\"\n' "3\`" of snow"
"3" of snow"        # output

有了此修复程序之后,那些希望通过平台的_default shell_使用给定命令行_as-is_的用户将能够使用逐字_here-string_传递给sh -c (或bash -c )/ cmd /c ; 例如:

# PowerShell - WISHFUL THINKING
PS> sh -c @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

请注意,在这里使用--%确实不起作用( printf --% '"%s"\n' "3\" of snow" ),基于这里字符串的方法的附加优点是,各种--%限制均不适用,尤其是无法使用输出重定向( > )。

如果切换到用_double_引用的此处字符串( @"<newline>....<newline>"@ ),则甚至可以嵌入_PowerShell变量和expressions_,这与--% ; 但是,然后您需要确保扩展值不会破坏目标Shell的语法。

我们可以考虑使用专用的cmdlet,该别名具有简洁的别名(例如Invoke-NativeShell / ins用于此类调用(因此,不需要sh -c / cmd /c被指定),但是为了按原样传递复杂的命令行,我不认为可以使用here-strings:

# PowerShell - WISHFUL THINKING
# Passes the string to `sh -c` / `cmd /c` for execution, as appropriate.
# Short alias: ins
PS> Invoke-NativeShell @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

当然,如果您依赖于平台本机shell的功能,则此类调用从定义上讲将是特定于平台[-family]的-在Windows和类似Unix的平台上都无法使用。

从长远来看,这就是为什么最好使用PowerShell _alone_及其_own语法_的原因:它为调用外部程序提供了可预测的跨平台体验-即使这意味着您不能使用为其他Shell设计的命令行-是; 随着PowerShell的普及,我希望发现并知道所需的修改减少的痛苦,并且我希望越来越多的文档能够显示PowerShell版本的命令行(也是如此)。

  • 您需要将\"替换`" ,但_only仅在"..." _内。

这并非在所有地方都有效。

# working everywhere but polluted with \
❯ node -e 'console.log(\"hey\")'
hey

# working in Node:
❯ node -e 'console.log(`"hey`")'
hey

# not working in Julia:
❯ julia -e 'print(`"hey`")'
`hey`

# not working anywhere:
❯ node -e "console.log(`"hey`")"
❯ node -e "console.log("hey")"
❯ node -e "console.log(""hey"")"
❯ node -e 'console.log(""hey"")'

Bash语法:

❯ node -e 'console.log("hey")'
hey

Powershell建议:

如果PowerShell团队只是在寻找不会破坏以前语法的符号,为什么不对Bash这样的行为使用反引号`之类的东西,该行为会自动转义文字并允许字符串插值。 这也类似于JavaScript的语法。

❯ node -e `console.log("hey")`
hey

❯ $a=hey 
❯ node -e `console.log($hey)`
hey

为什么不使用类似反引号的东西来进行类似Bash的行为

反引号已用于转义内容,其目的与POSIX shell中的反引号相同。 您所指的评论已经指出了这一点。 我们已经用完了所有类似ASCII引号的内容。 在普通的字符串文字中添加$前缀可能会起作用,但是我认为这样做没有足够的意义。


Windows解析命令行参数的方式可以在...

问题在于Windows MSVCR不仅会这样做:它还以未记录的方式处理极端情况。 ""东西的设置是如此牢固,以至于他们在将.NET移植到Unix时甚至将其放入CoreFX。 但是无论如何,逃脱总是足够好,至少直到有人要求遍历为止。

每个人也都有不同的做法,这是一个经典的问题,但是我们不必担心,因为我们总是将.NET用于原始cmdline。

为什么不使用类似反引号的东西来进行类似Bash的行为

反引号已用于转义内容,其目的与POSIX shell中的反引号相同。 我们已经用完了所有类似ASCII引号的内容。

如果解析器无法检测到此处`正在引入字符串,则可以使用符号组合。 像''类的东西甚至可以工作。

@aminya这是一个解决方案,如果您不使用旧的Windows Powershell 5.1和6+版本:
https://github.com/PowerShell/PowerShell/issues/1995#issuecomment -562334606

由于我必须在Windows上处理PowerShell 5,1,而在linux / mac上处理6+,因此我自己的实现多年来一直没有问题,可以与kubectl,helm,terraform和其他传递复杂JSON的工具一起使用参数内的对象:
https://github.com/choovick/ps-invoke-externalcommand

的GitHub
通过在GitHub上创建一个帐户,为choovick / ps-invoke-externalcommand开发做出贡献。

@choovick在这个线程中上面给出了稍微短一些的实现,我仍然在寻找一个失败的案例。 从v3开始,这在PS中有效。

@AndrewSav @TSlivedeRun-Native函数非常聪明简洁,值得称赞的是,它也可以在_Windows PowerShell_中可靠地工作; 需要注意的几件事:子进程必不可少。 commandlineargumentstring环境变量(可能很少,如果有的话,实际上是一个问题),无参数的调用当前无法正确处理(很容易解决,甚至没有问题,如果您确保仅使用该函数_with_参数,这就是它的用途),_all_参数得到双引号(例如,像42这样的东西作为"42"传递),这在Windows上可能(对于)具有副作用解释双引号(或在msiexec情况下为部分双引号)参数的程序。

@aminyanode -e 'console.log(`"hey`")' (某种)起作用的唯一原因是由于当前行为中断(请参见下文)。 我假设您要传递的是_verbatim_ console.log("hey") ,如果Windows上的PowerShell按此处的建议正确地进行了转义,您将按原样传递单引号: node -e 'console.log("hey")' 。 这应该在幕后自动翻译成node -e "console.log(\"hey\")" (双引号, \ -逐字转义" )。

鉴于此线程已持续多长时间,让我尝试回顾一下:
您只需要担心_PowerShell_的语法要求,这是PowerShell的工作_作为shell_,以确保将PowerShell自身解析产生的_verbatim参数values_传递给​​外部程序_as-is_。

  • 在类似Unix的平台上,由于我们现在已经支持.NET Core,所以这样做很简单,因为逐字值可以像参数_array_的元素一样原样传递,这是程序在本地接收参数的方式。
  • 在Windows上,外部程序将参数作为_single命令行字符串_(一个令人遗憾的历史设计决定)接收,并且必须执行自己的命令行解析。 将多个参数作为单个字符串的一部分传递需要引用和解析规则,以便正确地描述参数; 尽管这最终是万能的(程序可以自由地随意解析),但是Microsoft C / C ++编译器实现的是使用最广泛的语法约定(如前所述,并且在不幸的是现在废弃的RFC中也提出了),所以这样做很有意义。

    • _Update_:即使在Windows上,我们也可以利用.NET Core中System.Diagnostics.ProcessStartInfo普通令牌的ArgumentList属性:在Windows上,它会自动翻译为正确引用和转义的命令该过程开始时的-line字符串; 但是,对于_batch文件_,我们可能仍需要特殊处理-参见https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -552231174

实施上述操作无疑是一项重大突破,因此可能需要选择加入。
我认为,如果我们想摆脱所有当前的引人头痛的问题,这是必须的,这会不断出现并阻碍PowerShell的采用,尤其是在Unix世界中。


至于node -e 'console.log(`"hey`")' :在'...' ,请勿使用` -除非您希望该字符按原样传递。 因为PowerShell当前不__逐字转义"字符。 在参数\"在后台的情况下, node在命令行上看到的是console.log(`"hey`") ,它被解析为两个直接相邻的字符串文字:未引用的console.log(`和双引号"hey`" 。 在剥离了" _ \而具有_syntactic_功能,执行的JavaScript代码最终是console.log(`hey`) ,这只是因为`....`括起来的令牌是JavaScript中字符串文字的一种形式,即_templateliteral_。

@AndrewSav我已经用疯狂的测试对象进行了测试,它确实为我工作! 非常优雅的解决方案,已在Windows 5.1和Linux上的PS 7上进行了测试。 我可以将所有内容都用双引号括起来,但我不会处理msiexecsqlcmd也被称为显式对待"

我的个人实现还具有类似于您提到的简单逃逸逻辑: https :

但是我编写了一些代码来在该模块中实时显示和捕获STDOUT和STDERR线程...可能可以大大简化,但是我没有必要...

@ mklement0,此线程将永远不会结束(:我们要么需要在ps画廊中提供已发布的PowerShell模块,该模块将适合大多数用例,并且使用起来非常简单,或者等待即将到来的Shell改进。

的GitHub
通过在GitHub上创建一个帐户,为choovick / ps-invoke-externalcommand开发做出贡献。

@ mklement0,此线程将永远不会结束(:我们要么需要在ps画廊中提供已发布的PowerShell模块,该模块将适合大多数用例,并且使用起来非常简单,或者等待即将到来的Shell改进。

如果PowerShell团队决定不在程序本身中解决此问题,那么我会说无法自然而正确地运行外部程序的shell将不是我选择的shell! 这些是PowerShell中缺少的基本内容。

@aminya是的,我发现当我不得不继续前进时,这个问题使我很烦恼。 但是以下功能使它值得。

  • 灵活的参数框架,易于构建可靠的CMDlet。
  • 模块和内部模块存储库可在整个组织内共享通用逻辑,从而避免了很多代码重复和核心功能的集中化,这些功能可以一次重构。
  • 跨平台。 我让人们在PS 5.1的Windows上和PS 6/7的linux / mac上运行我的工具

我真的希望PS小组将来改进此方法,以减少混乱的情况。

实施上述操作无疑是一项重大突破,因此可能需要选择加入。

您是说实现https://github.com/PowerShell/PowerShell-RFC/pull/90吗?

@aminya ,我同意带参数调用外部程序是Shell的核心任务,必须正常运行。

@choovick建议的模块对于_Windows PowerShell_仍然有意义,它处于仅安全关键补丁修复模式。
作为补充,如果/在编写了有关调用外部程序的建议概念帮助主题时,请参见-请参阅https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152-一个帮助程序功能,用于纠正早期版本/ Windows PowerShell中的问题,例如上面的@TSlivede ,可以直接发布到那里。

@iSazonov是的,实现https://github.com/PowerShell/PowerShell-RFC/pull/90就是我的意思。

至于线程永无止境和重大变化的担忧:

对链接的RFC的最后一次正式回复是@joeyaiello于2019年7月8日发表的以下评论(强调添加):

但是我们认为,[RFC]在很大程度上与现有行为无关,并且不考虑其破坏性。 现在我们有了实验功能,我们认为今天在实验功能标记后面实施此功能是完全合理的,并且我们可以进一步弄清这是选择加入还是选择退出行为,是否存在某些过渡路径,以及首选项变量是否是打开和关闭它的正确机制。

_个人_,我不介意修复行为,默认情况下,即使这是一项重大更改; RFC确实提出了这一建议,并且如果您想要_old_(无效)行为,则建议加入。

我怀疑那些保留有旧代码的人会反对,因为_所有现有的解决方法都将停止起作用_-参见上文-并且保持向后兼容性仍然是首要目标。

如果选择采用新的固定行为,那么您仍然有笨拙的作法,只是为了获得正确的行为,但至少现有代码不会中断。

但是现有的选择加入机制本身存在问题:
现在@KirkMunro可选功能RFC已被拒绝,剩下的几乎是_preference variable_,而PowerShell的动态作用域是一个挑战:从选择不使用新范围的作用域中调用的第三方代码然后执行可能会中断(除非将偏好变量临时重置)。

此处需要_Lexical_选择参与范围,我们目前尚无此功能。 严格模式的_lexical_作用域using语句(特别是_dynamically_作用域)实现词法范围的功能(或不同目的)。 按照这种模式,如果在技术上可行的话,值得考虑的是词法范围内的using ProperExternalArgumentQuoting语句(名称为WIP :)。

我们需要PowerShell委员会(再次)进行权衡,并就前进的方向提供清晰的指导,并在问题出现时及时提供反馈。 @ SteveL-MSFT?


请注意,7.1博客文章(请参阅上文)提示的类似--%的解决方案(我个人认为不值得追求-参见上文此评论)将是_separate_功能-修复PowerShell的本机功能(非仿真)行为仍然是必须的。

就个人而言,我不介意在默认情况下修复此行为,即使这是一个重大更改。 RFC确实提出了这一建议,并建议您选择是否要使用旧的(无效)行为。

如果选择采用新的固定行为,那么您仍然有笨拙的作法,只是为了获得正确的行为,但至少现有代码不会中断。

同意,事实上,我认为先破坏默认值然后再正确实施默认值意义不大。 鉴于当前的实现实际上是一个错误,新的行为必须是选择加入,而不是选择退出,因为继续鼓励可能会在意外情况下中断的外部外壳调用确实没有任何意义。方法。 在任何情况下,PowerShell 7都应努力改进旧版Windows PowerShell。

@ SteveL-MSFT,我同意我们应该以#13068结尾关闭此程序。 我们在这里碰到的任何东西都不过是一个巨大的改变,我们应该使用一个新的操作员来解决这个问题,该操作员可以选择加入模式。

我绝对看不到#13068如何解决此问题:如果按预期引入该运算符,我们仍然无法用给定的参数数组或某些显式参数(其内容源自变量)正确调用任何本机可执行文件。

@JustinGrote在该线程中给出的示例当前无法可靠地工作(如果在参数有效负载中可以使用嵌入式引号),并且添加该运算符将不会提供任何改进的选择。

@joeyaiello您至少可以将此问题公开,直到该操作符实际存在并且有人可以证明该操作符将如何改进此线程中提到的任何内容为止?

哦,还有Linux呢? 这个问题在Windows上是愚蠢的,是无法预料的,​​但是在Linux上,它的意义甚至要差得多,尤其是因为没有悠久的Linux powershell脚本历史可以打破。

为此,为命令行shell设置一个特殊的运算符根本没有意义,因为它的主要工作是启动程序并传递参数。 引入一个新的运算符为该工作执行system()就像Matlab引入了一种调用calc.exe因为它在算术上存在错误。 相反,应该做的是:

  • pwsh团队正在准备一个新的主要发行版,该发行版修复了命令行问题,并将当前行为移至内置cmdlet后面。
  • 作为权宜之计,即将发布的pwsh版本获得了一个内置cmdlet,该cmdlet使用新的正确行为进行命令行传递。

Start-Process 。 (实际上,它是“新” cmdlet的不错选择,其中包括-QuotingBehavior Legacy ...等选项)。请参阅#13089。

为什么Powershell在这两种情况下的行为不同? 具体来说,它不一致地将包含空格的args括在双引号中。

我在v.7中获得一致的结果。 似乎固定。

PING 'A \"B'

Ping请求找不到主机A“ B。

PING 'A\" B'

Ping请求找不到主机A“B。

它不是固定的,因为ping应该看到的逐字主机名是A \"BA\" B -_with_ \字符。

作为外壳程序,PowerShell仅应按照_its_规则解析参数,然后透明地确保目标进程看到与PowerShell自身解析结果相同的逐字记录值。

那个_other_ shells-以及那些在Windows上运行的较差的程序,以某种方式,它们必须像它们自己的shell一样运行,只需解析_command line_以提取通过的各个参数即可-使用\作为转义符字符不应该在此处输入图片-适应(仅在Windows上是必需的,在Unix上,您只需将逐字参数直接作为数组传递)是PowerShell作为shell的工作,在幕后_。

顺便说一句,就像PowerShell本身不需要转义" _inside '...' _(单引号字符串)一样,也不兼容POSIX兼容的外壳,例如bash :从bash ,例如, /bin/echo 'A \"B' (明智地)打印A \"B\被视为单引号字符串中的文字)-执行来自PowerShell的相同命令(出乎意料)会产生
A "B - \丢失-是此处讨论的问题的体现。

我应该澄清:

  • 从希望最终通过A "B _verbatim_的角度来看,您应该能够使用PowerShell中的'A "B'

  • PowerShell当前在幕后构造的命令行包含"A "B" -目标进程将其视为A B -即"..."的盲文外壳,而不会转义_embedded_ "导致嵌入的"的有效损失。 在这种情况下,PowerShell在幕后命令行中使用的是"A \"B" -也就是说,嵌入式"需要转义\

  • 同样,相同的盲文框会导致'A \"B'在幕后命令行中表示为"A \"B" ,恰好将_embedded_ \"变成_escaped_ "字符,因此目标进程将其视为A "B ; 也就是说,缺少_automatic_转义会导致嵌入式\的有效损失。 在这种情况下,PowerShell在幕后命令行中使用的是"A \\\"B" -也就是说, \"需要转义。

它不是固定的,因为ping应该看到的逐字主机名是A \"BA\" B -_with_ \字符。

“它”在这里是指引用的投诉,我幸运地无法复制。

@ yecril71pl ,我看到:我的(不正确的)假设是“不一致地将包含空格的双引号括起来的args”是指_lack自动转义嵌入的"\字符_,在我之前的评论中进行了解释-这就是这个问题的症结所在。

在PowerShell Core中有一些次要的修补程序,而Windows PowerShell则没有。 我现在只能想到一个:

  • Windows PowerShell在尾随的\情况下使用盲目双引号: 'A B\'变成((断)) "A B\" -PS Core正确地处理了( "A B\\" ) 。

鉴于此回购仅适用于PS Core,足以专注于PS Core中仍然存在的问题(将差异作为旁白提及可能会有所帮助,但最好是明确说明这一方面)。


甚至您已经想到的方案_is_在PS Core中仍然无法解决-但只有_如果您从参数_中省略了\

传递'A" B'仍会导致_non_-双引号A" B在幕后(而'A\" B'导致"A\" B" -如前面所述,它也被破坏了-仅不同地)。

鉴于此回购仅适用于PS Core,足以专注于PS Core中仍然存在的问题(将差异作为旁白提及可能会有所帮助,但最好是明确说明这一方面)。

撇开meta:我发现了解另一位用户的评论中提到的错误行为不适用非常有用。 当然,我们可能只是因为用户不必费心检查当前版本就可以删除该评论。 好。 不守规矩的记者不守规矩,还是可以肯定的。 恕我直言。

那里没有论据,但为这样的_asides_提供_proper framing和context_很重要,尤其是在looooong线程中,很早以前就发布了需要上下文的原始注释,事实上,现在默认情况下_hidden_​​(永远不会受伤)实际_link_到引用的原始注释)。

我想知道这些讨论是否有所作为。 显然,委员会的决定与社区的需求无关。 查看标签: Resolution- won't fix.但由于PowerShell是开源(MIT),社区可以在单独的fork中修复此问题,并将其简称pwshc PowerShellCommunity(

这消除了向后兼容性的需要。 稍后在PowerShell 8中,委员会可以集成该fork。

关于向后兼容性:PowerShell尚未预安装在任何操作系统上,对我来说,安装PowerShellCommunity的难度与PowerShell 。 我更喜欢安装社区版本并立即使用它,而不是等待将来的8版本(或使用新的运算符使代码更复杂)。

由于委员会已决定将事情弄坏,所以最好知道事情有多严重。 我认为社区可以接受Invoke-Native做正确的事。 挽救Microsoft的面目违背他们的意愿不是社区的工作。

最好知道它们有多严重

我完全同意-即使暂时无法解决问题,原则上知道如何正确地做事,是否以及何时到来是很重要的-即使该时间来不及给定语言。

社区可以与做正确的事的Invoke-Native一起生活

要清楚:

  • Invoke-NativeShell解决了一个_不同的用例_-这是#13068的主题-对于这种用例,这样的cmdlet是_not_权宜之计:这是正确的解决方案-参见https://github.com/ PowerShell / PowerShell /问题/ 13068#issuecomment -656781439

  • _this_问题与修复_PowerShell本身_(与平台_native_功能无关)有关,而_stopgap_解决方案是提供一个低礼节性的_opt-in_-因此,建议将功能iep作为内置版本提供。 -in函数,待在将来的版本中进行_proper_修复,此修复将允许实质上破坏向后兼容性。

挽救微软的面子不是社区的工作

我认为@aminya的关注不是要挽救任何人的脸,而是要解决在Shell核心任务所在的区域中从根本上破坏行为的问题。

就是说,我不确定用fork分割PowerShell生态系统是否是正确的方法。

我暂时没有时间回应所有这些问题,但是我认为这是合理的,因此我重新开始:

您是否至少可以将此问题公开,直到该操作员实际存在并且有人可以证明该操作员将如何改进此线程中提到的任何内容?

就像我在相关问题中所说的那样,本地调用运算符增加了UX的复杂性,并且方向错误。
同时,这里的整个讨论都是关于简化与本机应用程序的交互。

阻止我们的唯一一件事就是这是一个巨大的变化。 我们已经多次说过,这是太多的破坏,但让我们来衡量一下。

让我们看一个交互式的会话。 用户将在调用本机应用程序时安装新的PowerShell版本并发现新的行为。 他会说什么? 我猜想他会说-“谢谢-终于我可以打字了并且可以使用!”。

让我们看一下脚本执行/托管方案。 应用程序的任何新版本(即使有很小的更改!)都可能破坏业务流程。 我们始终期望这一点,并在进行任何更新后检查我们的流程。 如果发现问题,则可以采用以下几种方法:

  • 回滚更新
  • 为脚本准备快速修复
  • 关闭会破坏脚本的功能,直到这些问题解决或它们本身随着时间消失。

_由于版本更新总是会破坏某些内容,因此最后一个选择是我们可以拥有并接受的最佳选择。_

(我想指出的是,PowerShell Core现在不是Windows的组件,因此无法直接破坏托管应用程序,仅直接影响脚本。)

我想提醒您杰森以上所说的-这是一个错误修复。
让我们对其进行修复,并为所有人简化所有操作。 让用户使用powershell-y

就像我在相关问题中所说的那样,本地调用运算符在UX中增加了共谋性,这是错误的方向。

复杂度❓

让我们看一个交互式的会话。 用户将在调用本机应用程序时安装新的PowerShell版本并发现新的行为。 他会说什么? 我猜想他会说-“谢谢-终于我可以打字了并且可以使用!”。

我有另一种情况:新用户尝试使用PowerShell,意识到它神秘地失败了,将其移到回收站,再也不会返回。 那就是我会做的。

为脚本准备快速修复

那是我们应该做的,以涵盖用户脚本中明显的情况。

意识到它神秘地失败了

这里的整个讨论只是关于摆脱这种“神秘失败”。 最好通过简化而不添加新的神秘事物来做到这一点。

这里的整个讨论只是关于摆脱这一点。 最好通过简化而不添加新的神秘事物来做到这一点。

我们不想摆脱直接调用可执行程序的能力。

旧的调用也不直接指向cmdline,因为它猜测是否已经引用了某些内容。 此外,上述能力仍将保留-%。

此外,上述能力仍将保留-%。

--%需要预定义的命令行,因此其实用程序受到限制。

@ yecril71pl

我们不想摆脱直接调用可执行程序的能力。

我认为@iSazonov意味着摆脱错误的行为

@ Artoria2e5

旧的调用也不直接指向cmdline,因为它猜测是否已引用某些内容

[_Update_:我误读了引号行,但希望该信息仍然值得关注。]

  • 您不必_guess_,规则很简单:

    • _仅if_您的命令名或路径是_quoted_- '/foo bar/someutil '-和/或包含_variable引用(或表达式)- $HOME/someutil - &是_required_。
  • 尽管您可能认为这很不幸,但这是PowerShell语言的核心,并且需要能够在语法上区分两种基本解析模式(参数模式和表达模式)。

    • 请注意,该问题并非特定于调用_externalprograms;。 如果通过带引号的字符串/变量引用指定,则本机PowerShell命令也需要&进行调用。
  • 如果您不想记住这个简单的规则,则更简单的规则是:_always_使用& ,您会没事的-它比_not_不需要任何东西要方便一些,但并不完全是困难(而且更短)比--% ,这绝对是错误的解决方案-参见下文)

所述能力仍将保留-%。

[_Update_:我误读了引号行,但希望该信息仍然值得关注。]

否,尽管不幸的是#13068与_this_问题混为一谈,但是--% -使用_its_,始终使用_platform-specific_语法调用_native shell_的能力-绝不是解决当前问题和讨论的方法这样只会增加混乱

--%是一个非常不同的用例,并且如当前所建议的那样,具有严重的局限性; 如果有某种不值得引入_operator_的东西(或更改现有行为的行为),则可以将逐字命令行传递给本机shell(当然,您已经可以使用sh -c '...'对Unix和cmd /c '...' ,_but只有稳健如果这个问题得到fixed_;二进制Invoke-NativeShell / ins cmdlet的实现,而主要是抽象掉目标壳的细节CLI语法可以避免出现此问题(直接使用System.Diagnostics.ProcessStartInfo.ArgumentList ,因此可以独立实现)。

@ yecril71pl

我有另一种情况:新用户尝试使用PowerShell,意识到它神秘地失败了,将其移到回收站,再也不会返回。 那就是我会做的。

您能否澄清一下:您是否担心,Powershell的当前行为会导致这种不愉快的用户体验,或者您是否担心所提议的更改会导致该用户体验。

因为在我看来,Powershell的当前行为更有可能产生这种体验。 如上所述:powershell的当前行为应视为一个错误。

为什么新用户在不了解PowerShell的情况下会发出带有扭曲参数的命令?

@ yecril71pl绝对要确保:您认为参数“扭曲”是当前所需的形式,而不是建议的解决方案。

我认为您的问题源自您对我是无法治愈的书呆子的假设。 没错,但是我保留了想象正常随机小伙子会认为扭曲的能力。

@ mklement0
我认为, @ Artoria2e5在谈论说将参数数组转换为单个lpCommandLine字符串时

旧的调用也不直接指向cmdline,因为它猜测是否已引用某些内容

因为打电话时

echoargs.exe 'some"complicated_argument'

您确实必须或多或少地猜测一下,无论powershell是否在some"complicated_argument附近添加引号。

示例1:在大多数PowerShell版本中
echoarg.exe 'a\" b'echoarg.exe '"a\" b"'将转换为
"C:\path\to\echoarg.exe" "a\" b" (测试版本2.0; 4.0; 6.0.0-alpha.15; 7.0.1)
但是我在Win10(版本5.1.18362.752)上的默认Powershell会翻译
echoarg.exe 'a\" b'"C:\path\to\echoarg.exe" a\" b
echoarg.exe '"a\" b"'"C:\path\to\echoarg.exe" ""a\" b""

示例2:较旧的powershell版本翻译
echoarg.exe 'a"b c"'"C:\path\to\echoarg.exe" "a"b c"" (测试版2.0; 4.0;)
而较新的版本会翻译
echoarg.exe 'a"b c"'"C:\path\to\echoarg.exe" a"b c" (测试版本5.1.18362.752; 6.0.0-alpha.15; 7.0.1)。


由于行为显然已经被多次更改,因此我不明白为什么不能再更改一次以获得预期的行为。

我看到@TSlivede ,感谢您的澄清,对不起您的误解@ Artoria2e5。

至于真正的问题:我们100%同意-实际上,您甚至不必思考lpCommandLine最终在Windows幕后被使用了什么。 如果PowerShell做正确的事,那么没有人会(除了极端情况,但这不是PowerShell的错,那时候--% (如当前实施)可以提供帮助;有了适当的修复,永远不会是在类Unix平台上的优势案例)。

至于简单地正确解决问题:您当然可以投我一票(但是现有的解决方法将会失效)。

TL; DR:关于我们可以可靠地将任何值作为参数传递给Microsoft Windows NT子系统中任何程序的假设是错误的,因此我们应该停止假装这是我们的目标。 但是,如果我们考虑论点的范围,仍然有很多事情可以挽救。

调用本机Windows可执行文件时,我们应保留原始引用。 例:

CMD /CSTART="WINDOW TITLE"

系统找不到文件WINDOW。

 { CMD /CSTART="WINDOW TITLE" }. Ast. EndBlock. Statements. PipelineElements. CommandElements[1]

StringConstantType
裸字
/ CSTART = WINDOW TITLE
静态类型
System.String
程度
/ CSTART =“ WINDOW TITLE”
父母
CMD / CSTART =“ WINDOW TITLE”

如果将扩展区作为模板,则不会丢失任何内容,并且可以按预期方式调用本机可执行文件。 使用字符串参数的解决方法在这里可行,但我认为这样做绝对不是技术上绝对必要的,只要在PowerShell中实现适当的支持即可。 这种方法适用于所有情况。

引号内的引号引起了一个无法解决的问题,因为存在解释反斜杠转义( TASKLIST "\\\"PROGRAM FILES" )的工具和不解释反斜杠转义( DIR "\""PROGRAM FILES" /B )的工具以及不会打扰的工具( TITLE A " B )。 但是,如果我们要进行转义,则带反斜杠的标准转义会使所有常见的文件管理工具失效,因为它们根本不支持引号,而双反斜杠\\表示与它们完全不同的内容(尝试DIR "\\\"PROGRAM FILES" /B ),因此发送内部带有引号的参数应该是运行时错误。 但是我们不能抛出错误,因为我们不知道哪个是哪个。 尽管使用普通的转义机制不会对不包含引号的参数造成任何损害,但我们无法确定当将其应用于包含引号的参数并馈入不支持将引号作为值的工具时,必然会导致异常中止的错误而不是意外的行为,并且意外的行为的确会非常糟糕。 这是我们给用户带来的沉重负担。 此外,我们将永远无法提供“无关”工具( CMD /CECHO='A " B' )。

请注意,环境变量不代表CMD ,它们代表随着环境变量扩展而重新解析的代码片段,并且没有提供将它们可靠地视为其他命令的参数的规定。 CMD不能对任何类型的对象进行操作,甚至不能对字符串进行操作,这似乎是当前难题的根本原因。

TL; DR:我们可以可靠地将任何值作为参数传递给Microsoft Windows NT子系统中任何程序的假设是_错误_,因此我们不应假装这是我们的目标。

那应该是目标,但不是吗? 如果程序无法解释其接收到的参数,则不是PowerShell的问题。

调用本机Windows可执行文件时,我们应保留原始引用。 例:

CMD /CSTART="WINDOW TITLE"

您是否建议调用程序应将语言从PowerShell动态更改为被调用程序使用的语言? 您在PowerShell中编写了该示例,这意味着它应等效于以下任何示例

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

TL; DR:我们可以可靠地将任何值作为参数传递给Microsoft Windows NT子系统中任何程序的假设是_错误_,因此我们不应假装这是我们的目标。

那应该是目标,但不是吗? 如果程序无法解释其接收到的参数,则不是PowerShell的问题。

程序CMD可以解释参数/CECHO=A " B但是PowerShell不能传递它而

调用本机Windows可执行文件时,我们应保留原始引用。 例:

CMD /CSTART="WINDOW TITLE"

您是否建议调用程序应将语言从PowerShell动态更改为被调用程序使用的语言? 您在PowerShell中编写了该示例,这意味着它应等效于以下任何示例

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

我试图建议,当与Microsoft Windows NT子系统下的外部程序进行交互时,PowerShell有多种方式来编码均等效于PowerShell但不等效于接收程序的参数。 轻率地说,直言不讳并强行使用One True Way™编码参数,而没有注意用户实际使用的引用方式是没有帮助的。

@ yecril71pl我对您的评论感到非常困惑。 您在这里提出什么建议? 您的用例全部包含在--% 。 您之前说过将其驳回

--%需要预定义的命令行,因此其实用程序受到限制。

但实际上,您可以将环境变量与--% 。 尝试这个:

PS > $env:mytitle='WINDOW TITLE'
PS > cmd --% /CSTART="%mytitle%"

那我想念什么呢?

我们缺少语法CMD /CSTART="$mytitle" ,而没有泄漏到ENV:

作为一个糟糕的主意,我们确实可以选择用其他东西代替Environment.ExpandEnvironmentVariables 。 无论如何,Unix上都没有本机实现,而且我不认为当用C#重写时,它处理的内容对性能至关重要。

由于无论如何在环境变量名称中都不允许使用等号,因此我们可以将%=$a%表示$a 。 这不会破坏现有的任何东西,同时允许一些非常灵活的(可能是不好的)扩展,例如使其像JS的模板字符串一样工作。 地狱,我们也可以将%VARNAME=$var%定义为某种后备语法。

至于文档地狱,这将导致...我深表歉意。

  • 我们确实没有解析问题。

  • 我们所做的是_how PowerShell的问题,PowerShell将_its_解析产生的逐字,字符串化参数传递给外部(本机)可执行文件_:

    • 在_Windows_上,问题在于,调用在后台构造的外部可执行文件的命令行_not_遵守最广泛使用的引用引号的约定,如Microsoft C / C ++编译器文档的Parsing C ++命令中所述-行参数部分。

    • 当前所发生的甚至还没有使用_different_约定:大概是由于疏忽大意,根据参数的具体情况,所构造的命令行在情况上_语法上从根本上来说已损坏_,这与嵌入式双引号和空格的组合有关以及空字符串参数。

    • 最终,问题在于Windows上进程创建的基本体系结构:您被迫对参数进行编码,以将其作为命令行传递给进程_(代表_all_参数的单个字符串),而不是将其作为参数的_array_传递(类Unix平台是如何做到的)。 需要传递命令行要求_引用和转义rules_才能实现,并且最终要取决于每个程序_如何解释给出的命令行。 实际上,这相当于不必要地迫使程序成为某种微型外壳程序:它们被迫_re-perform_该外壳程序已执行的任务,而该任务应仅属于_shell_( (在Unix上是这种情况),即将命令行解析为单独的参数。 简而言之,这是在Windows上传递参数

    • 在实践中,遵循上述约定的_most_程序减轻了混乱状态,并且正在开发的新程序很有可能遵守该约定,这主要是因为支持控制台应用程序的广泛使用的运行时实现了这些约定(例如Microsoft C / C ++ / .NET运行时)。 因此,明智的解决方案是:

      • 在幕后构建命令行时,使PowerShell遵守此约定。
      • 对于不遵守此约定的“恶意”程序-特别是包括cmd.exe ,批处理文件以及msiexec.exemsdeploy.exe类的Microsoft实用程序,它们提供了一种机制来显式地控制传递给目标可执行文件的命令行; 这就是停止解析符号--%提供的-尽管很尴尬
    • 在_Unix_上,问题是_a正在all_上构建命令行-而是应将逐字参数数组传递给_as-is_ ,.NET Core现在支持该语言(自v2.1起,通过ProcessStartInfo.ArgumentList ;考虑到-在类似Unix的平台上创建进程时-明智地-没有命令行,只有参数数组,它应该总是支持此操作。

    • 一旦我们使用ProcessStartInfo.ArgumentList ,Unix上的所有问题都会消失。

@TSlivedehttps://github.com/PowerShell/PowerShell-RFC/pull/90就是要解决这些问题。

https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -650242411中,我还建议自动补偿批处理文件的“粗糙性”,因为批处理文件仍然非常广泛地用作CLI入口点, -profile软件,例如Azure(将CLI az实现为批处理文件az.cmd )。
类似地,我们应该考虑对msiexec.exemsdeploy.exe以及其他知名的“恶意” Microsoft CLI执行相同的操作。


我刚刚发布了一个模块NativeInstall-Module Native -Scope CurrentUser ),该模块通过其ie函数解决了上述所有问题( i nvoke(外部) e xecutable的缩写;它是上面介绍的iep函数的更完整的实现。

它还包括insInvoke-NativeShell (地址#13068)和dbeaDebug-ExecutableArguments诊断参数传递-请参阅https://github.com。 com / PowerShell / PowerShell / issues / 13068#issuecomment -671572939以获得详细信息。

换句话说: ie可以充当一个轻松的权宜之计,而我们等待此问题解决,只需在调用之前加上ie作为命令即可:

代替:

# This command is currently broken, because the '{ "name": "foo" }' argument isn't properly passed.
curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

您将使用以下内容:

# OK, thanks to `ie`
ie curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

至于CMD /CSTART="WINDOW TITLE"示例(其惯用格式是cmd /c start "WINDOW TITLE" ,它已经起作用了):

从本质上讲,这与msiexec / msdeploy prop="<value with spaces>"参数存在相同的问题:PowerShell-正当理由-将/CSTART="WINDOW TITLE"转换为"/CSTART=WINDOW TITLE" ,但是,这会中断cmd.exe调用。

有两种方法可以解决此问题:

  • 委托给ins / Invoke-NativeShell (请注意,实际上暗示使用cmd.exe /c ):

    • ins 'START="WINDOW TITLE"'
    • 如果使用_expandable_字符串,则可以在命令字符串中嵌入PowerShell值。

      • $title = 'window title'; ins "START=`"$title`""

  • 或者,使用当前的--%实现,但要注意其局限性

    • cmd --% /CSTART="WINDOW TITLE"
    • 如上所述, --%一个有问题的限制是,嵌入_PowerShell_值的唯一方法是使用_aux。 环境变量_并使用%...%语法引用它:

      • $env:_title = 'window title'; cmd --% /CSTART="%_title%"

      • 为避免此限制,应该始终使用_single_字符串参数实现--% -例如,

        cmd --% '/CSTART="WINDOW TITLE"'cmd --% "/CSTART=`"$title`"" -但这不能在不破坏向后兼容性的情况下进行更改,因此必须引入_new_符号-就我个人而言,我认为不需要。

  • 他们被迫_重新执行_ Shell已经执行的任务

我认为CMD.EXE不会将命令行拆分为参数,唯一需要做的就是找出要调用的可执行文件,其余的只是用户编写的命令行(在环境变量替换之后,无需考虑参数边界)。 当然,内部shell命令在这里是一个例外。

本质上与msiexec / msdeploy prop="<value with spaces>"变量是相同的问题

我对这两者都不是一个有信心的用户,因此我更喜欢提出我更熟悉的东西。

需要明确的是:以下内容不会影响我先前的评论。

我不认为CMD.EXE将命令行拆分为参数

  • 调用_external可执行文件_(子进程中的另一个可执行文件运行的命令)时,它可以不产生_explicit_分裂的情况消失,但是它确实必须对_batch files_执行。

  • 即使在调用外部可执行文件时,也需要对参数边界进行_aware_判断,以便确定给定的元字符(例如& )是否具有_syntactic_函数,或者它是否是双引号参数的一部分,因此需要对其进行处理。作为文字:

:: OK - the "..." around & tells cmd.exe to use it verbatim
C:\>echoArgs.exe one "two & three"
Arg 0 is <one>
Arg 1 is <two & three>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" one "two & three"

另外, cmd.exe识别出嵌入了"字符。 如果将"..."字符串转义为""则会将其识别出来:

:: OK - the "" is recognized as an escaped "
C:\>echoArgs.exe "3"" of rain & such."
Arg 0 is <3" of rain & such.>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3"" of rain & such."

不幸的是, cmd.exe _only_支持(仅Windows) ""而不是更广泛使用的\" (这是Unix上类似POSIX的_shells_ _exclusively_使用-注意: _shells_,而不是_programs_,因为程序只看到由shell解析产生的逐字参数数组)。

尽管Windows上的大多数CLI都支持""\" ,但是有些_only_理解\" (特别是Perl和Ruby),然后您遇到了麻烦:

:: !! BROKEN: cmd.exe misinterprets the & as *unquoted*, thinks it's the statement-sequencing operator, 
:: !! and tries to execute `such`:
C:\>echoArgs.exe "3\" of rain & such."
Arg 0 is <3" of rain >

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3\" of rain

'such."' is not recognized as an internal or external command,
operable program or batch file.

因此:

  • 尽可能避免直接调用cmd.exe

    • 使用_PowerShell的_语法直接调用外部可执行文件(一旦解决此问题)或通过ie (现在)。
  • 如果您确实必须调用cmd.exe ,请使用ins / Invoke-NativeShell来简化操作,尤其是将PowerShell变量和表达式值嵌入命令行的简单性。

    • 仍然直接调用cmd.exe的合理原因是为了补偿PowerShell对管道中原始字节数据的缺乏支持-有关示例,请参见此SO答案

我知道我会在这里遇到很多麻烦,并且我非常感谢正在进行的深入讨论,但是……鸭子……在现实世界中,有没有人举过任何这样的例子呢? ?

我认为在PowerShell中我们无权解决Windows参数解析当前存在的“混乱状态”。 出于许多无法解决问题的相同原因,Windows和VC ++编译器选择不破坏此行为是有充分理由的。 这很猖,,如果我们改变事情,我们只会制造出新的(而且基本上是无法理解的)问题的长尾巴。

对于那些已经跨平台并且在Windows和Linux之间大量使用的实用程序(例如Docker,k8s,Git等),我看不到这个问题在现实世界中出现。

对于那些表现不佳的“流氓”应用程序:它们很大程度上是旧的,仅Windows的实用程序。

我同意您所说的@ mklement0在很大程度上是一种“正确”的解决方案。 我只是不知道如何在不搞砸的情况下到达那里。

相当基本的用法打破了:

❯ git commit --allow-empty -m 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $a = 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
❯ git commit --allow-empty -m "$a"
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.0.3
PSEdition                      Core
GitCommitId                    7.0.3
OS                             Microsoft Windows 10.0.19042
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

git.exe是一个表现良好的应用程序,因此尽管需要所有脚本编写者还原他们的明智解决方法,但PowerShell中的修复将非常简单。 cmd.exe很难适应,需要更体贴的方法来解决一些问题,但可能不是全部。 考虑到PowerShell是作为Windows NT工具启动的,这实际上是令人震惊的。 我将这个问题理解为_是否存在现实情况,当从PowerShell中调用行为不佳的旧版实用程序(如cmd.exe时,会导致interface_出现问题。 PowerShell尝试通过复制cmd.exe大多数功能来解决此问题,以使cmd.exe冗余。 这对于其他工具也是可能的,例如MSI可以通过ActiveX操作,尽管这样做需要大量知识。 那么,有没有涵盖的基本要素?

@ PowerShell / powershell-committee对此进行了讨论。 我们赞赏git示例,该示例清楚地显示了一个令人信服的真实示例。 我们同意,我们应该在7.2的早期就具有实验功能,以验证进行此类重大更改的影响。 另一个测试示例表明,即使--%也应该有问题,即使它本来应该没有解析也可以:

PS> testexe --% -echoargs 'a b c "d e f " g h'
Arg 0 is <'a>
Arg 1 is <b>
Arg 2 is <c>
Arg 3 is <d e f >
Arg 4 is <g>
Arg 5 is <h'>

在本地命令参数绑定程序中,这似乎是一个问题。

是的,谢谢@cspotcode。 这个例子对我来说绝对是个美好的时刻(尤其是考虑到我实际上已经在现实世界中遇到了那个时刻)。

我仍在关注突破性变化方面,我认为这可能是一项试验性功能的候选对象,该功能可能会在多个版本的PowerShell上保持试验性,这绝对不是我们确定最终会实现的功能。

我还需要深入了解RFC @ mklement0的允许列表/“ rouge app”方面,因为我不确定我们要注册多少来维护这样的列表。

@joeyaiello和@ SteveL-MSFT,让我首先进行元观察:

虽然很高兴看到@cspotcode的示例使您对问题有所了解,但您的回答仍然背叛了根本缺乏对潜在问题(数量级)的理解和欣赏的能力(我将在以后的评论中讨论这一点) 。

这不是一个个人的判断:我完全意识到,将自己拉得很瘦,并且必须在很短的时间内对众多主题做出决定是多么困难。

但是,这指向一个结构性问题:在我看来,@ PowerShell / powershell-committee通常是在对所讨论问题的肤浅理解的基础上常规做出决定的,这对整个社区都是不利的。

对我来说,委员会对此处讨论的问题的回应是迄今为止这一结构性问题的最重要的例子。

因此,请您考虑一下:

如何任命委员会咨询的特定主题的_sub_委员会_do_对所涉及的问题有必要的了解?

您可以共享testexe SteveL-MSFT的内容吗?

@TSlivedehttps://github.com/PowerShell/PowerShell/issues/13068#issuecomment -665125375中适当地总结了问题:

另一方面,PowerShell声称是外壳程序(直到1995年解决,我不会说它是外壳程序)

如前所述,shell的核心任务是调用带有参数的外部可执行文件。

鉴于未正确传递带有嵌入式双引号和空字符串参数的参数,PowerShell当前无法完成此任务。

如前所述,在仅Windows的日子里,这可能不是一个大问题,因为缺乏功能强大的外部CLI很少会出现此问题,但是这些日子已经过去,并且如果PowerShell希望将自己建立为可靠的跨平台外壳程序,它必须解决此问题。

@cspotcodegit示例是一个很好的例子; 您要向其传递JSON字符串的任何可执行文件(例如curl )是另一个:

# On Unix; on Windows, 
#   echoArgs.exe '{ "foo": "bar" }' 
# would show the same problem.
PS> /bin/echo '{ "foo": "bar" }'
{ foo: bar }  # !! Argument was incorrectly passed.

撇开向后兼容性:

  • 在Unix上,通过在后台使用ProcessStartInfo.ArgumentList轻松地解决此问题。

  • 在Windows上,通过在后台使用ProcessStartInfo.ArgumentList轻松解决问题,并且几乎可以解决问题。

    • 对于边缘情况(“恶意” CLI),存在(执行不佳--%
    • 作为礼貌,我们可以补偿某些众所周知的边缘情况,以减少对--% -参见下文。

因此,必须尽快做出以下选择之一:

  • 认识到使参数传递正常工作的重要性,并以向后兼容性_为代价修复它。

  • 如果向后兼容性确实是最重要的,请从Native模块提供一个新的运算符或一个函数,例如ie函数来解决此问题,并将其广泛宣传为调用外部函数的唯一可靠方法可执行文件。

提出一项“实验性”功能来解决严重损坏的基本功能是完全不够的。


@ SteveL-MSFT

即使考虑使用--%作为解决此问题的方法,从根本上也是错误的:

这是仅Windows功能,仅知道"..."引用和%...%样式的环境变量引用。

在Unix上,“停止解析”的概念从根本上不适用:_没有命令行_传递给子进程,只有参数数组。

因此,_someone_必须将命令行解析为参数_before_调用,该调用通过其.Arguments属性隐式委派给ProcessStartInfo类,该属性在Unix上使用_Windows_约定来解析命令行。 -因此只能识别"..."报价(将嵌入式"转义为""\" )。

--%是仅Windows功能,其唯一合法目的是调用“恶意” CLI。


@joeyaiello

Windows和VC ++编译器已选择不破坏此行为。

VC ++编译器强加了一个明智的,被广泛观察到的Convention_ ,以使无政府状态井井有条。

正是在这里提倡遵守此约定,使用ProcessStartInfo.ArgumentList会自动给我们。

_仅此一项就可以覆盖绝大多数电话。 覆盖所有调用是不可能的,并且实际上不是PowerShell的责任。_

如前所述,对于需要非常规引用形式的“流氓” CLI,必须使用--% (或Native模块中的ins / Invoke-NativeShell )。

_出于礼貌_,我们可以自动补偿众所周知的“流氓”情况,即调用批处理文件和某些引人注目的Microsoft CLI:

  • 批处理文件案例是一种通用案例,易于解释和概念化(例如,将a&b传递为"a&b" ,即使它不需要引用)也可以避免使用的--% ,所有使用批处理文件作为其入口点的CLI(这是很常见的),例如Azure的az.cmd

  • 特定CLI的硬编码异常的替代方法-无疑会引起混淆-是在PowerShell解析产生的参数中检测以下_pattern_- <word>=<value with spaces> -,而不是传递"<word>=<value with spaces>" ,当前发生的情况是,传递<word>="<value with spaces>" ; 后者满足“恶意” CLI,同时也被遵循惯例的CLI所接受; 例如, echoArgs "foo=bar baz"最终会看到与echoArgs --% foo="bar baz"相同的第一个参数

@musm您可以在https://github.com/PowerShell/PowerShell/blob/master/test/tools/TestExe/TestExe.cs上找到TestExe的源代码

的GitHub
适用于每个系统的PowerShell! 通过在GitHub上创建一个帐户为PowerShell / PowerShell开发做出贡献。

我认为默认情况下容纳异常只会导致与当前情况类似的情况,在此情况下,人们需要恢复PowerShell的“帮助”。 如果有例外,显然应该将其应用。

也许像这样:

# Arguments passed correctly, without regard for the program's ability to handle them
& $program a "" 'c "d e" f'
# Try to pass the arguments intelligently based on the program being called
&[] $program a "" 'c "d e" f'
# Escape the arguments for a batch file, eg) " -> ""
&[bat] $program a "" 'c "d e" f'

我真的很努力地为此找到语法。 如果您认为它是强制转换程序,则至少有这种意义,但是强制转换包含程序的实际变量将需要用括号括起来。

那,除了允许人们为他们想要的任何破坏行为添加例外之外,还有望消除对--% 。 然后,他们注册的异常类将具有一个确定其是否适用于该程序(用于智能调用)的方法,以及一个转义方法,在该转义方法中,仅将命令抽象语法树扔向它,并返回参数数组/字符串。

  • 而不是像现在那样传递"<word>=<value with spaces>"来传递<word>="<value with spaces>"

用于构造命令行的引号应遵循在PowerShell中引用调用的方式。 因此:

  1. 在其文本中不包含双引号的调用应在整个值周围加上双引号。
  2. 包含双引号的调用应保留它们,如脚本_if

特别是:
| 脚本参数| 命令行|
| ----------------- | ---------------- |
| p=l of v | “ v的p = l” |
| p=l` of` v | “ v的p = l” |
| p="l of v" | p =“ l of v” |
| p="l of v"'a s m' | p =“ l of v” a“ sm” |
| p="l of v"' s m' | p =“ l vsm”“ |

最后一行显示了一个示例,其中将不可能保留原始的双引号。

我知道我会在这里遇到很多麻烦,并且我非常感谢正在进行的深入讨论,但是..._ ducks _...在现实世界中,有没有人举过任何这样的例子呢? ?

一年前,我已经在此线程中提到了它(现在隐藏了……),在Powershell中使用ripgrep时,我经常得到很多WFT的

rg '"quoted"'

而在git bash中没有。

现在,我得到的WTF时间更少了,因为可悲的是,我发现了这个漫长的github问题,发现将"传递给Powershel完全被破坏了。 最近的“ git.exe”示例也很棒。

老实说,现在我什至不敢使用Powershell调用本地命令,因为我知道我可能会在字符串中传递"作为参数。 我知道我可能会得到错误的结果或错误。

真的, @ mklement0总结起来很棒(应该在某处刻上石头)

如前所述, shell
鉴于未正确传递带有嵌入式双引号和空字符串参数的参数,PowerShell当前无法完成此任务。
如前所述,在仅Windows的日子里,这可能不是一个大问题,因为缺乏功能强大的外部CLI很少会出现此问题,但是这些日子已经过去,并且如果PowerShell希望将自己建立为可靠的跨平台外壳程序,它必须解决这个问题

关于突破性变化。
最近,同事给我写信说我的脚本在他的机器上不起作用。 我只在Powershel Core上运行它,而他在Windows Powershell上运行它。 在Windows Powershell上显示带有“ BOM”的Out-File -Encoding utf8编码文件,而在Powershel Core上没有BOM。 这是一个无法删除的示例,但是表明Powershel中已经存在细微的重大变化,这很好,因为我们正在从一种著名的语言中消除怪癖和直观行为。 如果Powershel团队在应对变化方面更加宽容,那将是非常不错的事情,因为我们已经将跨平台Powershell交付到Windows之外,并且我们知道Windows Powershell处于“维护”模式,并且如果能够永久使用,您真的希望它运行一些旧脚本,这些旧脚本在Powershell的较新版本中损坏。

RE:突破性变化的最后一点-我完全同意。 由于各种原因,我们容忍了_many_项重大更改。 但是,越来越多的情况似乎是,某些突破性的更改只是出于偏爱的原因而被拒绝,而没有给予其实际价值适当的重视。

像这样的一些更改可以_massively_改善需要到达PowerShell外部才能完成任务的任何人的总体外壳体验,这种变化一直在发生。 一次又一次地达成共识,当前的行为是站不住脚的,并且除了最简单的用法以外,已经在很大程度上被破坏了。 但是,即使有数十种已经被接受的重大更改,我们仍然面临着对重大更改的抵触态度,其中有些具有类似的巨大影响。

对于那些要求示例的人-花一分钟时间一次访问Stack Overflow。 我确信@ mklement0上有很多示例,需要社区帮助来解释较新版本中的重大更改。 它总是发生。 我们没有理由不做出有益的重大改变。

每当MSFT团队重复一遍又一遍相同的事情时,我们都可以确保他们知道的比公开说的更多。 我们应该尊重他们的内在纪律,而不要向他们施加压力。 _也许我们可以找到一个折衷方案。_我希望我今天有时间描述延迟迁移的替代方法。

我确实意识到这一点,这就是为什么我很少提出质疑的原因。

但是,这是一个开源项目。 如果对这些决定没有任何可见性,人们将不可避免地最终感到沮丧。 在这枚硬币的两边都没有责备,这就是国际海事组织的现实情况。 是的,拥有迁移路径可能会在某种程度上减轻这种痛苦,但是我们需要定义明确的政策,说明如何工作才能使更多人受益。 但是,在缺乏信息时,很难达成妥协。

我期待看到您的袖手旁观。 😉

@ mklement0,您说的很对,以至于我现在只能回复您的元数据。 不幸的是,在我们无法达到回答此类问题所需的深度的情况下,更安全的方法通常是推迟或拒绝重大更改,直到我们有更多时间

不过,我想就破坏性变化提出另一个观点:我们的遥测意味着大多数PowerShell 7用户都无法管理自己的版本。 他们在舒适的托管环境中运行自动化脚本,例如将用户从6.2升级到7.0(请参阅从8/3开始的6.2用户从7.0用户升级为2天的过程;这不是我们这里唯一的数据点,但是这是很方便的,可以说明这一点)。 对于这些用户,将完美运行的脚本变成不正常运行的脚本的重大更改是不可接受的。

我还欠社区一个博客,介绍我如何看待突破性变化的影响:即权衡现有用法的普遍性和中断的严重性,而不是轻松识别和更正中断。 这在现有脚本中极为普遍,难以识别和修复,并且破坏行为是从完全成功到完全失败,因此,我非常愿意在此处执行任何操作。

我认为可以公平地说,我们不会在7.1中做任何事情,但是我绝对愿意将其作为7.2的调查重点(即,我们花费的时间不只是委员会在讨论这一点。)

任命委员会所咨询的特定主题的小组委员会对所涉及的问题确实有必要的了解如何?

我们正在为此。 我知道我之前已经说过,但是我们之间的距离非常近(例如,我正在撰写博客,您可能很快就会看到一些新的标签出现,我们将继续使用)。

我感谢每个人的耐心,并且我认识到,每当人们在讨论中投入大量想法和考虑时,每隔两周便会从委员会中得到一些小小的答复是令人讨厌的。 我知道这似乎意味着我们没有对事情进行深入思考,但我认为我们只是没有像人们在这里那样详细地表达我们的讨论深度。 在我自己的待办事项列表中,我有一整套的博客主题,例如围绕如何在委员会内部做出决策的方式进行的重大变革,但我从来没有机会坐下来提出自己的建议。 但是我在这里看到也许人们会从中发现很多价值。

希望我在这次讨论中不会过分。 我不希望这个问题完全成为有关项目管理的问题,但是我确实想解决我在这里看到的一些可以理解的挫败感。 我恳请愿意与我们详细讨论此问题的任何人下周加入社区电话在此处添加您的问题和想法,我将在电话中一定会解决)。

关于元数据的简短说明:@joeyaiello,感谢您的深思熟虑。

至于重大更改的严重性:以下陈述似乎不一致:

在现实世界中,有没有人举任何这方面的例子呢?

这一脚本在现有脚本中极为盛行,难以识别和修复

如果已经很普遍了,那么必要的解决方法的笨拙和晦涩就成为最终解决此问题的更多原因,尤其是考虑到我们预计案件数量将会增加。

我确实意识到所有现有的解决方法都将失效。

如果最重要的是避免这种情况,那么建议采用以下

Native模块提供一个_new运算符或一个function_(例如ie函数)来解决该问题,并将其广泛宣传为调用外部可执行文件的唯一可靠方法。

诸如ie这样的_function_可以使人们以_minus fuss_作为一种权宜之计来选择正确的行为,而不会给_language_加上新的语法元素(运算符),后者的唯一存在理由是解决遗留得难以修复的遗留错误:

  • 权宜之计将为官方提供对正确行为的批准(不依赖于实验性功能)。
  • 只要权宜之计是必要的,就需要广泛宣传和适当记录在案。

如果/当默认行为得到解决时:

  • 可以修改该函数以使其顺从,以免破坏使用该函数的代码。
  • 无需功能即可编写新代码。

诸如ie之类的功能可以使人们以最小的麻烦选择参加正确的行为,这是权宜之计

我们可以通过#13428简化采用。 我们可以将@ mklement0的调查透明地注入到Engine中。

@达博伯

我认为默认情况下容纳异常只会导致与当前情况类似的情况,在此情况下,人们需要恢复PowerShell的“帮助”。 如果有例外,显然应该将其应用。

我提议的适应机制使绝大多数调用“正常工作”-它们是Windows命令行无政府状态的有用抽象,我们应该尽最大努力使用户免受攻击。

合理的假设是,这种屏蔽对于_legacy_可执行文件是一次性的工作,而新创建的可执行文件将遵循Microsoft C / C ++约定。

但是,在所有情况下都不可能这样做。 对于那些无法自动容纳的对象,有--%

就我个人而言,我不想考虑给定实用程序foo恰好实现为foo.batfoo.cmd ,或者是否需要foo="bar none"参数,而不接受"foo=bar none" ,这对于符合约定的可执行文件是等效的。

而且我当然不希望针对各种异常使用单独的语法形式,例如&[bat]
相反, --%是(仅Windows)全部使用的工具,用于完全按照您希望的方式来编写命令行-无论目标程序的特定,非常规要求如何。

具体来说,建议的住宿如下:

注意:

  • 如前所述,仅在Windows_上是必需的。 在Unix上,委派ProcessStartInfo.ArgumentList足以解决所有问题。

  • 至少在较高层次上,这些适应性易于概念化和记录。

  • 请注意,在(仅限Windows)“转换为进程的命令行”步骤中,将在PowerShell的常规解析后应用它们。 也就是说,将不涉及PowerShell自身的参数解析-且不应该@ yecril71pl。

  • 这些便利条件未涵盖的任何真正异国情调的案例,都必须由用户自己处理,
    --% -或者,在安装了Native模块的情况下,使用ins / Invoke-NativeShell ,这使得在调用中更轻松地嵌入PowerShell变量和表达式值。

    • Native模块中的dbeaDebug-ExecutableArguments )命令可以帮助诊断和了解最终使用的过程命令行-请参阅下面的示例部分。

住宿清单:

  • 对于批处理文件(如上所述,自动适应这种情况的重要性是使用批处理文件作为入口点_的高配置CLI的普遍性,例如Azure的az.cmd )。

    • 嵌入式双引号(如果有的话)在所有参数中都以"" (而不是\" )转义。
    • 任何不包含空格但包含双引号或cmd.exe元字符(例如& )的参数都双引号引起来(而PowerShell默认情况下仅用双引号引起来的参数)。 例如,PowerShell视作a&b的逐字自变量在传递给批处理文件的命令行中作为"a&b"放置。
  • 对于高端的CLI,例如msiexec.exe / msdeploy.execmdkey.exe (它们没有硬编码例外):

    • 至少包含以下形式的一个参数的任何调用都会触发以下行为: <word>可以由字母,数字和下划线组成:

      • <word>=<value with spaces>
      • /<word>:<value with spaces>
      • -<word>:<value with spaces>
    • 如果存在这样的论点:

      • 嵌入式双引号(如果有的话)在所有参数中均以"" (而不是\" )转义。 -有关为什么我们不应该这样做的信息,请参见https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167; 这意味着在极少数情况下, <value with spaces>具有_embedded_ "字符,必须使用--% ; 例如,
        msiexec ... --% PROP="Nat ""King"" Cole"
      • 只有<value with spaces>部分被双引号括起来,而不是争论作为一个整体(后者是什么的PowerShell -名正言顺的-默认完成); 例如,PowerShell视为foo=bar none的逐字自变量在进程的命令行上放置为foo="bar none" (而不是"foo=bar none" )。
    • 注意:

      • 如果目标可执行文件不是msiexec样式的CLI,则不会造成任何危害,因为遵循约定的CLI会明智地考虑<word>="<value with spaces>""<word>=<value with spaces>" _equivalent_,它们都表示逐字记录<word>=<value with spaces>

      • 同样,绝大多数可执行文件可以与\" ""互换使用\"来转义嵌入式"字符。PowerShell自己的CLI,Ruby和Perl(_not_至少在调用PowerShell CLI时值得这样做,但是我认为对Ruby和Perl进行硬编码也很有意义。 https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167显示所有使用CommandLineToArgvW WinAPI函数的应用程序都_not_支持""转义。

Windows上的所有其他情况也可以使用ProcessStartInfo.ArgumentList来处理,这隐式地应用了Microsoft C / C ++约定(值得注意的是, \"用于"转义)。


ie从当前版本(功能1.0.7 )的的Native模块实现这些住宿(除了固定断裂参数解析),PowerShell的版本3及以上Install-Module Native )。

我邀请您和在座的每个人都努力进行测试,以检验这种说法“适用于”绝大多数外部可执行呼叫

当前不可避免的限制:

  • 注意:这些技术限制来自将ie作为_function_实现(对引擎本身进行适当的修复会_not_出现以下问题):

    • 虽然$LASTEXITCODE已正确设置为进程的退出代码,但$?最终总是$true结尾-用户代码当前无法显式设置$? ,尽管添加了此功能一直亮起绿色-请参阅https://github.com/PowerShell/PowerShell/issues/10917#issuecomment -550550490。 不幸的是,这意味着您当前无法将ie&&|| (即&&管道链运算符一起有意义地使用。
      但是,如果需要一个用于检测非零退出代码的脚本,则可以使用iee包装函数。

    • --作为参数总是被PowerShell参数绑定器“吃掉”; 只需将其传递_twice_即可将--传递到目标可执行文件( foo -- --而不是foo -- )。

    • 必须用带引号,的未引号引起来,以免将其解释为数组并作为多个参数传递。 例如,传递'a,b'而不是a,b ; 同样,将-foo:bar (类似于命名的PowerShell参数)传递为'-foo:bar' (这不是必需的,但要归功于错误:#6360); 类似地,'-foo.bar must be passed as '-foo.bar'`(另一个错误,也影响对外部可执行文件的直接调用:#6291)

  • 我希望该功能在PowerShell _Core_中能够正常运行。 由于_Windows PowerShell_随时间的变化,虽然我只知道以下两种情况,但可能存在无法正确处理的极端情况:

    • msiexec样式的CLI的部分引用容纳不能在版本3和版本4中应用,因为这些版本将整个参数包装在另一组双引号中。 它确实可以在v5.1中使用。

    • 默认情况下,使用""转义来解决问题,但是在需要\" (PowerShell CLI,Perl,Ruby)的情况下,则使用3" of snow这样的令牌。错误地将其作为_3_参数传递,因为所有Windows PowerShell版本都忽略了将此类参数用双引号引起来; 对于带有非初始"字符且不带空格字符的参数,似乎会发生这种情况。


Windows 10上PowerShell Core 7.1.0-preview.5的输出示例:

注意: dbeaDebug-ExecutableArguments )函数用于说明外部可执行文件/批处理文件如何接收参数。

当前无效的参数传递:

  • 调用符合约定的应用程序(默认情况下, dbea在后台使用的.NET控制台应用程序):
# Note the missing 2nd argument and the effective loss of embedded double quotes,
# due to the embedded " chars. not having been escaped.
PS> dbea -- 'a&b' '' '{ "foo": "bar" }'

2 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <{ foo: bar }>

Command line (helper executable omitted):

  a&b  "{ "foo": "bar" }"
  • 调用批处理文件:

请注意,使用-UseBatchFile来使dbea传递参数给助手_batch file_。

# Note that only *part of the first argument* is passed and that the `&` is interpreted as cmd.exe's
# statement separator, causing `b` to be run as a command (which fails).
PS> dbea -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

1 argument(s) received (enclosed in <...> for delineation):

  <a>

'b' is not recognized as an internal or external command,
operable program or batch file.
  • 调用msiexec样式的CLI, cmdkey.exe
# The call fails, because `cmdkey.exe` requires the password argument to 
# to be quoted exactly as `/password:"bar none"` (double-quoting of the option value only), 
# whereas PowerShell - justifiably - passes `"/password:bar none"` (double-quoting of the whole argument).
PS> cmdkey.exe /generic:foo /user:foo /password:'bar none'

The command line parameters are incorrect.

使用ie解决问题:

请注意,在dbea调用中使用了-ie ,这导致在调用中使用ie

  • 调用符合约定的应用程序(默认情况下, dbea在后台使用的.NET控制台应用程序):
# OK
# Note that the empty 2nd argument is correctly passed, and that \" is used for embedded "-escaping.
PS> dbea -ie -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <>
  <{ "foo": "bar" }>

Command line (helper executable omitted):

  a&b "" "{ \"foo\": \"bar\" }"
  • 调用批处理文件:
# OK
# - `a&b` was enclosed in "...", due to the presence of metacharacter `&`
# - "" is used for escaping of embedded " chars.
# Note that `echo %1`, for instance, prints the argument exactly as passed on the command line, including quoting.
# `echo %~1` strips the surrounding double quotes, but embedded escaped ones still print as "".
# However, if you pass these arguments (`%*`) through to convention-compliant CLIs, they are parsed correctly.
PS> dbea -ie -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <"a&b">
  <"">
  <"{ ""foo"": ""bar"" }">
  • 调用msiexec样式的CLI, cmdkey.exe
# The call now succeeds, because `ie` ensure the value-only double-quoting that cmdkey.exe requires.
# (Use `cmdkey /del:foo` to remove the credentials again.)
PS> ie cmdkey.exe /generic:foo /user:foo /password:'bar none'

CMDKEY: Credential added successfully.

若要显示仅值双引号已通过dbea在实际命令行中应用:

PS> dbea -ie -- cmdkey.exe /generic:foo /user:foo /password:'bar none'

  <cmdkey.exe>
  </generic:foo>
  </user:foo>
  </password:bar none>

Command line (helper executable omitted):

  cmdkey.exe /generic:foo /user:foo /password:"bar none"

以下代码导致数据丢失:

{ PARAM($A) $A } | OUT-FILE A.PS1
PWSH A.PS1 -A:(1,2)

1个

@JamesWTruher有一个建议的修复程序,正在验证是否可以解决此问题中提出的问题。

是否有该建议的修复的拉动请求? 如果我们可以对PR发表评论,那就太好了。 因为修复此问题是IMO从未涉及过的复杂部分。 复杂的部分是如何处理向后兼容。 如果我们能够看到建议的修复程序如何处理它,那就太好了。

听到@ SteveL-MSFT真是太好了-这里讨论的_all_问题的修复程序? 毕竟对于v7.1? 我第二次@TSlivede的请求。

@ yecril71pl ,这是一个很好的发现,尽管这确实与PowerShell自身的解析(确实具有用于外部可执行文件的_some_特殊外壳)有关,而不与本机命令行的构造方式_after_解析有关(这是前面讨论的问题所在)从)。

在Unix上,该问题更简洁地再现:

PS> printf '<%s>\n' -a:(1,2,3)
<-a:1>
<2>
<3>

也就是说,只有_first_数组元素直接附加到-a: ,其他元素作为单独的参数传递。

_look like_ PowerShell参数的参数存在相关问题,但不是:

有一个相关问题仅影响对使用$args / @args _PowerShell_命令的调用:#6360

  • & { $args.Count; $args } -foo:bar产生2, '-foo:', 'bar'

还有#6291,它会影响PowerShell命令和外部可执行文件(请注意. )。

  • & { $args.Count; $args } -foo.bar产生2, '-foo', '.bar'

需要注意的一件事是,作为裸字一部分的(...)通常会导致(...)的输出整体上成为_separate_参数,因此第一个元素_is_附加在printf命令是对外部可执行调用进行特殊处理的证据; 例如,
& { $args.Count; $args.ForEach({ "$_" }) } foo('bar', 'baz')产生2, 'foo', 'bar baz' ,第二个参数是数组'bar', 'baz'的字符串化。

当PowerShell必须为外部可执行文件传递-A:(1,2) ,它会发现-A:是一个字符串,而(1,2)是一个必须编组为“ 1 2”的数组。 PowerShell尝试保留调用的原始语法,因此将所有内容放在一起,我们得到“ -A:1 2”,而正确的结果将是“ -A:“ 1 2””。 在我看来,编组代码中的琐碎小事。

我不会说@ yecril71pl的特定问题与解析有关(尽管我同意,它与“将数组转换为命令行”这一问题无关,在本期中将对此进行讨论)。

当PowerShell必须为外部可执行文件传递-A:(1,2)时,它会得出-A:是字符串,而(1,2)是数组

几乎: -A:是一个命名参数,而数组是该参数的值(要测试一下:删除前面的- ,您会发现其引用不同)。 但是问题不在于数组是否被错误地转换为字符串-问题在于,对于本机可执行文件,参数(几乎)总是被压缩,即使使用$而不是@即使该数组源自(1,2)类的表达式。

例如,测试printf '<%s>\n' -a:('a b',2) :由于字符串a b包含一个空格,所以它正确地用引号引起了,但是由于2在下一个数组元素中并且该数组被分隔,因此2不是第一个参数的一部分。


魔术发生在NativeCommandParameterBinder.cs中

170行, powershell尝试获取当前参数值的枚举数。

IEnumerator list = LanguagePrimitives.GetEnumerator(obj);

如果list不是null ,则powershell将列表的每个元素(如果包含空格,则可能引用)添加到lpCommandLine。

默认情况下,元素之间用空格分隔(第449行)。 唯一的例外是如果数组是文字
(如printf '<%s>\n' -a:1,2 )。
然后,powershell尝试在lpCommandLine中使用与脚本行中相同的分隔符。

我准备好了,希望公关。 如果将其设为7.1,我们将采用它,否则将为7.2。 向后兼容性是他要解决的问题。 也许会有所帮助的是编写Pester测试(使用testexe -echoargs ,可以使用build.psm1中的publish-pstesttools构建)。

我准备好了会公关

恰恰是我要避免的事情-请显示尚未准备好的代码(将PR标记为正在进行中)。

或至少发表评论,说他想做什么。

如果我们可以讨论他如何处理向后兼容性,那将是很好的。

@TSlivede ,请注意,由于调用了PowerShell _CLI_-外部可执行文件-解析了-A:(1,2) _before_知道此令牌最终将绑定到_named_ -A参数-这样的参数_eventually_起作用了是这个问题附带的。

@ yecril71pl

计算出-A:是一个字符串

不,它在解析过程中是特殊情况,因为它碰巧碰到PowerShell参数的looklike。

这种特殊情况也会发生在使用$args PowerShell命令调用上(而不是绑定实际的已声明参数),但对于外部可执行文件却会_differently_(通常会附加一个单独的参数,但如果是集合,则仅提供其_first_元素)。

如果事先传递了-- ,则实际上可以选择退出此特殊情况,但是,当然,这也将传递-- ,仅在调用_PowerShell_命令时才会删除:

PS> printf '<%s>\n' -- -a:(1,2,3)
<-->   # !! not removed
<-a:>
<1>    # array elements are *all* now passed as indiv. arguments, because (...) output is separate (splatted) argument
<2>
<3>

如果参数_doesn't_看起来像PowerShell参数,则即使对于外部可执行文件,通常的行为(来自(...)成为单独的参数)(带有_array_的通常行为,即变成了外部可执行情况下的各个参数)。

# Note: No "-" before "a:" -> output from `(...)` becomes separate argument(s)
PS> printf '<%s>\n' a:(1,2,3)
<a:>
<1>
<2>
<3>

_consistently_应用此行为将是有道理的-如果(...) _ (...)表达式作为裸词的一部分-参见#13488。

为了通过数组_stringified_传递单个参数'-A:1 2 3' ,请使用(n隐式)_expandable string_,在这种情况下,您需要$(...)而不是(...) _and_-令人惊讶-目前也"..."

PS> printf '<%s>\n' "-a:$(1,2,3)"  # quotes shouldn't be needed; `-a:"$(1,2,3)"` would work too.
<a:1 2 3> # SINGLE argument with stringified array.

在这种情况下,您也应该不需要"..." -这又是必要的,因为与_look_ like参数有关的令牌异常(通常适用于_both_ PowerShell和外部可执行调用-参见#13489); 如果没有,则无需引用:

# Anomaly due to looking like a parameter: $(...) output becomes separate argument
PS> Write-Output -- -a:$(1,2,3)
-a:
1
2
3

# Otherwise (note the absence of "-"): no quoting needed; treated implicitly like 
# "a:$(1,2,3)"
PS> Write-Output -- a:$(1,2,3)
a:1 2 3  # SINGLE argument with stringified array.

参数模式下的复合令牌世界是一个复杂的世界,存在多个不一致之处-参见#6467。

@ SteveL-MSFT

在当前形式下, testexe -echoArgs仅打印从原始命令行(在Windows上)而不是原始命令行本身分析的.NET Core可执行文件解析的各个参数。

因此,它不能使用批处理文件和msiexec样式的CLI的选择性引用来测试适应性-假设将实现这种适应性,我强烈建议这样做; 例如,您将无法验证PROP='foo bar'是作为PROP="foo bar"传递的,在值部分周围用双引号引起来。

但是,为了打印原始命令行, testexe一定不能是.NET _Core_可执行文件,因为.NET Core _会重新创建一个假设的命令行_,该命令行始终使用\" -对嵌入式"转义。 "" ,并且通常也不能忠实地反映出哪些参数被双引号和哪些没有用-有关背景,请参见https://github.com/ dotnet / runtime / issues / 11305#issuecomment -674554010。

只有.NET _Framework_编译的可执行文件在Environment.CommandLine显示了真正的命令行,因此testexe必须以这种方式进行编译(并更改为(可选)打印原始命令行)。

要测试批处理文件的适应性,需要单独的测试_batch_文件,以验证'a&b'作为"a&b"'a"b'作为"a""b"传递,用于实例。

无法为.NET Framework进行

或者,我们可以仅依靠简单的bash脚本来发布Linux / macOS的args吗?

#!/bin/bash
for i; do
   echo $i
done

在Windows上,与批处理文件类似。

如何将节点与.js脚本一起使用?

console.log(process.execArgv.join('\n')或任何处理您的字符串
想使输出看起来不错吗?

@cspotcode ,要获取原始命令行,我们需要一个WinAPI调用。

@ SteveL-MSFT:

在Windows上,您可以通过其CLI将编译委托给_Windows PowerShell_,这是我在dbea所做的; 这是一个简单的示例,生成一个.NET _Framework_可执行文件,该可执行文件回显原始命令行(仅) ./rawcmdline.exe

powershell.exe -noprofile -args ./rawcmdline.exe -c {

  param([string] $exePath)

  Add-Type -ErrorAction Stop -OutputType ConsoleApplication -OutputAssembly $exePath -TypeDefinition @'
using System;
static class ConsoleApp {
  static void Main(string[] args) {
    Console.WriteLine(Environment.CommandLine);
  }
}
'@

}

样品电话:

PS> ./rawcmdline.exe --% "a&b" PROP="foo bar"
"C:\Users\jdoe\rawcmdline.exe"  "a&b" PROP="foo bar"

对于回显其参数的_batch file_, dbea也会按需创建一个

在Unix上,如您的注释中所示,简单的shell脚本确实足够了,您甚至可以

@ PowerShell / powershell-committee今天讨论了这个问题,我们要求@JamesWTruher更新其PR,以将其作为实验功能的一部分,以跳过本机命令处理器中的步骤,该命令处理器将args数组重新

对于那些可能没有注意到的人:PR已发布(作为WIP),并且已经在讨论中: https :

PS,@ SteveL-MSFT,关于在Windows上获取原始命令行:当然,将编译委派给Windows PowerShell / .NET _Framework_的替代方法是增强现有的.NET _Core_控制台应用程序,使其成为(平台条件的) P / Invoke调用GetCommandLine() WinAPI函数,如下所示。

using System;
using System.Runtime.InteropServices;

namespace demo
{
  static class ConsoleApp
  {
    [DllImport("kernel32.dll")]
    private static extern System.IntPtr GetCommandLineW();

    static void Main(string[] args)
    {
      Console.WriteLine("\n{0} argument(s) received (enclosed in <...> for delineation):\n", args.Length);
      for (int i = 0; i < args.Length; ++i)
      {
        Console.WriteLine("  <{0}>", args[i]);
      }

      // Windows only: print the raw command line.
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        Console.WriteLine("\nCommand line:\n\n  {0}\n", Marshal.PtrToStringUni(GetCommandLineW()));
      }

    }

  }

}

@ SteveL-MSFT

向后兼容性是他要解决的问题。

我认为您稍后的澄清隐含了ProcessStartInfo.ArgumentList (在Unix上按原样使用_verbatim_参数的_collection_,并在Windows上由.NET Core本身转换为与MS C / C ++常规兼容的命令行) ),但让我明确说明一下:

  • _一劳永逸地正确解决此问题,_禁止向后兼容_做出任何让步。

  • 在撰写本文时, @ JamesWTruher的PR处于正确的轨道上,唯一的问题似乎是仍然没有传递空参数

    • 解决该问题后,此修复程序将在_Unix_上完成-但缺少_Windows_上的CLI的重要功能(请参见下文)。

对于特殊情况下已知的命令,我们可能需要一个允许列表,这些命令仍然因建议的更改而失败,并且可以在以后添加。

我敦促你不要推迟这个

代替_allowlist_(用于特定可执行文件的特殊外壳),可以使用简单的通用规则来管理这些容纳,在与@TSlivede进行更多讨论之后,可以上面的容纳中精炼它们

这些适应(仅在Windows上需要):

具体来说,这些是:

  • 就像在Unix上一样,默认情况下使用ProcessStartInfo.ArgumentList -除非满足以下一个或两个条件,在这种情况下,必须手动构造过程命令行_(并分配给ProcessStartInfo.Arguments作为目前):

    • 批处理文件( .cmd.bat )或cmd.exe直接称为:
    • 在这种情况下,_embedded_ "被转义为"" (而不是\" ),并且包含以下任何cmd.exe元字符的无空间参数也用双引号引起来(通常,只有参数_withspaces_被双引号引起来): " & | < > ^ , ; -这将使对批处理文件的调用工作可靠,这很重要,因为许多高配置的CLI都将批处理文件用作_entry points_。
    • 独立(并且可能另外),如果至少一个与正则表达式匹配的参数
      '^([/-]\w+[=:]|\w+=)(.*? .*)$'存在,所有此类参数必须在_value部分only_周围加上_partial_双引号( :=

      • 例如,PowerShell逐字显示为msiexec.exe / msdeploy.execmdkey.exe -样式的参数
        FOO=bar baz/foo:bar baz / -foo:bar baz将放在进程命令行上,如下所示:
        foo="bar baz"/foo:"bar baz" / -foo:"bar baz"使需要这种风格的_any_ CLI感到高兴。
    • 参数中的逐字\字符必须按照MS C / C ++约定进行处理。

这些住宿未涵盖什么:

  • msiexec.exe (大概也有msdeploye.exe )支持_only_ ""转义为_embedded_ "字符。 ,以上规则将“不”覆盖-除非您碰巧通过批处理文件或cmd /c调用。

    • 首先应该很少见(例如,
      msiexec.exe /i example.msi PROPERTY="Nat ""King"" Cole" ),但由于misexec调用通常是_made_sync_来等待安装结束的事实,因此这种情况可能变得更加罕见,在这种情况下,您可以避免以下情况之一两种方式:
    • cmd /c start /wait msiexec /i example.msi PROPERTY='Nat "King' Cole' -依靠对cmd.exe调用(然后)触发""转义
    • Start-Process -Wait msiexec '/i example.msi PROPERTY="Nat ""King"" Cole"' -依靠-ArgumentList-Args )参数通过逐字作为过程命令行传递单个字符串参数(即使该参数不应该如此工作-请参阅#5576)。
  • 上述容纳条件还不足的任何其他非常规CLI,我个人都不知道。

总而言之,总有一种解决方法:通过cmd /c调用,或者对于非控制台应用程序,通过Start-Process调用,或者使用--% ; 如果并且当我们提供insInvoke-NativeShell )cmdlet时,这是另一种选择; 一个dbeaDebug-ExecutableArguments cmdlet和echoArgs.exe样的能力,但需求也为批处理文件,也将有助于_diagnose_问题。


至于重大变革与选择加入的途径:

  • 将其实现为实验功能是否意味着如果显示出足够的兴趣,它将成为_default_行为,并因此构成(不平凡的)重大变化?

  • 考虑到它的重要性,您能否确保此实验功能得到广泛宣传?

    • 我对实验性功能的一个普遍关注是,鉴于默认情况下_all_实验性功能处于启用状态,因此在预览版本中常常可以不使用它们。 我们绝对希望人们故意了解并使用此功能。
此页面是否有帮助?
0 / 5 - 0 等级