Powershell: Argumentos para executáveis ​​externos não têm escape correto

Criado em 21 ago. 2016  ·  170Comentários  ·  Fonte: PowerShell/PowerShell

Passos para reproduzir

  1. escrever um programa C native.exe que adquire ARGV
  2. Execute native.exe "`"a`""

    Comportamento esperado

ARGV [1] == "a"

Comportamento real

ARGV [1] == a

Dados ambientais

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

Comentários muito úteis

Criar um operador especial para isso não faz sentido para um shell de linha de comando, já que sua principal tarefa é lançar programas e passar argumentos para eles. Introduzir um novo operador que faz system() para este trabalho é como Matlab apresentando uma maneira de chamar calc.exe porque tem um bug em sua aritmética. O que deveria ser feito é o seguinte:

  • A equipe pwsh se prepara para uma nova versão principal que corrige as coisas da linha de comando, movendo o comportamento atual para trás de um cmdlet integrado.
  • Como uma solução temporária, a próxima versão do pwsh obtém um cmdlet integrado que usa o comportamento novo e correto para passagem de linha de comando.

O mesmo se aplica a Start-Process . (Na verdade, é um bom candidato para o "novo" cmdlet com algumas opções como -QuotingBehavior Legacy ...) Consulte # 13089.

Todos 170 comentários

Por que você acha que "\"a\"" o comportamento esperado? Minha compreensão das fugas do PowerShell diz que o comportamento real é o comportamento correto e esperado. " "a "" é um par de aspas em torno de um par de aspas com escape em torno de a , então o PowerShell interpreta o par externo sem escape como" este é um argumento de string "e então os descarta, então interpreta o par escapado como aspas escapadas e assim os mantém, deixando você com "a" . Em nenhum momento um \ adicionado à string.

O fato de o Bash usar \ como caractere de escape é irrelevante. No PowerShell, o caractere de escape é um crachá. Consulte caracteres de escape do PowerShell.

Se você quiser passar literalmente "\"a\"" , acredito que você usaria:

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

@andschwa
Sim, escapes funcionam bem para cmdlets internos, mas as coisas ficam estranhas quando se comunicam com binários nativos , especialmente no Windows.
Ao executar native.exe " "a "" , o ARGV [1] deve ser

"a"

(três caracteres)

ao invés de

a

(um personagem).

Atualmente, para fazer native.exe receber corretamente um ARGV com duas aspas e um caractere a , você deve usar esta chamada estranha:

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

Ah, entendo. Reabrindo.

Por forte curiosidade, o que acontece se você tentar uma construção usando # 1639?

@andschwa O mesmo. Você PRECISA ter um esforço duplo para satisfazer o PowerShell e CommandLineToArgvW . Está linha:

native.exe "`"a`""

resulta em um StartProcess igual a cmd

native.exe ""a""

@ be5invis @douglaswth isso foi resolvido em https://github.com/PowerShell/PowerShell/pull/2182?

Não, ainda precisamos adicionar uma barra invertida antes de uma aspa dupla com escape de crase? Isso não resolve o problema do escape duplo. (Ou seja, temos que escapar aspas duplas para PowerShell e CommandLineToArgvW.)

Visto que " "a "" é igual a '"a"' , você sugere que native.exe '"a"' deve resultar em "\"a\"" ?

Esta parece ser uma solicitação de recurso que, se implementada, pode quebrar um grande número de scripts do PowerShell já existentes que usam o escape duplo necessário, portanto, muito cuidado seria necessário com qualquer solução.

@vors Sim.
@douglaswth O escape duplo é realmente bobo: por que precisamos dos escapes “internos” feitos na era DOS?

@vors @douglaswth
Este é o código C usado para mostrar os resultados GetCommandLineW e CommandLineToArgvW:

#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);
}

Aqui está o resultado

$ ./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 Não discordo de você sobre o escape duplo ser irritante, mas estou apenas dizendo que uma mudança nisso precisaria ser compatível com o que os scripts do PowerShell existentes usam.

Quantos são eles? Não acho que existam roteiristas que conheçam essas citações duplas. É um bug, não um recurso, e não está documentado.

???? Iphone

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

@ be5i nvishttps: //github.com/be5invis Não discordo de você sobre o duplo escape ser irritante, mas estou apenas dizendo que uma mudança para isso precisaria ser compatível com o que os scripts do PowerShell existentes usam.

Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o em Gi tHubhttps: //github.com/PowerShell/PowerShell/issues/1995#issuecomment -248381045 ou desative o som do

O PowerShell existe há 9 anos, então é muito provável que haja um bom número de scripts por aí. Eu encontrei muitas informações sobre a necessidade de escape duplo de StackOverflow e outras fontes quando encontrei a necessidade, então não sei se concordo com suas afirmações de que ninguém sabe sobre a necessidade ou que não está documentado .

Para o contexto adicional, gostaria de falar um pouco sobre a implementação.
O PowerShell chama a API .NET para gerar um novo processo, que chama uma API Win32 (no Windows).

Aqui, o PS cria StartProcessInfo que usa
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs#L1063

A API fornecida pega uma única string para argumentos e então é analisada novamente em uma matriz de argumentos para fazer a execução.
As regras dessa nova análise não são controladas pelo PowerShell. É uma API Win32 (e felizmente, é consistente com o núcleo dotnet e regras unix).
Particularmente, este contrato descreve o comportamento de \ e " .

Embora o PowerShell possa tentar ser mais inteligente e fornecer uma experiência mais agradável, o comportamento atual é consistente com cmd e bash: você pode copiar a linha executável nativa deles e usá-la no PowerShell e funciona da mesma forma.

@ be5invis Se você conhece uma maneira de melhorar a experiência de forma https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md

Isso se aplica ao Windows, mas ao executar comandos no Linux ou Unix, é estranho que seja necessário inserir aspas duplas.

No Linux, os processos não têm uma única linha de comando, mas sim uma série de argumentos.
Portanto, os argumentos no PowerShell devem ser iguais aos que são passados ​​para o executável, em vez de mesclar todos os argumentos e depois dividi-los novamente.

Mesmo no Windows, o comportamento atual é inconsistente:
Se um argumento não contém espaços, ele é passado sem alterações.
Se um argumento contiver espaços, se estiver entre aspas, para mantê-lo junto por meio da chamada CommandLineToArgvW . => O argumento foi alterado para atender ao requisito de CommandLineToArgvW .
Mas se o argumento contém aspas, elas não são escapadas. => O argumento não é alterado, embora CommandLineToArgvW exija isso.

Acho que os argumentos nunca devem ser alterados ou sempre devem ser alterados para atender aos requisitos de CommandLineToArgvW , mas não na metade dos casos.

Em relação à quebra de contrato:
Como não consegui encontrar nenhuma documentação oficial sobre escape duplo, consideraria isso como categoria "Balde 2: Área Cinza Razoável", então há chances de mudar isso, ou estou errado?

@vors Isso é extremamente irritante se o seu argumento for uma variável ou outra coisa: você precisa escapar manualmente antes de enviá-lo para um aplicativo nativo.
Um operador de "escape automático" pode ajudar. como ^"a " " -> "a\ " "`

Acho que @TSlivede corrigiu a inconsistência no comportamento.

Acho que os argumentos nunca devem ser alterados ou sempre devem ser alterados para atender aos requisitos de CommandLineToArgvW, mas não na metade dos casos.

Não tenho certeza sobre o balde, mas até mesmo o balde "claramente quebrando mudanças" poderia ser alterado. Queremos tornar o PowerShell melhor, mas a compatibilidade com versões anteriores é uma de nossas maiores prioridades. É por isso que não é tão fácil.
Temos uma ótima comunidade e estou confiante de que podemos encontrar um consenso.

Alguém gostaria de iniciar um processo de RFC?

Seria interessante investigar o uso de P / Invoke em vez de .Net para iniciar um processo se isso evitasse a necessidade do PowerShell adicionar aspas aos argumentos.

@lzybkr , tanto quanto eu posso dizer, PInvoke não ajudaria.
E é aqui que as APIs do Unix e do Windows são diferentes:

https://msdn.microsoft.com/en-us/library/20y988d2.aspx (trata os espaços como separadores)
https://linux.die.net/man/3/execvp (não trata os espaços como separadores)

Eu não estava sugerindo mudar a implementação do Windows.

Eu tentaria evitar comportamentos específicos da plataforma aqui. Isso prejudicará a portabilidade dos scripts.
Acho que podemos considerar a mudança de comportamento do Windows de uma forma ininterrupta. Ou seja, com variável de preferência. E então podemos ter diferentes padrões ou algo parecido.

Estamos falando sobre chamar comandos externos - um tanto dependente da plataforma de qualquer maneira.

Bem, eu acho que não pode ser realmente independente da plataforma, já que o Windows e o Linux têm maneiras diferentes de chamar executáveis. No Linux, um processo obtém um array de argumentos, enquanto no Windows um processo obtém apenas uma única linha de comando (uma string).
(compare o mais básico
CreateProcess -> linha de comando (https://msdn.microsoft.com/library/windows/desktop/ms682425)
e
execve -> array de comando (https://linux.die.net/man/2/execve)
)

Como o Powershell adiciona essas aspas quando os argumentos têm espaços, me parece que o PowerShell tenta ** passar os argumentos de uma forma, que CommandLineToArgvW divide a linha de comando para os argumentos que foram originalmente fornecidos no PowerShell. (Desta forma, um programa c típico obtém os mesmos argumentos em sua matriz argv que uma função powershell obtém como $ args.)
Isso corresponderia perfeitamente a apenas passar os argumentos para a chamada do sistema do linux (como sugerido via p / invoke).

** (e falha, pois não escapa as aspas)

PS: O que é necessário para iniciar um processo RFC?

Exatamente - o PowerShell tenta garantir que CommandLineToArgvW produza o comando correto e _após_ reanalisando o que o PowerShell já analisou.

Este tem sido um ponto problemático de longa data no Windows, vejo um motivo para trazer essa dificuldade para * nix.

Para mim, isso parece um detalhe de implementação, sem realmente precisar de um RFC. Se mudarmos o comportamento no Windows PowerShell, isso pode justificar um RFC, mas mesmo assim, a mudança certa pode ser considerada uma correção de bug (possivelmente arriscada).

Sim, eu também acho que mudá-lo no Linux para usar uma chamada de sistema direta deixaria todos mais felizes.

Eu ainda acho que também deveria ser alterado no windows,
(Talvez adicionando uma variável de preferência para aqueles que não querem alterar seus scripts)
porque está errado agora - é um bug. Se isso fosse corrigido, um syscall direto no linux nem seria necessário, pois qualquer argumento alcançaria o próximo processo inalterado.

Mas como existem executáveis ​​que dividem a linha de comando de uma forma, incompatível com CommandLineToArgvW , eu gosto da ideia do @ be5invis de um operador para argumentos - mas eu não criaria um operador de escape automático (deveria ser padrão para todos os argumentos), mas em vez disso, adicione um operador para não escapar de um argumento (não adicione aspas, não faça escape de nada).

Esse problema surgiu para nós hoje, quando alguém tentou o seguinte comando no PowerShell e estava criticando o PowerShell quando ele não funcionou, mas o CMD sim:

wmic useraccount where name='username' get sid

Em PSCX echoargs, wmic.exe vê o seguinte:

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

Então, qual API o CMD.exe usa para invocar o processo / formar a linha de comando? Por falar nisso, o que -% faz para que esse comando funcione?

@rkeithhill CreateProcessW . chamada direta. realmente.

Por que o Powershell está se comportando de maneira diferente nessas duas situações? Especificamente, ele envolve argumentos que contêm espaços entre aspas duplas de maneira inconsistente.

# 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...

Parece não haver rima ou razão. Na primeira situação, ele envolve meu argumento com aspas duplas, mas na segunda situação não. Preciso saber exatamente quando isso acontecerá ou não entre aspas duplas para que eu possa quebrar manualmente (ou não) em meu script.

.echoargs.exe é criado compilando o seguinte com cl echoargs.c

// 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");
}

EDIT: Esta é minha $ 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

O comportamento em relação às cotações mudou várias vezes, portanto, sugiro usar algo assim:

Editar: formulário atualizado abaixo
Versão antiga:

# 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\\" \'

Resultado:

<"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\\" \>

O primeiro -replace dobra as barras invertidas antes das aspas e adiciona uma barra invertida adicional, para escapar do qoute.
O segundo -replace dobra as barras invertidas no final do argumento, de modo que a aspa de fechamento não seja escapada.

Isso usa --% (PS v3 e superior), que é a única maneira confiável de passar cotações para executáveis ​​nativos AFAIK.


Editar:

Versão atualizada de Run-Native , agora chamada Invoke-NativeCommand (como sugerido )

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%
    }
}

(com alguma inspiração do iep )

  • não cita interruptores simples
  • ainda funciona com args vazios
  • funciona se nenhum argumento estiver presente
  • deve funcionar principalmente com msiexec, cmdkey, etc ...
  • ainda sempre funciona para programas, que seguem as regras comuns
  • não usa fora do padrão "" como escapou " - vai, portanto, ainda não trabalho para citações embutidas em .bat argumentos ou msiexec

Obrigado, não sabia sobre --% . existe alguma maneira de fazer isso sem vazar a variável de ambiente para o processo nativo? (e para quaisquer processos que ele possa invocar)

Existe um módulo do PowerShell que implementa um Run-Native Cmdlet para que todos possam usar? Isso soa como algo que deveria estar na Galeria Powershell. Se fosse bom o suficiente, poderia ser a base para um RFC.

"vazamento" soa como se você estivesse preocupado com a segurança. Observe, entretanto, que a linha de comando é visível para os processos filhos de qualquer maneira. (Por exemplo: gwmi win32_process |select name,handle,commandline|Format-Table no Windows e ps -f no Linux)

Se você ainda deseja evitar uma variável de ambiente, pode ser capaz de construir algo usando invoke-expression.

Em relação ao RFC:
Não acho que esse commandlet seja necessário, em vez disso, este deve ser o comportamento padrão:

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

Concordo que o comportamento padrão do PowerShell deve ser corrigido. Eu estava assumindo pessimisticamente que isso nunca mudaria por motivos de compatibilidade com versões anteriores, e é por isso que sugeri escrever um módulo. No entanto, eu realmente gosto da maneira como seu RFC permite que o antigo comportamento de escape seja reativado por meio de uma variável de preferência.

Deixe-me resumir a discussão, com a dose certa de opinião:

  • Está claro que temos um problema de compatibilidade com versões anteriores, portanto, o comportamento antigo deve continuar disponível.

  • A proposta RFC de @TSlivede leva isso em consideração, ao mesmo tempo que aponta _o caminho para o futuro_.
    Infelizmente, sua proposta definhava como _PR_ no momento em que este livro foi escrito, e ainda nem foi aceita como um _draft_ de RFC.


Por _o futuro_, quero dizer:

  • PowerShell é um shell em seu próprio direito que esperançosamente em breve se livrará de sua bagagem relacionada a cmd.exe , então as únicas considerações que importam quando se trata de chamar utilitários externos (executáveis ​​que são (normalmente) aplicativos de console / terminal) são :

    • Os argumentos a serem aprovados devem ser especificados pelas regras de análise do modo de argumento do _PowerShell_ _apenas_.

    • Qualquer _literals_ resultado desse processo deve ser passado _as-is_ para o executável de destino, como _individual_ argumentos.

    • Em outras palavras: como usuário, tudo o que você precisa se concentrar é em qual será o resultado da análise do _PowerShell_ e poder confiar que esse resultado será passado como está, com o PowerShell cuidando de qualquer - codificação de cenas - se necessário.


_Implementando_ o futuro:

  • No _Windows_:

    • Por razões _históricas_, o Windows _não_ permite passar argumentos _como uma matriz de literais_ para o executável de destino; em vez disso, é necessária uma string _única_ que codifica _todos_ os argumentos usando _pseudo-sintaxe de shell_. O que é pior, _finalmente depende do executável de destino individual interpretar essa única string_ e dividi-la em argumentos.

    • O melhor que o PowerShell pode fazer é formar essa única string - nos bastidores, após ter executado sua divisão _own_ da linha de comando em argumentos individuais - de uma _ maneira padronizada e previsível_.

    • A proposta RFC de @TSlivede propõe exatamente isso, sugerindo que o PowerShell sintetize a linha de comando do pseudo shell de uma maneira que fará com que o tempo de execução do Windows C / C ++ recupere os argumentos de entrada como estão ao realizar sua análise :

      • Dado que, em última análise, cabe a cada executável de destino interpretar a linha de comando, não há _garantia_ de que isso funcionará em todos os casos, mas essas regras são a escolha mais sensata, porque _a maioria_ dos utilitários existentes usam essas convenções.

      • As únicas exceções notáveis ​​são os _arquivos de lote_, que podem receber tratamento especial, como sugere a proposta RFC.

  • Em plataformas _Unix_:

    • Estritamente falando, os problemas que atormentam a análise de argumento do Windows _nunca surgem_, porque as chamadas nativas da plataforma para a criação de novos processos _aceita argumentos como matrizes de literais_ - quaisquer argumentos que o PowerShell acabe após realizar sua análise _own_ devem apenas ser transmitidos _as-is_ .
      Para citar @lzybkr : "Não vejo razão para trazer essa dificuldade para * nix."

    • Infelizmente, devido às limitações atuais do .NET Core (CoreFX), esses problemas _do_ entram em jogo, porque a API CoreFX força desnecessariamente a anarquia do argumento _Windows_ passando para o mundo Unix também, exigindo o uso de uma linha de comando pseudo mesmo em Unix.

    • Criei este problema CoreFX para pedir que esse problema seja corrigido.

    • Enquanto isso, dado que o CoreFX divide a pseudo linha de comando de volta em argumentos baseados nas regras C / C ++ citadas acima, a proposta de @TSlivede deve funcionar também em plataformas Unix.

Como https://github.com/PowerShell/PowerShell/issues/4358 foi fechado como uma duplicata deste, aqui está um breve resumo desse problema:

Se um argumento de um executável externo com uma barra invertida contiver um espaço, ele está ingenuamente entre aspas (adicione aspas antes e depois do argumento). Qualquer executável, que segue as regras usuais, interpreta assim:
De @ mklement0 's comentário :

O segundo " em ".\test 2\" , devido a ser precedido por \ é interpretado como um escape ", fazendo com que o resto da string - apesar de um fechamento ausente" seja interpretado como parte do mesmo argumento.

Exemplo:
(a partir @akervinen 's comentário )

PS X:\scratch> .\ps-args-test.exe '.\test 2\'
Argumento recebido: .\test 2"

O problema ocorre com muita freqüência, porque PSReadLine adiciona uma barra invertida final no preenchimento automático para diretórios.

Uma vez que corefx parece aberto para produzir a API de que precisamos, estou adiando isso para 6.1.0. Para 6.0.0, verei se podemos consertar # 4358

@TSlivede Peguei sua função, Invoke-NativeCommand (já que Run não é um verbo válido) e adicionei um alias ^ e publiquei-o como um módulo no PowerShellGallery:

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

@ SteveL-MSFT:

É bom ter um paliativo, mas um menos complicado seria - enquanto esperamos por uma solução CoreFX - implementar as regras oficiais de cotação / análise de argumentos bem definidas, conforme detalhado na proposta RFC do @TSlivede , preliminarmente - o que não não parece muito difícil de fazer.

Se apenas corrigirmos o problema \" , a passagem de argumento ainda estará fundamentalmente quebrada, mesmo em cenários simples como os seguintes:

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

Acho que neste ponto há acordo suficiente sobre qual deve ser o comportamento, então não precisamos de um processo RFC completo, não é?

A única decisão pendente é como lidar com problemas de compatibilidade com versões anteriores no _Windows_.

@ mklement0 @ SteveL-MSFT
quebramos a compatibilidade?

A única decisão pendente é como lidar com problemas de compatibilidade com versões anteriores no Windows.

Sim, mas essa é a parte difícil, certo?

@ be5invis o que você quer dizer com "compatibilidade já quebrou"?

Além disso, se o CoreFX estiver à beira de uma correção em sua camada, prefiro não criar um paliativo em nossa camada antes disso.

E como alguém disse acima no tópico, isso é irritante, mas também está muito bem documentado na comunidade. Não tenho certeza se devemos quebrá-lo duas vezes nos próximos dois lançamentos.

@joeyaiello :

A correção para # 4358 já não é uma mudança significativa para aqueles que contornaram o problema dobrando o \ final; por exemplo, "c:\tmp 1\\" ? Em outras palavras: se você limitar as alterações a esta correção, _duas_ alterações significativas são garantidas: esta agora e outra mais tarde, após mudar para a futura API CoreFx; e embora isso _podesse_ também acontecer se um tapa-buraco completo fosse implementado agora, é improvável, dado o que sabemos sobre essa mudança que se aproxima.

Por outro lado, pode dificultar a adoção no Unix se cenários comuns de cotação, como
bash -c 'echo "hi there"' não funcionam corretamente.

Sei que consertar isso é uma alteração muito maior, no entanto.

@ PowerShell / powershell-Committee discutiu isso e concordou que, no mínimo, o uso de --% deve ter o mesmo comportamento de bash em que as aspas são escapadas para que o comando nativo as receba. O que ainda está aberto para debate é se este deve ser o comportamento padrão sem usar --%

Nota:

  • Estou assumindo que uma chamada para um executável shell real é necessária ao usar --% no _Unix_, ao contrário de tentar _emular_ o comportamento do shell, que é o que acontece no _Windows_. Emular não é difícil no Windows, mas seria muito mais difícil no Unix, devido aos muitos outros recursos que precisariam ser emulados.

  • Usar um shell real então levanta a questão de qual shell usar: enquanto bash é onipresente, seu comportamento padrão não é compatível com POSIX nem é exigido pelo POSIX para estar presente, portanto, para portabilidade, outras linguagens de script chamam /bin/sh , o executável do shell decretado pelo POSIX (que _pode_ ser o Bash rodando em modo de compatibilidade (por exemplo, no macOS), mas certamente não precisa (por exemplo, Dash no Ubuntu)).

Indiscutivelmente, devemos ter como alvo /bin/sh também - o que, no entanto, significa que alguns recursos do Bash - principalmente a expansão da chave, certas variáveis ​​automáticas ... - não estarão disponíveis


Uso de --%

Usarei o comando echoargs --% 'echo "hi there"' como exemplo abaixo.

o mesmo comportamento do bash em que as aspas são escapadas para que o comando nativo as receba.

A maneira de fazer _no futuro, depois que a API CoreFX for estendida_ seria não executar nenhum escape e, em vez disso, fazer o seguinte:

  • Crie um processo da seguinte forma:

    • /bin/sh como o executável, (efetivamente) atribuído a ProcessStartInfo.FileName .

    • A seguinte matriz de tokens de argumento _literal_, _individual_ como ProcessStartInfo.ArgumentList :

    • -c como o primeiro argumento

    • echoargs 'echo "hi there"' como o segundo argumento - ou seja, a linha de comando original usada _literalmente_, exatamente como especificado, exceto que --% foi removido.

Na verdade, a linha de comando é passada _como está_ para o executável do shell, que pode então realizar sua análise _sua_.

Eu entendo que, na ausência atual de uma maneira baseada em array para passar argumentos literais, precisamos combinar -c e echoargs 'echo "hi there"' em uma string _única_ _com escape_, lamentavelmente _só para o benefício do CoreFX API_, que, quando chega a hora de criar o processo real, então _reversa_ esta etapa e divide a única string de volta em tokens literais - e garantir que essa reversão sempre resulte na lista original de tokens literais é a parte desafiadora.

Novamente: A única razão para envolver o escape aqui é devido à limitação atual do CoreFX.
Para trabalhar com essa limitação, a seguinte string de escape único deve ser atribuída à propriedade .Arguments de uma instância ProcessStartInfo , com o escape executado conforme especificado pela análise de argumentos de linha de comando C ++ :

  • /bin/sh como o executável, (efetivamente) atribuído a ProcessStartInfo.FileName .
  • A seguinte string de escape único como o valor de ProcessStartInfo.Arguments :
    -c "echoargs 'echo \"hi there\"'"

## Comportamento padrão

O que ainda está aberto para debate é se este deve ser o comportamento padrão sem usar -%

O comportamento padrão no Unix deve ser muito diferente:

  • Nenhuma consideração de escape _além do próprio PowerShell_ deve entrar em jogo (exceto no _Windows_, onde isso não pode ser evitado, infelizmente; mas lá as regras do MS C ++ são o caminho a percorrer, _a serem aplicadas nos bastidores_; caso contrário, --% fornece uma saída de emergência).

  • Quaisquer argumentos com os quais _PowerShell_ termine, após sua própria análise, devem ser passados ​​como uma _matriz de literais_ , por meio da próxima propriedade ProcessStartInfo.ArgumentList .

Aplicado ao exemplo sem --% : echoargs 'echo "hi there"' :

  • O PowerShell executa sua análise normal e termina com os 2 argumentos a seguir:

    • echoargs
    • echo "hi there" (aspas simples - que só tinham função sintática para _PowerShell_, removido)
  • ProcessStartInfo é então preenchido da seguinte forma, com a próxima extensão CoreFX em vigor:

    • echoargs como o (efetivo) .FileName valor da propriedade
    • _Literal_ echo "hi there" como o único elemento a ser adicionado à instância Collection<string> exposta por .ArgumentList .

Novamente, na ausência de .ArgumentList isso não é uma opção _ainda_, mas _interiormente_ o mesmo escape auxiliar compatível com MS C ++ como descrito acima pode ser empregado.

@ SteveL-MSFT
Como já mencionei em Faça o símbolo de parada de análise (-%) funcionar no Unix (# 3733), eu desaconselho fortemente alterar o comportamento de --% .

Se alguma funcionalidade especial para /bin/sh -c for necessária , use um símbolo diferente e deixe --% como está!

@TSlivede :

_Se_ algo --% -_like_ for implementado no Unix - e com globbing nativo e uma multidão geralmente mais experiente em linha de comando no Unix, percebo menos necessidade disso - então escolho um _símbolo diferente_ - como --$ - provavelmente faz sentido (desculpe, eu perdi o controle de todos os aspectos deste longo debate de várias questões).

Símbolos diferentes também serviriam como lembretes visualmente evidentes de que o comportamento _específico da plataforma_ não portátil está sendo invocado.

Isso deixa a dúvida o que o PowerShell deve fazer quando encontrar --% no Unix e --$ no Windows.

Estou bem deixando --% como está. Introduzir algo como --$ que chama / bin / sh e acho que cmd.exe no Windows pode ser uma boa maneira de resolver isso.

Sem chance de criar um cmdlet para esses comportamentos?

@iSazonov você está sugerindo algo como Invoke-Native ? Não tenho certeza se sou fã disso.

Sim, como Start-Native .
Brincadeira :-), você não gosta de cmdlets no PowerShell?

Em Build.psm1, temos Start-NativeExecution com link para https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/

@ SteveL-MSFT

Estou bem saindo -% como está.

Acho que todos concordamos que --% deve continuar a se comportar da maneira que faz no _Windows_.

Em _Unix_, por outro lado, esse comportamento não faz sentido, como tentei demonstrar aqui - em resumo:

  • aspas simples não estão sendo tratadas corretamente
  • a única maneira de fazer referência à variável de ambiente é _cmd.exe-style_ ( %var% )
  • recursos nativos importantes, como globbing e divisão de palavras, não funcionam.

A principal motivação para a introdução de --% foi, se bem entendi, permitir a _reutilização de cmd.exe linhas de comando existentes_ como estão.

  • Como tal, --% é inútil no Unix com seu comportamento atual.
  • _Se_ quiséssemos um recurso _análogo_ no Unix, ele teria que ser baseado em /bin/sh -c , conforme proposto acima, provavelmente usando um símbolo diferente.

Não acho que haja necessidade de um recurso baseado em cmd /c no Windows, já que --% tem isso _principalmente_ coberto, indiscutivelmente de uma maneira que é _bom o suficiente_.
@TSlivede apontou que nem todos os recursos do shell estão sendo emulados, mas na prática isso não parece ser uma preocupação (por exemplo, substituições de valor variável como %envVar:old=new% não são suportadas, ^ não é um caractere de escape, e o uso de --% é limitado a um comando _único_ - não há uso dos operadores de redirecionamento e controle de cmd.exe ; isso dito, não acho --% sempre foi feito para emular comandos inteiros _lines_).

Assim, algo como --$ - se implementado - seria a _contadora_ do Unix para --% .

Em qualquer caso, pelo menos o tópico de ajuda about_Parsing merece um aviso conspícuo de que --% será inútil no Unix , exceto em alguns casos.

@iSazonov Pode fazer sentido ter Start-Native para lidar com alguns cenários específicos, mas devemos tentar melhorar o PowerShell para que o uso de exes nativos seja mais natural e previsível

@ PowerShell / powershell-Committee revisou isso e concorda que --% deve significar tratar os argumentos como fariam em suas plataformas relevantes, o que significa que eles se comportam de maneira diferente no Windows e no Linux, mas consistentes no Windows e consistentes no Linux. Seria mais confuso para o usuário introduzir um novo sigilo. Deixaremos a implementação para o engenheiro sobre como habilitar isso.

Scripts de plataforma cruzada deveriam estar cientes dessa diferença de comportamento, mas parece improvável que os usuários acertem isso. Se o feedback do usuário for de que há uma necessidade devido ao uso de mais plataforma cruzada, então podemos revisitar a introdução de um novo sigilo.

Pela segunda vez, fui mordido por essas diferenças de análise de argumento de string de cmdlets e nativas quando usado ripgrep .

Aqui está o resultado das chamadas do PowerShell (echo.exe é de "C: \ Arquivos de programas \ Git \ usrbin \ echo.exe")

Agora eu sei que devo tomar cuidado com esta peculiaridade " :

> echo.exe '"test'
test

Mas essa peculiaridade está além de mim ...

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

No segundo caso eu preciso colocar \ duplo para passar \ para o comando nativo, no primeiro caso eu não preciso fazer isso 😑

Eu entendo que seria uma mudança significativa mudar esse comportamento, então não haverá diferença entre cmlets e comandos nativos. No entanto, acho que peculiaridades como essa são algo que impede as pessoas de usar o PowerShell como shell padrão.

@mpawelski Acabei de experimentar isso na minha caixa do Windows com git 2.20.1.vfs.1.1.102.gdb3f8ae e não reproduz para mim com 6.2-RC.1. Executei-o várias vezes e ele ecoa consistentemente ^\+.+;

@ SteveL-MSFT, acho que @mpawelski acidentalmente especificou o mesmo comando duas vezes. Você verá o problema se passar '^\"+.*;' por exemplo - observe a parte \" - com a expectativa - razoável - de que o conteúdo da string entre aspas simples será transmitido como está , de modo que o programa de destino externo veja ^\"+.*; como o valor do argumento:

# 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"' - '^\"+.*;'
^\"+.*; 

-% foi, se bem entendi, para permitir a reutilização de linhas de comando cmd.exe existentes como estão.

Não é bem isso. --% foi introduzido porque muitos utilitários de linha de comando do Windows usam args que são interpretados pelo PowerShell que bloqueiam completamente a invocação do utilitário. Isso pode irritar as pessoas rapidamente se elas não puderem mais usar facilmente seus utilitários nativos favoritos. Se eu tivesse um quarto para cada vez que respondi a perguntas no SO e em outros lugares RE comandos nativos exe que não funcionam direito por causa desse problema, provavelmente poderia levar a família para jantar no Qoba. :-) Por exemplo, tf.exe permite ; como parte da especificação de um espaço de trabalho. Git permite {} e @~1 , etc, etc, etc.

--% foi adicionado para dizer ao PowerShell para NÃO analisar o resto da linha de comando - apenas envie "como está" para o exe nativo. Com um esfregar, permita variáveis ​​usando a sintaxe cmd env var. É um pouco feio, mas cara, realmente vem a calhar ainda no Windows.

RE tornando isso um cmdlet, não tenho certeza se vejo como isso funciona. --% é um sinal para o analisador simplificar a análise até o fim do prazo.

Francamente, como usuário de longa data do PowerShell e desse recurso em particular, faz sentido para mim usar o mesmo operador em outras plataformas para simplesmente significar - simplificar a análise até EOL. Existe a questão de como permitir alguma forma de substituição de variável. Mesmo que pareça um pouco feio, no macOS / Linux você pode pegar% envvar% e substituir o valor de qualquer env correspondente. Então, ele pode ser portátil entre plataformas.

O fato é que, se você não fizer isso, acabará com um código condicional - não exatamente o que eu chamaria de portátil:

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

Eu prefiro este trabalho em todas as plataformas:

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

O comportamento no Windows deve permanecer como está devido à compatibilidade.

@rkeithhill

Não é bem isso.

Por processo de eliminação, as linhas de comando anteriores ao PowerShell no mundo do Windows foram escritas para cmd.exe - e é exatamente isso que --% pretende emular , conforme evidenciado pelo seguinte:

  • --% expande cmd.exe -style %...% referências de variáveis ​​de ambiente, como %USERNAME% (se nenhum shell e nenhuma lógica especial estivessem envolvidos, tais tokens seriam passados _verbatim_)

  • direto da boca do PowerShell (se você quiser; ênfase adicionada):

A web está cheia de linhas de comando escritas para o Cmd.exe . Essas linhas de comando funcionam com frequência suficiente no PowerShell, mas quando incluem certos caracteres, por exemplo, um ponto e vírgula (;) um cifrão ($) ou chaves, você precisa fazer algumas alterações, provavelmente adicionando algumas aspas. Essa parecia ser a origem de muitas dores de cabeça menores.

Para ajudar a resolver esse cenário, adicionamos uma nova maneira de “escapar” da análise de linhas de comando. Se você usar um parâmetro mágico -%, paramos nossa análise normal de sua linha de comando e mudamos para algo muito mais simples. Não combinamos aspas. Não paramos no ponto e vírgula. Não expandimos as variáveis ​​do PowerShell. Expandimos as variáveis ​​de ambiente se você usar a sintaxe do Cmd.exe (por exemplo,% TEMP%). Fora isso, os argumentos até o final da linha (ou pipe, se você estiver encanando) são passados ​​como estão. Aqui está um exemplo:

Observe que essa abordagem é inadequada para o mundo _Unix_ (para recapitular acima):

  • Misturar argumentos não citados como *.txt não funcionará.

  • Os shells tradicionais (semelhantes ao POSIX) no mundo Unix _não_ usam %...% para se referir a variáveis ​​de ambiente; eles esperam a sintaxe $... .

  • Não há (felizmente) nenhuma linha de comando _raw_ no mundo Unix: qualquer executável externo deve receber uma _array_ de _literals_, portanto, ainda é o PowerShell ou CoreFx que deve analisar a linha de comando em argumentos primeiro.

  • Os shells tradicionais (semelhantes ao POSIX) no mundo Unix aceitam strings '...' (aspas simples), que --% não reconhece - veja # 10831.

Mas mesmo no mundo Windows --% tem limitações graves e não óbvias :

  • Obviamente, você não pode fazer referência direta às variáveis ​​do PowerShell em sua linha de comando se usar --% ; a única solução - complicada - é definir temporariamente as variáveis ​​do _ambiente_, que você deve referenciar com %...% .
  • Você não pode incluir a linha de comando em (...) - porque o fechamento ) é interpretado como uma parte literal da linha de comando.
  • Você não pode seguir a linha de comando com ; e outra instrução - porque ; é interpretado como uma parte literal da linha de comando.
  • Você não pode usar --% dentro de um bloco de script de uma única linha - porque o fechamento } é interpretado como uma parte literal da linha de comando.
  • Você não pode usar redirecionamentos - porque eles são tratados como uma parte literal da linha de comando - no entanto, você pode usar cmd --% /c ... > file , para deixar cmd.exe lidar com o redirecionamento.
  • Você não pode usar caracteres de continuação de linha - nem do PowerShell ( ` ) nem do cmd.exe ( ^ ) - eles serão tratados como _literals_.

    • --% apenas analisa (no máximo) até o final da linha.


Felizmente, nós já _do_ temos uma sintaxe de plataforma cruzada: _PowerShell's own syntax_.

Sim, usar isso requer que você saiba o que _PowerShell_ considera metacaracteres, que é um _superset_ do que cmd.exe e conchas semelhantes a POSIX, como Bash, consideram metacaracteres, mas esse é o preço a pagar por uma plataforma mais rica. experiência agnóstica de linha de comando.

Que pena, então, que o PowerShell lida tão mal com a citação de " caracteres - que é o próprio assunto deste problema e que está resumido neste número de documentos .

@rkeithhill , comecei um pouco pela tangente; deixe-me tentar fechá-lo:

  • --% na verdade já _is_ implementado a partir do PowerShell Core 6.2.0; por exemplo,
    /bin/echo --% %HOME% imprime o valor da variável de ambiente HOME ; por contraste,
    /bin/ls --% *.txt não funcionará conforme o esperado, porque *.txt é passado como um literal.

  • Em última análise, quando não estivermos usando --% , precisamos ajudar os usuários a diagnosticar como é a linha de comando / array de argumentos que é construído _por trás dos bastidores_ (o que nos leva de volta ao venerável # 1761):

    • Seu útil echoArgs.exe faz exatamente isso e, na questão vinculada, você sensatamente pediu que essa funcionalidade fizesse parte do próprio PowerShell.
    • @ SteveL-MSFT ponderou incluindo a linha de comando resultante no registro de erro.

Finalmente, para aplicar meu argumento anterior - usando a sintaxe do próprio PowerShell como a inerentemente portátil - ao seu exemplo:

# 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

Sim, requer que você saiba que um token inicial @ é um metacaractere que você deve citar, mas à medida que o PowerShell se torna mais amplamente usado, o reconhecimento de tais requisitos também deve se tornar mais difundido.

Para sua informação, esse problema deve ser quase o mesmo no Windows e no Unix-like. A implementação do CoreFx do Unix SDProcess tem uma coisa chamada ParseArgumentsIntoList , que implementa CommandLineToArgvW quase exatamente sem _setargv switches (e com o não documentado "" entre aspas → " recurso). O Unix não deve ser um ponto adicional de dor nisso porque na forma atual ele está quebrado da mesma forma que o Windows.

_setargv não é algo que todo programa usa, afinal, e provavelmente não vale a pena considerá-lo porque, bem, é meio que afetado por mudanças comportamentais entre as versões CRT . O melhor que podemos e devemos fazer é cercar tudo com aspas duplas, adicionar algumas backslahes agradáveis ​​e isso é tudo.

Outro exemplo em que os args não estão sendo analisados ​​corretamente:

az "myargs&b"

Neste caso, az obtém myargs e b tenta ser executado como um novo comando.

A solução alternativa é: az -% "myargs & b"

@ SteveL-MSFT, podemos remover o rótulo Waiting - DotNetCore , dado que o recurso necessário - a propriedade ProcessStartInfo.ArgumentList baseada em coleção está disponível desde .NET Core 2.1

@TSlivede está ciente do novo método e planeja usá-lo, mas o RFC associado, https://github.com/PowerShell/PowerShell-RFC/pull/90 , está definhando, infelizmente.

Eu sugiro que continuemos a discussão de _implementação_ lá.

No entanto, na discussão RFC, @joeyaiello fala sobre fazer as alterações em um recurso experimental, mas está se tornando cada vez mais claro para mim que você não pode corrigir o comportamento das citações sem quebrar maciçamente o código existente:

Qualquer pessoa que teve que _saber_:

  • a incapacidade de passar um argumento de string vazia ( foo.exe "" atualmente passa _nenhum_ argumentos)
  • a remoção efetiva inesperada de aspas duplas devido à falta de escape automático de aspas duplas incorporadas ( foo.exe '{ "foo": "bar" }' sendo passado como escape incorreto "{ "foo": "bar" }" )
  • as peculiaridades de certos CLIs, como msiexec que não aceitam certos argumentos entre aspas _como um todo_ ( foo.exe foo="bar none" sendo passado como "foo=bar none" ).
    Nota: msiexec é o culpado aqui e, com as alterações propostas aplicadas, passar a foo="bar none" forma de cotação exigida exigirá --% .

terá problemas, porque as soluções alternativas irão _interromper_ com as alterações propostas aplicadas.

Portanto, uma pergunta adicional é:

  • Como podemos disponibilizar o comportamento correto pelo menos como um recurso _opt-in_?

  • Um problema fundamental com tais mecanismos - normalmente, por variável de preferência, mas cada vez mais também por using instruções - é o escopo dinâmico do PowerShell; ou seja, o comportamento optado será, por padrão, aplicado ao código denominado _de_ o código optado de alguém também, o que é problemático.

    • Talvez seja a hora de introduzir geralmente o escopo _lexical_ de recursos, uma generalização da proposta using strict escopo léxico no - igualmente enfraquecido -

    • Algo como using preference ProperArgumentQuoting escopo léxico? (Nome obviamente negociável, mas estou lutando para encontrar um).

Considerando todas essas advertências, e que este é um problema com a invocação direta, onde não podemos simplesmente adicionar um novo parâmetro, sou firmemente a favor de quebrar o comportamento antigo.

Sim, provavelmente vai quebrar muito. Mas, realmente, só porque estava totalmente quebrado para começar. Não acho que seja particularmente viável priorizar a manutenção do que equivale a uma enorme pilha de soluções alternativas para o comportamento interrompido em vez de ter um recurso que _realmente funciona_.

Como uma nova versão principal, acho que a v7 realmente é a única chance que teremos de retificar essa situação adequadamente por algum tempo, e devemos aproveitar a oportunidade. Desde que alertemos os usuários, não acho que a transição será mal recebida em geral.

Se sentimos que mudanças bruscas são inevitáveis, então talvez devêssemos desenhar a solução ideal, levando em consideração que deve ser fácil imprimir em uma sessão interativa e é possível ter uma versão de script que funcione melhor em todas as plataformas.

Concordo, @ vexx32 : Manter o comportamento existente, mesmo que apenas por padrão, continuará sendo um ponto de dor perene. Os usuários não esperam a necessidade de opt-in para estar com e, uma vez que o façam, provavelmente se esquecerão ocasionalmente e / ou se ressentirão da necessidade de aplicá-lo todas as vezes.

Um shell que não passa argumentos de maneira confiável para programas externos está falhando em um de seus mandatos principais.

Você certamente tem _meu_ voto para fazer a alteração significativa, mas temo que outros pensem de forma diferente, até porque a v7 está sendo anunciada por permitir que usuários de longo prazo do WinPS migrem para o PSCore.


@iSazonov : https://github.com/PowerShell/PowerShell-RFC/pull/90 descreve a solução correta.

Para recapitular seu espírito:

O PowerShell, como um shell, precisa analisar argumentos de acordo com as regras _its_ e, em seguida, passar os valores de argumento expandidos resultantes _verbatim_ para o programa de destino - os usuários nunca devem ter que pensar sobre como o PowerShell faz isso acontecer; tudo o que eles deveriam se preocupar é em obter a sintaxe correta do _PowerShell_.

  • Em plataformas do tipo Unix, ProcessStartInfo.ArgumentList agora nos dá uma maneira de implementar isso perfeitamente, dado que a _array_ de valores de argumento expandidos pode ser passada _como está_ para o programa de destino, porque é assim que a passagem de argumento - sensatamente - funciona neste mundo.

  • No Windows, temos que lidar com a infeliz realidade da anarquia que é a análise de linha de comando do Windows , mas, como um shell, cabe a nós _apenas fazer funcionar nos bastidores, tanto quanto possível_, que é o que o RFC descreve - embora eu tenha acabado de descobrir uma ruga que faz uso exclusivo de ProcessStartInfo.ArgumentList não bom o suficiente, infelizmente (devido à disseminação de _arquivos em lote_ como pontos de entrada CLI, como demonstrado por az[.cmd] @ SteveL-MSFT --% .

Talvez o PSSA possa ajudar a mitigar a alteração significativa, avisando aos usuários que eles usam um formato de argumento que será alterado.

Acho que precisamos considerar a adoção de algo como Recursos opcionais para seguir em frente com essa alteração significativa junto com algumas outras .

Existe realmente algum valor em manter o comportamento existente? Além da compatibilidade com versões anteriores, quero dizer.

Não acho que vale a pena manter dois caminhos de código para isso, simplesmente para manter uma implementação quebrada porque algum código antigo pode precisar dela de vez em quando. Eu não acho que seja irracional esperar que o folx atualize seu código de vez em quando. 😅

Duplamente se esperamos que o PS7 seja um substituto do WinPS; a única razão que vejo para manter o comportamento da v7 é se esperamos que as pessoas usem o mesmo script para executar comandos no 5.1 e no 7, o que (espero) deve ser um caso muito raro se o PS7 for um bom substituto para o 5.1.

E mesmo assim, não seria muito difícil para os usuários contabilizar ambos. Desde que não estejamos mudando a sintaxe real da linguagem, deve ser muito fácil fazer algo assim:

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

Desde que conscientizemos os usuários sobre a diferença, acho que seria um alívio bem-vindo da dor que tem sido lidar com executáveis ​​nativos estranhos no PS até agora. 😄

Como @TylerLeonhardt mencionou na discussão de recursos opcionais - implementar isso significa que agora você mantém _várias_ implementações distintas, cada uma delas precisa ser mantida e testada, além de manter e testar a estrutura de recursos opcionais. Realmente não parece valer a pena por isso, tbh.

A compatibilidade com versões anteriores do https://github.com/PowerShell/PowerShell/issues/1761 e https://github.com/PowerShell/PowerShell/issues/10675. "Consertar" a interoperabilidade com comandos nativos é algo que eu gostaria de resolver para o vNext. Portanto, se alguém vir qualquer problema existente ou novo nesta categoria, envie uma cópia para mim e eu o marcarei apropriadamente (ou se você tiver permissão de triagem, marque-o como os outros).

Recursos opcionais são módulos :-) Ter recursos opcionais no Engine é uma grande dor de cabeça para suporte, especialmente para suporte ao Windows. Poderíamos modularizar o Engine reduzindo as dependências internas e substituindo APIs internas por públicas - depois disso, poderíamos implementar recursos opcionais no Engine de maneira fácil.

@iSazonov, uma das coisas que minha equipe analisará no vNext é tornar o mecanismo mais modular :)

Qual é a solução recomendada aqui para usuários finais?

Isso é inventado, mas é a maneira mais direta de obter o tratamento correto * ArgumentList da própria estrutura .NET:

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'

Claro, você pode torná-lo menos planejado para usar aqui usando parâmetros posicionais e alguns truques get-command .

* ISENÇÃO DE RESPONSABILIDADE: Não existe uma única maneira correta de analisar um cmdline no Windows. Por "correto", quero dizer o estilo MSVCRT de conversão de cmdline de / para argv, implementado pelo .NET em todas as plataformas para manipulação de ArgumentList, processamento de main (string[] args) e chamadas de spawn externas no Unix. Este exemplo é fornecido COMO ESTÁ, sem garantia de interoperabilidade geral. Consulte também a seção "linha de comando do Windows" da documentação proposta do NodeJS child_process .

@ Artoria2e5 Essa é exatamente a conclusão a que cheguei. System.Diagnostics.Process é a única maneira confiável de executar executáveis ​​externos, mas os argumentos de escape podem ser complicados devido às regras stdlib:

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

Como resultado, eu vim com a seguinte lógica para argumentos de escape envolvê-los entre aspas " para a execução do processo:
https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L244

Também pode ser complicado obter STDOUT e STDERR em tempo real enquanto um executável externo é executado, então criei este pacote

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

que estou usando muito no Windows, Linux e Mac e até agora sem problemas e posso passar argumentos com nova linha e outros caracteres especiais neles.

GitHub
Contribua com o desenvolvimento de choovick / ps-invoke-externalcommand criando uma conta no GitHub.
GitHub
Contribua com o desenvolvimento de choovick / ps-invoke-externalcommand criando uma conta no GitHub.

@choovick, todo o redirecionamento do stdio é ótimo!

Eu discordo um pouco sobre a parte de escape, pois já existe uma coisa que faz isso para você chamada ArgumentList. Eu entendo que é uma adição relativamente recente (?), E fica decepcionante, pois a MS se esqueceu de colocar um inicializador String, String [] para SDProcessStartInfo. (Há um lugar para essas… propostas de interface .NET?)


bate papo

Minha função de escape desse exemplo NodeJS é um pouco diferente da sua: ela usa o escape não documentado (mas encontrado no .NET core e no MSVCRT) "" para aspas. Fazer isso simplifica o trabalho de separação por barra invertida. Fiz isso principalmente porque ele era usado para todo o poderoso cmd, que não entende que \" não deve retirar aspas do resto da string. Em vez de lutar com \^" , descobri que ficarei melhor com algo que está em uso secreto desde o início dos tempos.

@ Artoria2e5, infelizmente, ArgumentList não está disponível no PowerShell 5.1 no Windows. Usando seu exemplo, estou obtendo:

`` `Você não pode chamar um método em uma expressão de valor nulo.
Em C: Users \ yser \ dev \ test.ps1: 7 char: 37

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

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

    • FullyQualifiedErrorId: InvokeMethodOnNull

      `` `

Uma vez que a lógica de escape do argumento personalizado ...

bate papo

em relação a `\ ^" `em NodeJS, acho que tive que fazer isso há vários anos :) e acho que funcionou

System.Diagnostics.Process é a única maneira confiável de executar executáveis ​​externos

O problema é que você não obterá integração com os fluxos de saída do PowerShell e não obterá comportamento de fluxo no pipeline.

Este é o resumo das soluções alternativas necessárias se você ainda quiser permitir que o PowerShell execute a invocação (o que é definitivamente preferível) :

  • Se você precisar passar argumentos com _embedded_ " chars., _Dobre-os_ no Windows, se possível, ou \ -escape-os:

    • No Windows, ao chamar _arquivos de lote_ e se você souber que o programa de destino entende "" como um " escape, use $arg -replace '"', '""'

      • O uso de "" é preferível no Windows (evita o problema do Windows PowerShell e funciona com CLIs que usam arquivos em lote como _stubs_, como Node.js e Azure), mas nem todos os executáveis ​​o suportam (notavelmente não Ruby e Perl).
    • Caso contrário (sempre no Unix), use $arg -replace '"', '\"'

    • Nota: No _Windows PowerShell_, isso ainda nem sempre funciona corretamente se o valor também contém _spaces_, porque a presença de \" literal no valor _não_ aciona aspas duplas, ao contrário do PowerShell Core; por exemplo, passando '3\" of snow' quebras.

    • Além disso, antes do escape acima, você deve duplicar a instância \ imediatamente anterior a " , se eles forem tratados como literais:

      • $arg = $arg -replace '(\\+)"', '$1$1"'
  • Se você precisar passar um argumento _empty_, passe '""' .

    • '' -eq $arg ? '""' : $arg (alternativa WinPS: ($arg, '""')['' -eq $arg]
  • Somente Windows PowerShell, não faça isso no PS Core (onde o problema foi corrigido):

    • Se o seu argumento _contém espaços_ e _ termina em_ (um ou mais) \ , duplique as \ instância (s) finais.

      • if ($arg -match ' .*?(\\+)$') { $arg = $arg + $Matches[1] }
  • Se cmd / um arquivo em lote está sendo invocado com argumentos que _não_ têm espaços (portanto, _não_ acionando aspas duplas automáticas pelo PowerShell), mas contêm &|<>^,; (por exemplo, a&b ), use _embedded delimitando aspas_ para garantir que o PowerShell passe um token entre aspas duplas e, portanto, não interrompa a chamada cmd / batch-file:

    • $arg = '"' + $arg + '"'
  • Se você precisar lidar com executáveis ​​mal-comportados, como msiexec.exe , coloque aspas simples no argumento:

    • 'foo="bar none"'

Conforme declarado, essas soluções alternativas _interromperão_, uma vez que o problema subjacente seja corrigido.


Abaixo está a função simples (não avançada) iep (para "invocar programa externo"), que:

  • Executa todos os escapes descritos acima, incluindo caixa especial automática para msiexec e preferindo "" escapando em vez de \" dependendo do programa alvo.

    • A ideia é que você pode transmitir qualquer argumento focalizando apenas a sintaxe de string do _PowerShell_ e contar com a função para executar o escape necessário para que o valor literal que o PowerShell vê também seja visto pelo programa de destino.

    • No PowerShell _Core_, isso deve funcionar de maneira bastante robusta; no Windows PowerShell, você ainda tem casos extremos com aspas duplas incorporadas que quebram se o escape \" deve ser usado (conforme discutido acima).

  • Preserva a sintaxe de invocação do comando shell.

    • Simplesmente acrescente iep  à sua linha de comando.

  • Como a invocação direta faria:

    • integra-se com streams do PowerShell

    • envia a saída linha por linha através do pipeline

    • define $LASTEXITCODE base no código de saída do programa externo; entretanto, $? _não_ pode ser invocado.

Nota: A função é propositalmente minimalista (sem declarações de parâmetro, sem ajuda de linha de comando, nome curto (irregular)), porque seu objetivo é ser o mais discreto possível: simplesmente acrescente iep à sua linha de comando e coisas assim Deveria trabalhar.

Exemplo de invocação usando EchoArgs.exe (instalável via Chocolatey a partir de uma sessão _elevada_ com choco install echoargs -y ):

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"

O exemplo acima mostra a saída do PowerShell Core. Observe como todos os argumentos foram transmitidos corretamente conforme vistos literalmente pelo PowerShell, incluindo o argumento vazio.

No Windows PowerShell, o argumento 3" of snow não será transmitido corretamente, porque o escape \" é usado devido à chamada de um executável desconhecido (conforme discutido acima).

Para verificar se os arquivos em lote passam os argumentos corretamente, você pode criar echoargs.cmd como um wrapper para echoargs.exe :

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

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

Como um arquivo em lote agora é chamado, "" -escaping é empregado, o que corrige o problema 3" of snow ao chamar do Windows PowerShell.

A função também funciona em plataformas do tipo Unix, que você pode verificar criando um script de shell sh denominado echoargs :

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

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


Importante : uma versão mais completa desta função foi publicada como ie ( I nvoke (externo) E xecutable) em Acabei de publicar um módulo Native , que encorajo você a usar em vez de. Instale o módulo com
Install-Module Native -Scope CurrentUser .
O módulo também contém um comando ins ( Invoke-NativeShell ) que aborda o caso de uso discutido em # 13068 - consulte https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -671572939 para detalhes.

Código-fonte da função iep (use o módulo Native - veja acima):

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 Impressionante, mas aqui estão alguns que não funcionam para mim no 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

aqui está meu conjunto de argumentos de teste :)

$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 tem limitações em quando usado, é por isso que eu tive que escrever meu próprio runner com habilidades para capturar STDOUT STDERR e sua combinação em variáveis, o que é suficiente para meus casos de uso, mas não perfeito.

Edit: como você disse, parece que tem casos extremos no Windows Poweshell 5. Testei no Powershell 6 e funciona muito bem! infelizmente eu tenho que lidar com o Poweshell 5 até que o 7 assuma no caso do Windows ...

Impressionante, mas aqui estão alguns que não funcionam para mim no Windows:

Sim, essas limitações se aplicam a _Windows PowerShell_, com executáveis ​​para os quais o suporte para "" escape de " incorporado não pode ser assumido, conforme detalhado em meu comentário anterior.
Se você estiver disposto a assumir o suporte para "" escapando em todas as suas invocações (_most_, mas nem todos os executáveis ​​no Windows suportam), você pode facilmente ajustar a função.

E, para confirmar o que você disse em sua edição: No PowerShell _Core_:

  • iep echoargs 'somekey="value with spaces"' 'te\" st' funciona bem.
  • Seu conjunto de argumentos de teste também parece funcionar bem.

Se você está disposto a assumir o suporte para "" escapando em todas as suas invocações (_most_, mas nem todos os executáveis ​​no Windows suportam), você pode facilmente ajustar a função.

Obrigado, vou tentar em um futuro próximo. Ocasionalmente, lido com sqlcmd e ele não suporta "" com certeza. Para esse caso - é fácil fornecer uma opção para pular a lógica de escape para argumentos específicos.

A maneira como o Windows analisa os argumentos da linha de comando pode ser encontrada em Analisando argumentos da linha de comando C ++ :

O código de inicialização do Microsoft C / C ++ usa as seguintes regras ao interpretar os argumentos fornecidos na linha de comando do sistema operacional:

  • Os argumentos são delimitados por um espaço em branco, que é um espaço ou uma tabulação.

  • O acento circunflexo (^) não é reconhecido como um caractere de escape ou delimitador. O caractere é manipulado completamente pelo analisador de linha de comando no sistema operacional antes de ser passado para o array argv no programa.

  • Uma string entre aspas duplas (" string ") é interpretada como um único argumento, independentemente do espaço em branco contido nela. Uma string entre aspas pode ser incorporada em um argumento.

  • Uma aspa dupla precedida por uma barra invertida (\ ") é interpretada como um caractere literal de aspa dupla (").

  • As barras invertidas são interpretadas literalmente, a menos que precedam imediatamente as aspas duplas.

  • Se um número par de barras invertidas for seguido por aspas duplas, uma barra invertida será colocada no array argv para cada par de barras invertidas e as aspas duplas serão interpretadas como um delimitador de string.

  • Se um número ímpar de barras invertidas for seguido por aspas duplas, uma barra invertida é colocada na matriz argv para cada par de barras invertidas, e as aspas duplas são "escapadas" pela barra invertida restante, causando um literal aspas duplas (") a serem colocadas em argv .

Isso também é discutido em Funções _exec, _wexec :

Os espaços incorporados nas strings podem causar um comportamento inesperado; por exemplo, passando _exec a string "hi there" resultará no novo processo obtendo dois argumentos, "hi" e "there" . Se a intenção era fazer com que o novo processo abrisse um arquivo denominado "hi there", o processo falharia. Você pode evitar isso citando a string: "\"hi there\"" .

Como Python chama executáveis ​​nativos

O subprocess.Popen do Python pode lidar com o escape corretamente convertendo args em uma string da maneira descrita em Convertendo uma sequência de argumentos em uma string no Windows . A implementação é subprocess.list2cmdline .

Espero que isso possa lançar alguma luz sobre como o PowerShell pode lidar com isso de uma maneira mais elegante, em vez de usar --% ou escape duplo (seguindo a sintaxe do PowerShell e do CMD). As soluções alternativas atuais realmente afetam os clientes da CLI do Azure (que se baseia em python.exe).

Ainda não temos um método claro e conciso para entender como lidar com esse problema. Alguém da equipe Powershell pode esclarecer se isso é simplificado com a v7?

Bem, a boa notícia é que um dos focos do PS 7.1 é facilitar a invocação de comandos nativos. De uma postagem no blog sobre investimentos 7.1 :

A maioria dos comandos nativos funcionam bem no PowerShell, no entanto, há alguns casos em que a análise de argumento não é ideal (como lidar com aspas corretamente). A intenção é permitir que os usuários recortem linhas de comando de amostra para qualquer ferramenta nativa popular, cole-as no PowerShell e funcione sem a necessidade de escape específico do PowerShell.

Então, talvez (espero) isso seja abordado em 7.1

Obrigado, @rkeithhill , mas não:

A intenção é permitir que os usuários recortem linhas de comando de amostra para qualquer ferramenta nativa popular, cole-as no PowerShell e funcione sem a necessidade de escape específico do PowerShell.

soa como outra abordagem do inerentemente problemático --% (símbolo de parada de análise)?

@ SteveL-MSFT, você pode nos contar mais sobre esse próximo recurso?


Para recapitular, a correção proposta aqui é que tudo que você precisa se concentrar é em satisfazer os requisitos de sintaxe do _PowerShell_ e que o PowerShell cuida de todos os escapes _por trás dos bastidores_ (no Windows; no Unix isso nem é mais necessário, agora que o .NET permite para passarmos uma série de tokens textuais ao programa de destino) - mas não há dúvida de que a correção teria que ser opcional, se a compatibilidade com versões anteriores fosse mantida.

Com essa correção, _algumas_ alterações nas linhas de comando para outros shells ainda será necessário, mas será mais simples - e, em comparação com --% , você retém todo o poder da sintaxe do PowerShell e expansões variáveis ​​(expressões com (...) , redirecionamentos, ...):

  • Você precisa substituir \" por `" , mas _apenas dentro de "..." _.

  • Você precisa estar ciente de que _nenhuma (outra) shell_ está envolvida, de modo que referências a variáveis ​​de ambiente no estilo Bash, como $USER _não_ funcionarão (elas serão interpretadas como variáveis ​​_PowerShell_), a menos que você as substitua pelas sintaxe equivalente do PowerShell, $env:USER .

    • Como um aparte: --% tenta compensar isso - notavelmente _invariably_ - expandindo cmd.exe -style referências de variáveis ​​de ambiente como %USERNAME% , mas note que isso não apenas t suporte referências de estilo Bash ( $USER ) é passado _verbatim_ para o programa de destino, mas também expande inesperadamente referências de estilo cmd.exe em plataformas do tipo Unix e não reconhece '...' -quoting.
    • Veja abaixo uma alternativa que _não envolve_ o respectivo shell nativo da plataforma.
  • Você precisa estar ciente de que o PowerShell tem metacaracteres _adicionais_ que requerem aspas / escape para uso literal; estes são (observe que @ só é problemático como _primeiro_ caractere do argumento.):

    • para shells semelhantes a POSIX (por exemplo, Bash): @ { } ` (e $ , se você quiser evitar a expansão inicial pelo PowerShell)
    • por cmd.exe : ( ) @ { } # `
    • Individualmente ` escapando de tais caracteres. é suficiente (por exemplo, printf %s `@list.txt ).

Um exemplo um tanto artificial:

Use a seguinte linha de comando Bash:

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

Com a correção proposta implementada, tudo o que é necessário é substituir as instâncias \" dentro do argumento "..." encerrado por `" :

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

Ou seja, você não precisa se preocupar com " incorporados dentro de '...' , e dentro de "..." você só precisa escapar deles para deixar o _PowerShell_ feliz (o que você também pode fazer com "" aqui).

A função iep implementa essa correção (como um paliativo), de forma que iep printf '"%s"\n' "3`" of snow" funcione conforme o planejado.


Compare isso com o comportamento atual interrompido, onde você precisa pular através dos seguintes obstáculos para fazer o comando funcionar como no Bash (inexplicavelmente, precisa de uma rodada _adicional_ de escape com \ ):

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

Com a correção em vigor, aqueles que desejam usar uma determinada linha de comando _as-is_ por meio do _concha padrão_ da plataforma, serão capazes de usar um _here-string_ literalmente para passar para sh -c (ou bash -c ) / cmd /c ; por exemplo:

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

Observe que o uso de --% _não_ funciona aqui ( printf --% '"%s"\n' "3\" of snow" ), e a vantagem adicional da abordagem baseada em strings aqui é que as várias limitações de --% não se aplicam , notavelmente a incapacidade de usar um redirecionamento de saída ( > ).

Se você mudar para uma _dobro_ aqui-string entre aspas ( @"<newline>....<newline>"@ ), você pode até incorporar _variáveis ​​e expressões do PowerShell_, ao contrário de --% ; entretanto, você precisa se certificar de que os valores expandidos não quebrem a sintaxe do shell de destino.

Podemos pensar em um cmdlet dedicado com um alias sucinto (por exemplo, Invoke-NativeShell / ins ) para essas chamadas (de modo que sh -c / cmd /c não precisa ser especificado), mas para passar linhas de comando complexas como estão, não acho uma maneira de contornar o uso de strings here:

# 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

Claro, se você está contando com recursos do shell nativo da plataforma, tais chamadas serão, por definição, específicas da plataforma [-family] - elas não funcionarão em _both_ Windows e plataformas do tipo Unix.

Esta é a razão pela qual confiar no PowerShell _alone_, com sua _própria sintaxe_ é preferível a longo prazo : ele fornece uma experiência de plataforma cruzada previsível para chamar programas externos - mesmo que isso signifique que você não pode usar linhas de comando criadas para outros shells é; À medida que o PowerShell ganha em popularidade, espero que a dor de descobrir e saber as modificações necessárias diminua e espero que mais e mais documentação mostre as versões do PowerShell de linhas de comando (também).

  • Você precisa substituir \" por `" , mas _apenas dentro de "..." _.

Isso não funciona em todos os lugares.

# 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"")'

Sintaxe Bash:

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

Sugestão Powershell:

Se a equipe do PowerShell está apenas procurando por um símbolo, que não quebra a sintaxe anterior, por que não usar algo como crases `para um comportamento semelhante ao Bash, que escapa de literais automaticamente e permite a interpolação de string. Isso também é semelhante à sintaxe do JavaScript.

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

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

por que não usar algo como backticks `para um comportamento do tipo Bash

Backticks já são usados ​​para escapar de coisas, o mesmo propósito que backslahes no shell POSIX. O comentário a que você está se referindo já aponta isso. Nós usamos todo o material de citação ASCII. Adicionar um prefixo $ a literais de string normais pode funcionar, mas não acho que faça sentido o suficiente.


A maneira como o Windows analisa os argumentos da linha de comando pode ser encontrada em ...

O problema é que o Windows MSVCR não faz apenas isso: ele lida com casos extremos de maneiras não documentadas . O material "" está tão solidamente configurado que eles até o colocaram no CoreFX quando portaram o .NET para o Unix. Mas de qualquer forma, é sempre bom o suficiente para escapar, pelo menos até que alguém peça para ser contornado.

Também existe o problema clássico de todo mundo fazer isso de maneira diferente, mas não precisamos nos preocupar com isso porque sempre temos o .NET para cmdline bruto.

por que não usar algo como backticks `para um comportamento do tipo Bash

Backticks já são usados ​​para escapar de coisas, o mesmo propósito que backslahes no shell POSIX. Nós usamos todo o material de citação ASCII.

Existe a possibilidade de usar uma combinação de símbolos se o analisador não for capaz de detectar que aqui `está introduzindo uma string. Algo como '' pode até funcionar.

@aminya aqui está uma solução se você não está usando o antigo Windows Powershell 5.1 e em 6+
https://github.com/PowerShell/PowerShell/issues/1995#issuecomment -562334606

Como tenho que lidar com PowerShell 5,1 no windows e 6+ no linux / mac, tenho minha própria implementação que está funcionando sem problemas há anos que permite trabalhar com ferramentas como kubectl, helm, terraform e outras passando por JSON complexo objetos dentro de parâmetros:
https://github.com/choovick/ps-invoke-externalcommand

GitHub
Contribua com o desenvolvimento de choovick / ps-invoke-externalcommand criando uma conta no GitHub.

Uma implementação um pouco mais curta de acima neste tópico , ainda estou para encontrar um caso em que isso falharia para mim. Isso funciona no PS a partir da v3.

A função Run-Native @AndrewSav @TSlivede é muito inteligente e concisa e, admiravelmente, também funciona de maneira confiável no _Windows PowerShell_; algumas coisas dignas de nota: por necessidade, o processo filho vê o aux. commandlineargumentstring variável de ambiente (provavelmente raramente, ou nunca, um problema na prática), invocações sem argumento atualmente não são tratadas corretamente (facilmente corrigidas e nem mesmo um problema, se você se certificar de que usa apenas a função _com_ argumentos, que é para que serve), _todos_ os argumentos são colocados entre aspas (por exemplo, algo como 42 é passado como "42" ), que no Windows pode (infelizmente) ter efeitos colaterais para programas que interpretam argumentos entre aspas duplas (ou parcialmente aspas, como no caso msiexec ) de maneira diferente.

@aminya , a única razão pela qual node -e 'console.log(`"hey`")' (mais ou menos) funciona é por causa do comportamento atual interrompido (veja abaixo). Suponho que o que você pretendia passar foi _verbatim_ console.log("hey") , que, se o PowerShell no Windows escapasse das coisas corretamente conforme proposto aqui, você passaria como está entre aspas simples: node -e 'console.log("hey")' . Isso deve ser _automaticamente_ traduzido para node -e "console.log(\"hey\")" (aspas duplas, \ -escaped literalmente " ) _por trás das cenas_.

Dado o quão longo este tópico se tornou, deixe-me tentar recapitular:
Você só deve se preocupar com os requisitos de sintaxe do _PowerShell_ e é trabalho do PowerShell _como um shell_ garantir que os valores de argumento _verbatim_ que resultam da própria análise do PowerShell sejam passados ​​para o programa externo _as-is_.

  • Em plataformas do tipo Unix, agora que temos suporte .NET Core para isso, fazer isso é trivial, já que os valores textuais podem ser passados ​​como estão, como os elementos de uma _array_ de argumentos, que é como os programas nativamente recebem argumentos lá .
  • No Windows, os programas externos recebem argumentos como uma _cadeia de linha de comando única_ (uma decisão de design histórica lamentável) e devem realizar sua própria análise de linha de comando. A passagem de vários argumentos como parte de uma única string exige regras de citação e análise para delinear os argumentos de maneira adequada; embora isso seja, em última análise, um free-for-all (os programas são livres para analisar como quiserem), a convenção de sintaxe mais amplamente usada (como declarada antes e também proposta no infelizmente agora abandonado RFC ) é o que os compiladores C / C ++ da Microsoft implementam , então faz sentido ir com isso.

    • _Update_: Mesmo no Windows, podemos tirar proveito da propriedade de coleção de tokens verbatim ArgumentList de System.Diagnostics.ProcessStartInfo no .NET Core: no Windows, ele é traduzido automaticamente para um comando devidamente citado e escapado -line string quando o processo é iniciado; no entanto, para _arquivos em lote_, ainda podemos precisar de tratamento especial - consulte https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -552231174

A implementação do acima é, sem dúvida, uma grande mudança significativa, portanto, presumivelmente, requer um opt-in.
Acho que ir em frente com isso é uma obrigação, se quisermos nos livrar de todas as dores de cabeça atuais de citações, que continuam surgindo e dificultam a adoção do PowerShell, especialmente no mundo Unix.


Quanto a node -e 'console.log(`"hey`")' : dentro de '...' , não use ` - a menos que você queira que o caractere seja passado como está. Porque o PowerShell atualmente _não_ escapa literalmente " chars. em seu argumento como \" nos bastidores, o que node vê na linha de comando é console.log(`"hey`") , que é analisado como dois literais de string diretamente adjacentes: não citado console.log(` e com aspas "hey`" . Depois de remover o " , que tem função _sintática_ devido a não ser \ -escaped, o código JavaScript sendo executado é finalmente console.log(`hey`) , e isso só funciona porque um `....` token fechado é uma forma de string literal em JavaScript, ou seja, um _template literal_.

@AndrewSav Eu testei com meu objeto de teste louco e funcionou para mim! Solução muito elegante, testada em Windows 5.1 e PS 7 em linux. Eu estou bem em ter tudo entre aspas, eu não lido com msiexec ou sqlcmd também conhecido por tratar " explicitamente.

Minha implementação pessoal também tem uma lógica de escape simples semelhante à que você mencionou: https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L278

mas eu escrevi um monte de código para exibir e capturar threads STDOUT e STDERR em tempo real dentro desse módulo ... Provavelmente pode ser bastante simplificado, mas não precisei ...

@ mklement0 este encadeamento nunca terminará (: ou precisamos fornecer o módulo PowerShell publicado na galeria ps que atenderá à maioria dos casos de uso e será simples de usar, ou esperar pelos próximos aprimoramentos de shell.

GitHub
Contribua com o desenvolvimento de choovick / ps-invoke-externalcommand criando uma conta no GitHub.

@ mklement0 este encadeamento nunca terminará (: ou precisamos fornecer o módulo PowerShell publicado na galeria ps que atenderá à maioria dos casos de uso e será simples de usar, ou esperar pelos próximos aprimoramentos de shell.

Se a equipe do PowerShell decidir não corrigir isso no próprio programa, eu diria que um shell que não pode executar programas externos nativa e corretamente não será o shell de minha escolha! Essas são as coisas básicas que estão faltando no PowerShell.

@aminya sim, descobri que esse problema é muito chato para mim mesmo quando tive que mudá-lo. Mas recursos como os abaixo valeram a pena.

  • Estrutura de parâmetros flexível, fácil de construir CMDlets confiáveis.
  • Módulos e repositórios de módulos internos para compartilhar lógica comum em toda a organização, evitando muita duplicação de código e centralização da funcionalidade central que pode ser refatorada em um único lugar.
  • Plataforma cruzada. Tenho pessoas executando minhas ferramentas no Windows no PS 5.1 e no linux / mac no PS 6/7

Eu realmente espero que a equipe do PS melhore isso no futuro para torná-lo menos complicado.

A implementação do acima é, sem dúvida, uma grande mudança significativa, portanto, presumivelmente, requer um opt-in.

Você quer dizer implementar https://github.com/PowerShell/PowerShell-RFC/pull/90?

@aminya , concordo que chamar programas externos com argumentos é um mandato central de um shell e deve funcionar corretamente.

Um módulo, conforme sugerido por @choovick , ainda faz sentido para _Windows PowerShell_, que está no modo de manutenção apenas com correções críticas de segurança.
Complementarmente, se / quando o tópico de ajuda conceitual proposto sobre como chamar programas externos for escrito - consulte https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152 - uma função auxiliar que corrige os problemas em versões anteriores / Windows PowerShell's, como @TSlivede acima, podem ser postados lá diretamente.

@iSazonov Sim, implementar https://github.com/PowerShell/PowerShell-RFC/pull/90 é o que eu quis dizer.

Quanto ao tópico sem fim e às preocupações com a mudança decisiva:

A última resposta oficial ao RFC vinculado foi este comentário de @joeyaiello de 8 de julho de 2019 (ênfase adicionada):

mas pensamos que isso [RFC] faz muito sentido, independentemente do comportamento existente e sem levar em conta o seu quebrantamento . Agora que temos recursos experimentais, pensamos que é perfeitamente razoável ir e implementar isso hoje atrás de um sinalizador de recurso experimental, e podemos descobrir mais adiante se esse é um comportamento de aceitação ou não, se há alguma transição caminho e se uma variável de preferência é o mecanismo correto para ativá-lo e desativá-lo.

_Pessoalmente_, eu não me importaria em corrigir o comportamento _por padrão_, mesmo que seja uma alteração significativa; a RFC de fato propõe isso e sugere um opt-in se você quiser o comportamento _antigo_ (quebrado).

Suspeito que aqueles com código legado para manter irão objetar, entretanto, como _todas as soluções alternativas existentes deixarão de funcionar_ - veja acima - e manter a compatibilidade com versões anteriores ainda parece ser o objetivo geral.

Se o novo comportamento fixo for opt-in, você ainda terá a estranheza de ter que fazer algo apenas para obter o comportamento correto, mas pelo menos o código existente não quebrará.

Mas os mecanismos de aceitação existentes são problemáticos:
Agora que o RFC de recursos opcionais do @KirkMunro foi rejeitado, isso praticamente deixa uma _variável de preferência_, e o desafio é o escopo dinâmico do PowerShell: código de terceiros chamado de um escopo que optou por não foi projetado para usar o novo a implementação pode então ser interrompida (a menos que a variável de preferência seja temporariamente redefinida).

O escopo _Lexical_ do opt-in é necessário aqui, o que não temos no momento. O RFC para escopo _lexical_ do modo estrito propõe a implementação de um recurso com escopo lexical (ou um propósito diferente), por meio de uma instrução using (que, notavelmente, geralmente tem escopo _dinamicamente_). Seguindo esse padrão, vale a pena considerar uma instrução using ProperExternalArgumentQuoting escopo léxico (o nome é um WIP :) - se tecnicamente viável.

Precisamos que o comitê do PowerShell avalie (novamente) e forneça uma orientação clara quanto ao caminho a seguir, com feedback _timely_ sobre as questões que surgirem. @ SteveL-MSFT?


Observe que uma solução do tipo --% sugerida pela postagem do blog 7.1 (veja acima ) (que eu pessoalmente acho que não vale a pena perseguir - veja acima e este comentário ), seria um recurso _separate_ - consertar o nativo do PowerShell comportamento (não emulação) ainda é obrigatório.

Pessoalmente, eu não me importaria em corrigir o comportamento por padrão, mesmo que seja uma alteração significativa; a RFC de fato propõe isso e sugere um opt-in se você quiser o comportamento antigo (quebrado).

Se o novo comportamento fixo for opt-in, você ainda terá a estranheza de ter que fazer algo apenas para obter o comportamento correto, mas pelo menos o código existente não quebrará.

Concordo, na verdade, eu argumentaria que faz menos sentido ter um default quebrado do que um default corretamente implementado. Dado o fato de que a implementação atual é na verdade um bug , o novo comportamento deve ser opt-in, não opt-out, uma vez que realmente não faz sentido continuar a encorajar chamadas de shell externas quebradas que são propensas a interromper inesperadamente maneiras. Em qualquer caso, o PowerShell 7 deve se esforçar para melhorar o Windows PowerShell legado.

@ SteveL-MSFT e eu concordamos em fechar este em favor de # 13068. Qualquer coisa que tocamos aqui é uma mudança muito grande, e devemos resolver o problema com um novo operador que funciona como um modo opcional.

Eu absolutamente não vejo como # 13068 resolveria isso: se esse operador for introduzido como pretendido, ainda não temos como chamar adequadamente qualquer executável nativo com um determinado array de argumentos ou com alguns argumentos explícitos cujo conteúdo se origina de variáveis.

O exemplo que @JustinGrote deu naquele thread atualmente não funciona de maneira confiável (se aspas embutidas forem possíveis na carga útil do argumento) e adicionar esse operador não dará nenhuma alternativa que melhore nada.

@joeyaiello Você pode pelo menos deixar este assunto em aberto até que esse operador realmente exista e alguém possa mostrar como esse operador pode melhorar alguma coisa, que foi mencionado neste tópico?

Ah, e também o Linux? Este problema é estúpido e inesperado no Windows, mas no Linux faz ainda menos sentido, especialmente porque não há um loooongo histórico de scripts Powerhell do Linux, que irão falhar.

Criar um operador especial para isso não faz sentido para um shell de linha de comando, já que sua principal tarefa é lançar programas e passar argumentos para eles. Introduzir um novo operador que faz system() para este trabalho é como Matlab apresentando uma maneira de chamar calc.exe porque tem um bug em sua aritmética. O que deveria ser feito é o seguinte:

  • A equipe pwsh se prepara para uma nova versão principal que corrige as coisas da linha de comando, movendo o comportamento atual para trás de um cmdlet integrado.
  • Como uma solução temporária, a próxima versão do pwsh obtém um cmdlet integrado que usa o comportamento novo e correto para passagem de linha de comando.

O mesmo se aplica a Start-Process . (Na verdade, é um bom candidato para o "novo" cmdlet com algumas opções como -QuotingBehavior Legacy ...) Consulte # 13089.

Por que o Powershell está se comportando de maneira diferente nessas duas situações? Especificamente, ele envolve argumentos que contêm espaços entre aspas duplas de maneira inconsistente.

Obtenho resultados consistentes no v.7. Parece corrigido.

PING 'A \"B'

A solicitação de ping não encontrou o host A "B.

PING 'A\" B'

A solicitação de ping não encontrou o host A "B.

Não foi corrigido, porque os nomes de host textuais que ping deveriam ver são A \"B e A\" B - _with_ os \ caracteres.

O PowerShell, como um shell, deve analisar os argumentos de acordo com as regras _its_ - apenas - e, em seguida, garantir de forma transparente que o processo de destino veja os mesmos valores literais que foram o resultado da própria análise do PowerShell.

Esses _outros_ shells - e aqueles programas pobres em execução no Windows que devem agir como seu próprio shell, por assim dizer, tendo que analisar uma _linha de comando_ apenas para extrair os argumentos individuais passados ​​- use \ como escape caractere não deve entrar em cena aqui - acomodar isso (necessário apenas no Windows, no Unix você apenas passa os argumentos verbatim diretamente como uma matriz) é o trabalho do PowerShell como um shell, _por trás das cenas_.

Como um aparte, assim como o PowerShell em si não requer escape de " _inside '...' _ (strings entre aspas simples), nem shells compatíveis com POSIX, como bash : executado a partir de bash , por exemplo, /bin/echo 'A \"B' (sensatamente) imprime A \"B (os \ são tratados como literais em strings entre aspas simples) - que executando o mesmo comando do PowerShell (inesperadamente) produz
A "B - falta o \ - é uma manifestação dos problemas discutidos aqui.

Devo esclarecer:

  • Do ponto de vista de querer passar A "B _verbatim_, você deve ser capaz de usar 'A "B' do PowerShell.

  • A linha de comando que o PowerShell atualmente constrói nos bastidores contém "A "B" - que o processo de destino vê como A B - ou seja, o invólucro cego em "..." , sem escapar de _embedded_ " resultou na perda efetiva do " incorporado. O que o PowerShell _deve_ usar na linha de comando dos bastidores, neste caso, é "A \"B" - ou seja, o " incorporado precisa de \ -escaping.

  • Da mesma forma, o mesmo fechamento cego faz com que 'A \"B' seja representado como "A \"B" na linha de comando dos bastidores, que por acaso transforma o _embedded_ \" em um _escaped_ " caractere, que o processo de destino, portanto, vê como A "B ; ou seja, a falta de escape _automático_ resultou na perda efetiva do \ incorporado. O que o PowerShell _deve_ usar na linha de comando dos bastidores, neste caso, é "A \\\"B" - ou seja, tanto \ quanto " precisam de escape.

Não foi corrigido, porque os nomes de host textuais que ping deveriam ver são A \"B e A\" B - _with_ os \ caracteres.

"Isso" se refere aqui à reclamação citada, que felizmente não posso reproduzir.

@ yecril71pl , vejo: minha suposição (incorreta) era de que o "argumento inconsistente contendo espaços entre aspas duplas" refere-se à _falha de escape automático de " e \ characters_ incorporados, como explicado em meu comentário anterior - e esse é o ponto crucial desta questão.

Houve pequenas correções no PowerShell Core que o Windows PowerShell não possui; Só consigo pensar em um agora:

  • O Windows PowerShell usa aspas duplas cegas no caso de um \ final: 'A B\' transformar em "A B\" (quebrado) - PS Core lida com isso corretamente ( "A B\\" ) .

Dado que este repo é apenas para o PS Core, é suficiente focar no que ainda está quebrado no PS Core (pode ser útil mencionar as diferenças _como um aparte_, mas é melhor deixar esse aspecto explícito).


E mesmo o cenário que você tinha em mente _ainda está quebrado no PS Core - mas apenas _se você omitir \ do argumento_:

Passar 'A" B' ainda resulta em _non_-aspas duplas A" B nos bastidores (enquanto 'A\" B' resulta em "A\" B" - que também está quebrado, conforme discutido - apenas diferente).

Dado que este repo é apenas para o PS Core, é suficiente focar no que ainda está quebrado no PS Core (pode ser útil mencionar as diferenças _como um aparte_, mas é melhor deixar esse aspecto explícito).

Meta à parte: acho útil saber que o comportamento errado mencionado no comentário de outro usuário não se aplica. Claro, poderíamos ter descartado o comentário simplesmente porque o usuário não se preocupou em verificar a versão atual. Bem. Repórteres indisciplinados sendo indisciplinados, é ainda melhor ter certeza. NA MINHA HUMILDE OPINIÃO.

Nenhum argumento aqui, mas fornecer _quadro e contexto adequados_ para tais _asides_ é importante, especialmente em um tópico looooong onde o comentário original - necessário para o contexto - foi postado há muito tempo e está, de fato, agora _descondido_ por padrão (nunca é demais para realmente _link_ ao comentário original sendo citado).

Eu me pergunto se essas discussões fazem alguma diferença. Claramente, as decisões do comitê são independentes do que a comunidade deseja. Observe as tags: Resolution- won't fix. Mas, como o PowerShell é de código aberto (MIT), a comunidade pode corrigir isso em uma bifurcação separada e chamar isso de PowerShellCommunity ( pwshc ).

Isso elimina a necessidade de compatibilidade com versões anteriores. Posteriormente, no PowerShell 8, o comitê pode integrar a bifurcação.

Sobre compatibilidade com versões anteriores: PowerShell não vem pré-instalado em nenhum sistema operacional e, para mim, a dificuldade de instalar PowerShellCommunity é igual a PowerShell . Eu prefiro instalar a versão da comunidade e usá-la imediatamente em vez de esperar por alguma versão 8 futura (ou tornar o código mais complexo com um novo operador).

Já que o Comitê decidiu manter as coisas quebradas, é melhor saber o quão danificadas elas estão. Acho que a comunidade pode viver com Invoke-Native que faz a coisa certa. Não é função da comunidade salvar a face da Microsoft contra sua vontade.

é melhor saber o quão mal eles estão

Eu concordo plenamente - mesmo que o problema não possa ser resolvido no momento, saber como fazer as coisas certas _em princípio, se e quando chegar a hora_ é importante - mesmo que essa hora _nunca_ chegue no contexto de um determinado idioma.

a comunidade pode viver com Invoke-Native que faz a coisa certa

Para ser claro:

  • Algo como Invoke-NativeShell endereça um _caso de uso diferente_ - que é o assunto de # 13068 - e para esse caso de uso, tal cmdlet _não_ é um tapa-buraco: é a solução adequada - consulte https://github.com/ PowerShell / PowerShell / issues / 13068 # issuecomment -656781439

  • _Este_ problema é sobre consertar o _PowerShell propriamente dito_ (não é sobre a funcionalidade da plataforma _nativa_), e a solução _stopgap_ para isso é fornecer uma _opt-in_ de baixa cerimônia - daí a proposta de enviar a função iep como um -in function, pendente de uma correção _proper_ em uma versão futura que tem permissão para quebrar substancialmente a compatibilidade com versões anteriores.

Não é trabalho da comunidade salvar a face da Microsoft

Não acho que a preocupação de @aminya seja em salvar a cara de ninguém - trata-se de consertar comportamentos fundamentalmente quebrados em uma área que é o principal mandato do shell.

Dito isso, não tenho certeza de que fragmentar o ecossistema do PowerShell com uma bifurcação seja o caminho certo a seguir.

Não tenho tempo para responder a tudo isso neste momento, mas acho que isso é razoável, então estou reabrindo:

Você pode pelo menos deixar este problema em aberto até que esse operador realmente exista e alguém possa mostrar como esse operador poderia melhorar alguma coisa, que foi mencionado neste tópico?

Como eu disse em questão relacionada, o operador de chamada nativo adiciona complexidade na UX e é a direção errada.
Ao mesmo tempo, toda a discussão aqui é sobre como simplificar a interação com aplicativos nativos.

A única coisa que nos impede é que se trata de uma mudança significativa. Já dissemos muitas vezes que isso é muita destruição, mas vamos pesar.

Vejamos uma sessão interativa. Um usuário instalará uma nova versão do PowerShell e descobrirá um novo comportamento ao invocar aplicativos nativos. O que ele vai dizer? Vou especular que ele dirá - "Obrigado - finalmente posso apenas digitar e funciona!".

Vejamos o cenário de execução / hospedagem do script. Qualquer nova versão de um aplicativo (mesmo com uma pequena alteração!) Pode interromper um processo de negócios. Sempre esperamos isso e verificamos nossos processos após qualquer atualização. Se encontrarmos um problema, temos várias maneiras:

  • reverter a atualização
  • prepare uma correção rápida para o script
  • desative um recurso que quebra nosso script até que essas coisas sejam corrigidas ou que morram com o tempo.

_Já que atualizações de versão sempre quebram alguma coisa, a última opção é a melhor que poderíamos ter e aceitar._

(Quero salientar que agora o PowerShell Core não é um componente do Windows e, portanto, não pode estragar os aplicativos de hospedagem diretamente, apenas os scripts são afetados diretamente.)

Quero lembrar a você o que Jason disse acima - esta é uma correção de bug.
Deixe-nos consertar e simplificar tudo para todos. Deixe os usuários trabalharem powershell-y .

Como eu disse em questão relacionada, o operador de chamada nativo adiciona cumplicidade na UX e é a direção errada.

complexidade ❓

Vejamos uma sessão interativa. Um usuário instalará uma nova versão do PowerShell e descobrirá um novo comportamento ao invocar aplicativos nativos. O que ele vai dizer? Vou especular que ele dirá - "Obrigado - finalmente posso apenas digitar e funciona!".

Eu tenho um cenário diferente: novos usuários experimentam o PowerShell, percebem que ele falha misteriosamente, mova-o para a lixeira e nunca mais volte. Isso é o que eu faria.

prepare uma correção rápida para o script

Isso é algo que devemos fazer para cobrir os casos óbvios em scripts de usuário.

perceber que ele falha misteriosamente

Toda a discussão aqui é apenas sobre como se livrar dessa "falha misteriosa". E é melhor fazer isso simplificando, mas não adicionando novas coisas misteriosas.

Toda a discussão aqui é apenas para se livrar disso. E é melhor fazer isso simplificando, mas não adicionando novas coisas misteriosas.

Não queremos nos livrar da capacidade de invocar diretamente programas executáveis.

A antiga invocação também não é direta ao cmdline com sua suposição de que algo já foi citado. Além disso, a referida capacidade ainda seria mantida com -%.

Além disso, a referida capacidade ainda seria mantida com -%.

--% requer uma linha de comando predefinida, portanto sua utilidade é limitada.

@ yecril71pl

Não queremos nos livrar da capacidade de invocar diretamente programas executáveis.

Acho que @iSazonov significa se livrar _do comportamento de

@ Artoria2e5 :

A antiga invocação não é direta ao cmdline também com sua suposição de se algo já está citado

[_Update_: Eu li errado a linha citada, mas espero que a informação ainda seja de interesse.]

  • Você não precisa _ adivinhar_, e a regra é simples:

    • _Somente se_ seu nome ou caminho de comando for _quoted_ - '/foo bar/someutil '- e / ou contém _referências de variáveis ​​(ou expressões) - $HOME/someutil - & is _required_.
  • Embora você possa considerar essa necessidade infeliz, ela está no cerne da linguagem do PowerShell e necessária por ser capaz de distinguir sintaticamente entre os dois modos de análise fundamentais, modo de argumento e modo de expressão.

    • Observe que o problema não é específico para chamar _programas externos_; Os comandos nativos do PowerShell também exigem & para invocação, se especificado por meio de uma referência de string / variável entre aspas.
  • Se você não quiser memorizar esta regra simples, a regra mais simples é: _sempre_ use & , e você ficará bem - é um pouco menos conveniente do que _não_ precisar de nada, mas não exatamente um sofrimento (e mais curto do que --% , que é a solução absolutamente errada - veja abaixo)

a referida habilidade ainda seria mantida com -%.

[_Update_: Eu li errado a linha citada, mas espero que a informação ainda seja de interesse.]

Não, apesar da infeliz fusão de # 13068 com _este_ problema, --% - a capacidade de chamar o _shell nativo_, usando _sua_, invariavelmente, sintaxe _específica da plataforma_ - não é de forma alguma uma solução alternativa para o problema em questão e discussão como tal, só aumenta a confusão .

--% é um caso de uso muito diferente e, como proposto atualmente, tem limitações severas; Se há algo que _não_ vale a pena introduzir um _operador_ (ou mudar o comportamento de um existente), é a capacidade de passar uma linha de comando literal para o shell nativo (o que, é claro, você já pode fazer você mesmo, com sh -c '...' no Unix e cmd /c '...' , _mas apenas de forma robusta se este problema for corrigido_; uma implementação binária de Invoke-NativeShell / ins cmdlet, enquanto abstrai principalmente os detalhes do shell de destino A sintaxe CLI evitaria o problema em questão (pelo uso direto de System.Diagnostics.ProcessStartInfo.ArgumentList ) e, portanto, pode ser implementada de forma independente).

@ yecril71pl

Eu tenho um cenário diferente: novos usuários experimentam o PowerShell, percebem que ele falha misteriosamente, mova-o para a lixeira e nunca mais volte. Isso é o que eu faria.

Você poderia esclarecer: Você tem medo de que o comportamento atual do PowerShell possa levar a essa experiência do usuário desagradável, ou teme que a mudança proposta leve a essa experiência do usuário.

Porque, na minha opinião, o comportamento atual do PowerShell é muito mais provável de gerar tal experiência. Como dito acima: O comportamento atual do PowerShell deve ser considerado um bug.

Por que um novo usuário, sem nenhum conhecimento de que o PowerShell está quebrado, emitiria comandos com argumentos distorcidos?

@ yecril71pl Apenas para ter certeza absoluta: você considera a forma atualmente exigida para argumentos "distorcida", não a correção sugerida.

Considero sua pergunta originada de sua suposição de que sou um nerd incurável. Isso está correto - mas mantive a capacidade de imaginar o que um sujeito normal e aleatório consideraria distorcido.

@ mklement0
Eu acho que @ Artoria2e5 estava falando sobre converter a matriz de argumentos para a única string lpCommandLine ao dizer

A antiga invocação não é direta ao cmdline também com sua suposição de se algo já está citado

Porque ao ligar

echoargs.exe 'some"complicated_argument'

você realmente tem que adivinhar mais ou menos se o PowerShell adiciona cotações em torno de some"complicated_argument .

Exemplo 1: na maioria das versões do PowerShell
echoarg.exe 'a\" b' e echoarg.exe '"a\" b"' serão traduzidos para
"C:\path\to\echoarg.exe" "a\" b" (versões testadas 2.0; 4.0; 6.0.0-alpha.15; 7.0.1)
mas meu PowerShell padrão no Win10 (versão 5.1.18362.752) traduz
echoarg.exe 'a\" b' a "C:\path\to\echoarg.exe" a\" b e
echoarg.exe '"a\" b"' a "C:\path\to\echoarg.exe" ""a\" b"" .

Exemplo 2: traduções de versões mais antigas do PowerShell
echoarg.exe 'a"b c"' a "C:\path\to\echoarg.exe" "a"b c"" (versões testadas 2.0; 4.0;)
enquanto as versões mais recentes traduzem
echoarg.exe 'a"b c"' a "C:\path\to\echoarg.exe" a"b c" (versões testadas 5.1.18362.752; 6.0.0-alpha.15; 7.0.1).


Como o comportamento já foi claramente alterado várias vezes, não entendo por que ele não pode ser alterado mais uma vez para obter o comportamento esperado.

Entendo, @TSlivede , obrigado pelo esclarecimento, e desculpe pelo erro de interpretação, @ Artoria2e5.

Quanto ao problema real: estamos 100% de acordo - na verdade, você nunca deve ter que pensar sobre o que lpCommandLine acabará sendo usado nos bastidores do Windows; se o PowerShell fizesse a coisa certa, ninguém teria que fazer (exceto em casos extremos, mas eles não são culpa do PowerShell, e é quando --% (como implementado atualmente) pode ajudar; com uma correção adequada, nunca haverá casos extremos em plataformas do tipo Unix).

Quanto a simplesmente consertar o problema adequadamente: você certamente tem meu voto (mas as soluções alternativas existentes serão interrompidas).

TL; DR: A suposição de que podemos transmitir de forma confiável qualquer valor como um argumento para qualquer programa no subsistema Microsoft Windows NT está errada , portanto, devemos parar de fingir que esse é nosso objetivo. No entanto, ainda há muito a ser resgatado se considerarmos a extensão do argumento.

Ao invocar executáveis ​​nativos do Windows, devemos preservar as citações originais. Exemplo:

CMD /CSTART="WINDOW TITLE"

O sistema não consegue encontrar o arquivo WINDOW.

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

StringConstantType
BareWord
Valor
/ CSTART = TÍTULO DA JANELA
StaticType
System.String
Extensão
/ CSTART = "TÍTULO DA JANELA"
Pai
CMD / CSTART = "TÍTULO DA JANELA"

Se tomarmos a extensão como o modelo, não perderemos nada e poderemos chamar o executável nativo conforme o esperado. A solução alternativa para usar um argumento de string funciona aqui, mas não acho que seja estritamente tecnicamente necessário fazer isso, desde que o suporte adequado seja implementado no PowerShell. Essa abordagem funcionaria para todos os casos.

As aspas dentro de citações representam um problema intransponível porque existem ferramentas que interpretam escape de barra invertida ( TASKLIST "\\\"PROGRAM FILES" ) e ferramentas que não ( DIR "\""PROGRAM FILES" /B ) e ferramentas que não incomodam ( TITLE A " B ) No entanto, se quiséssemos escapar, o escape padrão com barras invertidas envenena todas as ferramentas comuns de gerenciamento de arquivos porque elas simplesmente não suportam aspas e barras invertidas duplas \\ significam algo totalmente diferente para eles (tente DIR "\\\"PROGRAM FILES" /B ), portanto, enviar um argumento com aspas dentro deve ser um erro em tempo de execução. Mas não podemos lançar um erro porque não sabemos qual é qual. Embora o uso do mecanismo de escape normal não deva causar nenhum dano aos argumentos que não contêm aspas, não podemos ter certeza de que, quando aplicado a argumentos que as contêm e alimentado para uma ferramenta que não suporta aspas como valores, necessariamente causa um erro de anulação em vez de um comportamento inesperado, e um comportamento inesperado seria realmente muito ruim. Este é um fardo sério que colocamos sobre o usuário. Além disso, nunca seremos capazes de fornecer ferramentas 'irrelevantes' ( CMD /CECHO='A " B' ).

Observe que as variáveis ​​de ambiente não representam valores em CMD , elas representam fragmentos de código que são refeitos conforme as variáveis ​​de ambiente são expandidas e não há provisão para tratá-los de forma confiável como argumentos para outros comandos. CMD simplesmente não opera em objetos de nenhum tipo, nem mesmo strings, o que parece ser a causa raiz do presente enigma.

TL; DR: A suposição de que podemos passar com segurança qualquer valor como argumento para qualquer programa no subsistema Microsoft Windows NT é _errada_, portanto, devemos parar de fingir que esse é nosso objetivo.

Esse deveria ser o objetivo, não deveria? Não é problema do PowerShell se um programa não consegue interpretar os argumentos que recebe.

Ao invocar executáveis ​​nativos do Windows, devemos preservar as citações originais. Exemplo:

CMD /CSTART="WINDOW TITLE"

Você está sugerindo que chamar um programa deve alterar dinamicamente o idioma do PowerShell para o que o programa invocado usa? Você escreveu esse exemplo no PowerShell, o que significa que deve ser equivalente a qualquer um dos seguintes

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

TL; DR: A suposição de que podemos passar com segurança qualquer valor como argumento para qualquer programa no subsistema Microsoft Windows NT é _errada_, portanto, devemos parar de fingir que esse é nosso objetivo.

Esse deveria ser o objetivo, não deveria? Não é problema do PowerShell se um programa não consegue interpretar os argumentos que recebe.

O programa CMD pode interpretar o argumento /CECHO=A " B mas o PowerShell não pode transmiti-lo sem distorcê-lo.

Ao invocar executáveis ​​nativos do Windows, devemos preservar as citações originais. Exemplo:

CMD /CSTART="WINDOW TITLE"

Você está sugerindo que chamar um programa deve alterar dinamicamente o idioma do PowerShell para o que o programa invocado usa? Você escreveu esse exemplo no PowerShell, o que significa que deve ser equivalente a qualquer um dos seguintes

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

Tentei sugerir que, ao fazer interface com programas externos no subsistema Microsoft Windows NT, o PowerShell tem uma miríade de maneiras de codificar os argumentos que são todos equivalentes ao PowerShell, mas não equivalentes ao programa receptor. Ser direto e forçar o One True Way ™ de codificação de argumentos, sem prestar atenção ao arranjo de citações que o usuário realmente usou, não ajuda, para dizer o mínimo.

@ yecril71pl Estou realmente confuso com seus comentários. O que exatamente você está propondo aqui? Seus casos de uso são todos cobertos por --% . Você o dispensou antes, dizendo

--% requer uma linha de comando predefinida, portanto, sua utilidade é limitada.

Mas, na verdade, você pode usar variáveis ​​de ambiente com --% . Experimente isto:

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

Então, o que estou perdendo?

Estamos perdendo a sintaxe CMD /CSTART="$mytitle" , sem vazar informações para ENV: .

Como uma péssima ideia, temos a opção de substituir Environment.ExpandEnvironmentVariables por outra coisa. Não há implementação nativa no Unix de qualquer maneira, e não acredito que as coisas que ele processa se tornariam críticas para o desempenho quando reescritas em C #.

Visto que os sinais de igual não são permitidos em nomes de env de qualquer maneira, podemos ter %=$a% significar $a . Isso não quebraria nada existente, embora permitindo algumas extensões muito flexíveis (e possivelmente ruins), como fazê-lo funcionar como strings de modelo de JS. Inferno, podemos definir %VARNAME=$var% como algum tipo de sintaxe de fallback também.

Quanto ao inferno de documentação que isso causaria ... Peço desculpas.

  • _Não_ temos um problema de análise.

  • O que temos é um problema com _como o PowerShell transmite os argumentos texturizados literalmente que resultaram de _its_ análise para executáveis ​​externos (nativos )_:

    • No _Windows_, o problema é que a linha de comando para invocar o executável externo com o qual é construído nos bastidores _não_ adere à convenção mais amplamente usada para citar argumentos , conforme detalhado na documentação do compilador C / C ++ da Microsoft, Parsing C ++ command- Seção de

    • O que acontece atualmente não é nem mesmo que uma convenção _diferente_ seja usada: presumivelmente devido a um descuido, as linhas de comando que são construídas são situacionalmente _sintaticamente fundamentalmente quebradas_, dependendo das especificações dos argumentos, relacionadas a uma combinação de aspas duplas e espaços incorporados bem como argumentos de string vazia.

    • Em última análise, o problema é a arquitetura fundamental da criação de processos no Windows: você é forçado a codificar os argumentos para passar para um processo _como uma linha de comando_ - uma única string representando _todos_ os argumentos - em vez de passá-los como uma _matriz_ de argumentos (que é como as plataformas do tipo Unix fazem isso). A necessidade de passar uma linha de comando requer _ regras de citação e escape_ para ser implementado e, em última análise, é _até cada programa_ como interpretar a linha de comando fornecida. Na verdade, isso equivale a forçar desnecessariamente os programas a serem uma espécie de mini-shell: eles são forçados a _re-executar_ a tarefa que o shell já executou, que é uma tarefa que deve ser o escopo de um _shell apenas_ (como é o caso no Unix), ou seja, analisar uma linha de comando em argumentos individuais. Em suma, essa é a anarquia que é a transmissão de argumentos no Windows .

    • Na prática, a anarquia é atenuada por _maioria_ dos programas que aderem à convenção mencionada, e novos programas em desenvolvimento são altamente propensos a aderir a essa convenção, principalmente porque os tempos de execução amplamente usados ​​que sustentam os aplicativos de console implementam essas convenções (como o Microsoft C / C ++ / Tempos de execução do .NET). A solução sensata é, portanto:

      • Faça o PowerShell aderir a essa convenção ao criar a linha de comando nos bastidores.
      • Para programas "desonestos" que _não_ aderem a esta convenção - que inclui principalmente cmd.exe , arquivos em lote e utilitários da Microsoft como msiexec.exe e msdeploy.exe - fornecem um mecanismo para explicitamente controlar a linha de comando passada para o executável de destino; isso é o que --% , o símbolo de parada de análise fornece - embora de forma bastante estranha .
    • No _Unix_, o problema é que _uma linha de comando está sendo construída em tudo_ - em vez disso, a matriz de argumentos literais deve ser passada _como está_ , que o .NET Core agora suporta (desde a v2.1, via ProcessStartInfo.ArgumentList ; deveria _sempre_ ter suportado isso, dado que - sensatamente - _não há linhas de comando_, apenas arrays de argumento, quando um processo é criado em plataformas do tipo Unix).

    • Uma vez que usamos ProcessStartInfo.ArgumentList , todos os problemas no Unix desaparecem.

Resolver esses problemas é o objetivo do https://github.com/PowerShell/PowerShell-RFC/pull/90 de @TSlivede .

Em https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -650242411, propus a compensação automática adicional pela "malandragem" dos arquivos em lote, devido ao seu uso ainda muito difundido como pontos de entrada CLI para alta -Software de perfil, como Azure (CLI az é implementado como um arquivo em lote, az.cmd ).
Da mesma forma, devemos considerar fazer o mesmo para msiexec.exe e msdeploy.exe e talvez outros CLIs "desonestos" de alto nível da Microsoft.


Acabei de publicar um módulo, Native , ( Install-Module Native -Scope CurrentUser ) que aborda todos os itens acima por meio de sua função ie (abreviação de i nvoke (external) e xecutável; é uma implementação mais completa da função iep apresentada acima ).

Também inclui ins ( Invoke-NativeShell ) , que aborda # 13068, e dbea ( Debug-ExecutableArguments ) para diagnosticar a passagem de argumento - consulte https: // github. com / PowerShell / PowerShell / issues / 13068 # issuecomment -671572939 para obter detalhes.

Em outras palavras: ie pode servir como um paliativo discreto enquanto esperamos que esse problema seja corrigido, simplesmente prefixando invocações com ie como o comando:

Ao invés de:

# 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" }'

você usaria o seguinte:

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

Quanto ao exemplo CMD /CSTART="WINDOW TITLE" (cuja forma mais idiomática é cmd /c start "WINDOW TITLE" , que já funciona):

Em essência, é o mesmo problema de prop="<value with spaces>" argumentos para msiexec / msdeploy : PowerShell - justificadamente - transforma /CSTART="WINDOW TITLE" em "/CSTART=WINDOW TITLE" , que, entretanto, quebra a invocação cmd.exe .

Existem duas maneiras de resolver isso:

  • Delegar para ins / Invoke-NativeShell (observe que o uso de cmd.exe /c está efetivamente implícito):

    • ins 'START="WINDOW TITLE"'
    • Se você usar uma string _expandable_, poderá incorporar valores do PowerShell na string de comando.

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

  • Alternativamente, use a implementação --% atual, mas tome cuidado com suas limitações :

    • cmd --% /CSTART="WINDOW TITLE"
    • Conforme discutido, uma limitação problemática de --% é que a única maneira de incorporar valores _PowerShell_ é usar um _aux. variável de ambiente_ e referenciá-la com a sintaxe %...% :

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

      • Para evitar essa limitação, --% deve sempre ter sido implementado com um argumento de string _único_ - por exemplo,

        cmd --% '/CSTART="WINDOW TITLE"' ou cmd --% "/CSTART=`"$title`"" - mas isso não pode ser alterado sem quebrar a compatibilidade com versões anteriores, então um símbolo _novo_ teria que ser introduzido - pessoalmente, não vejo a necessidade de um.

  • eles são forçados a _reformar_ a tarefa que o shell já executou

Não acho que CMD.EXE divida as linhas de comando em argumentos, a única coisa que é necessária é descobrir qual executável chamar e o resto é apenas a linha de comando escrita pelo usuário (após substituições de variáveis ​​de ambiente, que são feitas sem qualquer consideração para os limites do argumento). Claro, os comandos internos do shell são uma exceção aqui.

Em essência, é o mesmo problema que com prop="<value with spaces>" argumentos para msiexec / msdeploy

Não sou um usuário confiante de nenhum dos dois, então preferi mencionar algo com o qual estou mais familiarizado.

Para ser claro: o seguinte não tem impacto sobre os pontos levantados em meu comentário anterior.

Não acho que CMD.EXE divida as linhas de comando em argumentos

  • Ele pode ser executado sem divisão _explícita_ ao chamar _executáveis ​​externos_ (comandos executados por outro executável em um processo filho), mas tem que fazer isso para _arquivos em lote_.

  • Mesmo ao chamar executáveis ​​externos, ele precisa estar _ciente_ dos limites do argumento, de modo a determinar se um determinado metacaractere (por exemplo, & ) tem função _sintática_ ou se é parte de um argumento entre aspas duplas e, portanto, deve ser tratado como um literal:

:: 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"

Além disso, cmd.exe reconhece _embedded_ " chars. em "..." strings são reconhecidas se escapadas como "" :

:: 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."

Infelizmente, cmd.exe _only_ suporta (somente Windows) "" e não também o mais amplamente usado \" (que é o que _shells_ do tipo POSIX no Unix _exclusivamente_ usam - nota: _shells_, não _programs_, porque os programas apenas veem a matriz de argumentos literais que resultam da análise do shell).

Enquanto a maioria dos CLIs no Windows suportam _both_ "" e \" , alguns _apenas_ entendem \" (notavelmente Perl e Ruby), e então você está em apuros:

:: !! 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.

Portanto:

  • Evite chamar cmd.exe diretamente, se possível.

    • Chame executáveis ​​externos diretamente (uma vez que este problema seja corrigido) ou via ie (por enquanto), usando a sintaxe _PowerShell's_.
  • Se você tiver que chamar cmd.exe , use ins / Invoke-NativeShell para simplicidade geral e, especificamente, para saber como é fácil incorporar variáveis ​​PowerShell e valores de expressão na linha de comando .

    • Um motivo legítimo para ainda chamar cmd.exe diretamente é compensar a falta de suporte do PowerShell para dados de byte brutos no pipeline - veja esta resposta SO para um exemplo.

Eu sei que vou pegar muitas críticas aqui, e eu realmente aprecio a profundidade da discussão acontecendo, mas ... patos ... alguém tem um exemplo de que isso realmente importa em um cenário do mundo real ?

Acho que não temos poderes no PowerShell para resolver a "anarquia" que existe atualmente com a análise de argumentos do Windows. E por muitos dos mesmos motivos pelos quais não podemos resolver o problema, há um bom motivo para que o Windows e os compiladores VC ++ tenham optado por não interromper esse comportamento. É galopante e só criaremos uma longa cauda de problemas novos (e em grande parte indecifráveis) se mudarmos as coisas.

Para os utilitários que já são multiplataforma e em uso intenso entre Windows e Linux (por exemplo, Docker, k8s, Git, etc.), não vejo esse problema se manifestando no mundo real.

E para aqueles aplicativos "desonestos" que fazem um trabalho ruim: eles são, em grande parte, utilitários herdados somente do Windows.

Concordo que o que você descreveu @ mklement0 é basicamente uma solução "correta". Só não sei como chegar lá sem realmente estragar as coisas.

Quebra de uso bastante básico:

❯ 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 é um aplicativo bem-comportado, então a correção no PowerShell será direta, embora exija que todos os criadores de script por aí revertam suas soluções inteligentes. cmd.exe é mais difícil de se adaptar e requer uma abordagem muito mais cuidadosa que pode resolver alguns problemas, mas provavelmente não todos. O que é realmente terrível, considerando que o PowerShell começou como uma ferramenta do Windows NT. Entendo essa questão como _se há um cenário da vida real em que um utilitário legado malcomportado como cmd.exe será chamado do PowerShell de uma forma que causa problemas na interface_. O PowerShell tentou abordar esse problema duplicando a maior parte da funcionalidade em cmd.exe , de modo a tornar cmd.exe redundante. Isso também é possível para outras ferramentas, por exemplo, o MSI pode ser operado via ActiveX, embora isso requeira um conhecimento considerável. Então, há algo essencial que não esteja coberto?

@ PowerShell / powershell-Committee discutiu isso. Agradecemos o exemplo git, que mostra claramente um exemplo convincente do mundo real. Concordamos que deveríamos ter um recurso experimental no início de 7.2 para validar o impacto de fazer tal alteração significativa. Um exemplo de teste adicional mostra que mesmo --% tem um problema, embora não devesse ter sido analisado:

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'>

Isso parece ser um problema no fichário de parâmetro de comando nativo.

Sim, obrigado @cspotcode. Esse exemplo foi definitivamente um momento aha para mim (especialmente considerando que eu realmente acertei aquele no mundo real).

Ainda estou preocupado com o aspecto da alteração significativa e acredito que este seja um candidato a um recurso experimental que pode permanecer experimental em várias versões do PowerShell, e isso não é absolutamente algo que temos certeza que acabará por vir.

Também preciso me aprofundar mais para entender o aspecto da lista de permissões / "app rouge" de sua RFC, @ mklement0 , já que não tenho certeza de quanto queremos nos inscrever para manter uma lista como essa.

@joeyaiello e @ SteveL-MSFT, deixe-me fazer uma meta observação primeiro:

Embora seja bom ver que o exemplo de @cspotcode deu a você um _divisão_ do problema, suas respostas ainda revelam uma falta fundamental de compreensão e apreciação da (magnitude do) problema subjacente (discutirei esse ponto em um comentário posterior) .

Este não é um julgamento _pessoal_: eu reconheço plenamente o quão difícil deve ser ser esticado e ter que tomar decisões sobre uma ampla gama de assuntos em um curto período de tempo.

No entanto, isso aponta para um problema _estrutural_: para mim, parece que as decisões são rotineiramente tomadas pelo @ PowerShell / powershell-Committee com base em uma compreensão superficial dos problemas que estão sendo discutidos, em detrimento da comunidade em geral.

Para mim, a resposta do comitê à questão que está sendo discutida aqui é o exemplo mais importante desse problema estrutural até o momento.

Portanto, peço que você considere o seguinte:

Que tal nomear _sub_comitês de assuntos específicos que o comitê consulta e que _tem_ o entendimento necessário das questões envolvidas?

você pode compartilhar o conteúdo de testexe SteveL-MSFT, só quero ter certeza!

@TSlivede resumiu o problema apropriadamente em https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -665125375:

O PowerShell, por outro lado, afirma ser um shell (até que # 1995 seja resolvido, não direi que _é_ um shell)

Como afirmado muitas vezes antes, um mandato central de um shell é chamar executáveis ​​externos com argumentos.

O PowerShell atualmente falha em cumprir esta ordem, visto que os argumentos com aspas duplas incorporadas e argumentos de string vazia não são transmitidos corretamente.

Como afirmado antes, isso pode ter sido menos problemático nos dias apenas com Windows, onde a falta de CLIs externos capazes raramente trazia esse problema, mas esses dias acabaram e o PowerShell quer se estabelecer como uma plataforma cruzada confiável shell, ele deve resolver esse problema.

O exemplo git @cspotcode é bom; qualquer executável para o qual você deseja passar uma string JSON - por exemplo, curl - é outro:

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

Deixando de lado a compatibilidade com versões anteriores:

  • No Unix, o problema é trivial e _completamente_ resolvido usando ProcessStartInfo.ArgumentList nos bastidores.

  • No Windows, o problema é trivial e _principalmente_ resolvido usando ProcessStartInfo.ArgumentList nos bastidores.

    • Para casos extremos (CLIs "desonestos"), existe o ( mal implementado ) --%
    • Como uma _cortesia_, podemos compensar certos casos extremos bem conhecidos para diminuir a necessidade de --% - veja abaixo.

Portanto, _o mais rápido possível_, uma das seguintes escolhas deve ser feita:

  • Perceba a importância de fazer a passagem de argumentos funcionar corretamente e _corrija-a às custas da compatibilidade com versões anteriores_.

  • Se a compatibilidade com versões anteriores for realmente primordial, forneça um novo operador ou uma função como a função ie do módulo Native que corrige o problema e divulgue-o amplamente como a única maneira confiável de invocar o externo executáveis.

Propor um recurso _experimental_ para lidar com um recurso fundamental danificado é totalmente inadequado.


@ SteveL-MSFT

Mesmo considerando o uso de --% como a solução para este problema, é fundamentalmente equivocado:

É um recurso _apenas do Windows_ que conhece apenas "..." quoting e %...% -style referências de variáveis ​​de ambiente.

No Unix, o conceito de "parar a análise" fundamentalmente não se aplica: _não há linha de comando_ para passar para os processos filhos, apenas arrays de argumentos.

Assim, _alguém_ tem que analisar a linha de comando em argumentos _antes_ de invocação, que é implicitamente delegado à classe ProcessStartInfo , por meio de sua propriedade .Arguments , que no Unix usa as convenções do _Windows_ para analisar uma linha de comando - e, portanto, reconhece "..." aspas (com escape de " incorporados como "" ou \" ) apenas.

--% é um recurso exclusivo do Windows cujo único propósito legítimo é chamar CLIs "desonestos".


@joeyaiello

que o Windows e os compiladores VC ++ optaram por não interromper esse comportamento.

O compilador VC ++ _ impõe uma convenção sensata e amplamente observada_ , para trazer ordem à anarquia.

É precisamente a adesão a esta convenção que está sendo defendida aqui , que o uso de ProcessStartInfo.ArgumentList nos daria automaticamente.

_Isso por si só cobrirá a grande maioria das ligações. Cobrir TODAS as chamadas é impossível e, de fato, não é responsabilidade do PowerShell._

Conforme declarado, para CLIs "desonestos" que exigem formas não convencionais de cotação, --% deve ser usado (ou ins / Invoke-NativeShell do módulo Native )

_Como cortesia_, podemos compensar automaticamente cenários "invasores" bem conhecidos, ou seja, chamar arquivos em lote e certos CLIs de alto nível da Microsoft:

  • O caso do arquivo em lote é genérico e facilmente explicado e conceituado (por exemplo, passe a&b como "a&b" , embora não deva exigir cotação) - evitará a necessidade de uso de --% com todos os CLIs que usam arquivos em lote como ponto de entrada (o que é bastante comum), como o az.cmd do Azure

  • A alternativa às exceções de codificação para CLIs específicas - que reconhecidamente podem ser confusas - é detectar o seguinte _pattern_ nos argumentos que resultam da análise do PowerShell - <word>=<value with spaces> - e, em vez de passar "<word>=<value with spaces>" , como acontece atualmente, para passar <word>="<value with spaces>" ; o último satisfaz os CLIs "desonestos", ao mesmo tempo que é aceito por CLIs que aderem à convenção; por exemplo, echoArgs "foo=bar baz" finalmente vê o mesmo primeiro argumento como echoArgs --% foo="bar baz"

@musm Você pode encontrar o código-fonte de TestExe em https://github.com/PowerShell/PowerShell/blob/master/test/tools/TestExe/TestExe.cs.

GitHub
PowerShell para todos os sistemas! Contribua com o desenvolvimento do PowerShell / PowerShell criando uma conta no GitHub.

Acho que acomodar exceções por padrão só vai levar a uma situação semelhante à atual, em que as pessoas precisam reverter a "utilidade" do PowerShell. Se houver exceções, deve ser óbvio que estão sendo aplicadas.

Talvez algo como:

# 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'

Estou realmente lutando para encontrar uma sintaxe para isso que não esteja quebrada. Pelo menos isso faz sentido se você pensar nisso como uma conversão do programa, mas a conversão da variável real que contém o programa exigiria parênteses.

Isso, além de permitir que as pessoas adicionem exceções para qualquer comportamento quebrado que desejarem, esperamos que elimine a necessidade de --% . A classe de exceção que eles registram teria um método para determinar se ela é aplicável ao programa (para invocação inteligente) e um método de escape onde você apenas lança a árvore de sintaxe abstrata do comando e retorna o argumento array / string.

  • em vez de passar "<word>=<value with spaces>" , como acontece atualmente, para passar <word>="<value with spaces>"

As aspas usadas para construir a linha de comando devem seguir a forma como a chamada é citada no PowerShell. Portanto:

  1. Uma chamada que não contenha aspas duplas em seu texto deve colocar aspas duplas em torno de todo o valor.
  2. Uma chamada que contém aspas duplas deve mantê-los conforme escrito no script _se possível_.

Em particular:
| argumento do script | linha de comando |
| ----------------- | ---------------- |
| p=l of v | "p = l de v" |
| p=l` of` v | "p = l de v" |
| p="l of v" | p = "l de v" |
| p="l of v"'a s m' | p = "l de v" a "sm" |
| p="l of v"' s m' | p = "l de vsm" |

A última linha mostra um exemplo onde não será possível manter as aspas duplas originais.

Eu sei que vou pegar muitas críticas aqui, e eu realmente aprecio a profundidade da discussão acontecendo, mas ..._ patos _... alguém tem um exemplo de que isso realmente importa em um cenário do mundo real ?

Eu já mencionei neste tópico há um ano (está escondido agora ...) que eu costumava ter muitos momentos WFT ao usar ripgrep no Powershell. Não consegui entender por que não consegui pesquisar strings entre aspas. Ele ignorou minhas citações:

rg '"quoted"'

e no git bash isso não aconteceu.

Agora recebo menos momentos deste WTF porque, infelizmente, encontrei este longo problema no github e descobri que passar " para o Powershel está totalmente quebrado. O exemplo "git.exe" recente também é ótimo.

Para ser honesto, agora nem me atrevo a usar o Powershell para chamar o comando nativo quando sei que posso estar passando " em string como parâmetro. Eu sei que posso obter um resultado errado ou erro.

Realmente, @ mklement0 resumiu muito bem (isso deve estar gravado em pedra em algum lugar)

Como afirmado muitas vezes antes, um mandato central de um shell é chamar executáveis ​​externos com argumentos .
O PowerShell atualmente falha em cumprir esta ordem, visto que os argumentos com aspas duplas incorporadas e argumentos de string vazia não são transmitidos corretamente.
Como afirmado antes, isso pode ter sido menos problemático na época somente do Windows, onde a falta de CLIs externos capazes raramente trazia esse problema, mas esses dias acabaram e se o PowerShell deseja se estabelecer como uma plataforma cruzada confiável shell, ele deve resolver esse problema .

E sobre mudanças significativas.
Recentemente, um colega de trabalho me escreveu dizendo que meu script não funcionou em sua máquina. Eu estava executando apenas no Powershel Core e ele estava executando no Windows Powershell. Acontece que Out-File -Encoding utf8 arquivo codificado com "BOM" no Windows Powershell e sem BOM no Powershel Core. Este é um exemplo não relacionado, mas mostra que já existem mudanças sutis no Powershel e isso é bom porque estamos eliminando peculiaridades e comportamento intuitivo de uma linguagem que é famosa por ele. Seria ótimo se a equipe do Powershel fosse um pouco mais tolerante quando se trata de interromper as mudanças, agora que temos o Powershell de plataforma cruzada que está sendo enviado fora do Windows e que sabemos que o Windows Powershell está em modo de "manutenção" e poderá ser usado para sempre se você realmente quer que ele execute algum script antigo que quebrou na versão mais recente do Powershell.

RE: esse último ponto de mudanças significativas - concordo plenamente. Existem _muitas_ alterações importantes que toleramos por vários motivos. No entanto, cada vez mais parece ser o caso de que algumas alterações significativas são simplesmente desaprovadas por razões de preferência e não recebem a devida gravidade de consideração para seu valor real.

Existem algumas mudanças como esta que melhorariam _massivamente_ a experiência geral do shell para qualquer um que precise ir além do PowerShell para fazer as coisas, o que acontece o tempo todo. Foi acordado repetidamente que o comportamento atual é insustentável e já está em grande parte quebrado para qualquer coisa, exceto os usos mais simples. E, no entanto, ainda estamos enfrentando essa reticência em interromper alterações, mesmo quando há dezenas de alterações de interrupção já aceitas, algumas das quais têm um impacto igualmente grande.

Para aqueles que pedem exemplos - reserve um minuto para visitar Stack Overflow uma vez. Tenho certeza de que @ mklement0 tem uma série de exemplos em que a ajuda da comunidade é necessária para ajudar a explicar uma mudança significativa em versões mais recentes. Isso acontece o tempo todo_. Não temos desculpa para não fazer alterações úteis.

Sempre que a equipe MSFT repete a mesma coisa indefinidamente, podemos ter certeza de que eles sabem mais do que podem dizer publicamente. Devemos respeitar sua disciplina interna e não pressioná-los. _Talvez possamos encontrar um meio-termo._ Espero ter tempo hoje para descrever um caminho alternativo para a migração preguiçosa.

Eu reconheço isso, e é por isso que raramente faço questão de questionar isso.

No entanto, este é um projeto de código aberto; se não houver visibilidade possível dessas decisões, as pessoas _vai_ inevitavelmente ficarão frustradas. Não há culpa em nenhum dos lados da moeda, essa é apenas a realidade da situação aqui, IMO. Então, sim, ter um caminho de migração pode aliviar um pouco essa dor, mas precisamos de políticas claras definidas sobre como isso deve funcionar para fazer as coisas funcionarem para o máximo de pessoas possível. No entanto, é difícil chegar a um acordo quando faltam informações.

Estou ansioso para ver o que você tem na manga. 😉

@ mklement0 você está absolutamente certo, e tanto que só posso responder ao seu meta-ponto agora. Infelizmente, nos casos em que não conseguimos atingir o nível de profundidade necessário para responder a uma pergunta como esta, a abordagem mais segura é geralmente adiar ou rejeitar a alteração significativa até que tenhamos mais tempo para

Quero fazer outro meta-ponto sobre as alterações de interrupção: nossa telemetria implica que a maioria dos usuários do PowerShell 7 não está gerenciando suas próprias versões. Eles estão executando scripts automatizados em um ambiente gerenciado que é confortável, por exemplo, atualizando seus usuários de 6.2 para 7.0 (veja o salto de 2 dias em usuários 6.2 se tornando usuários 7.0 a partir de 03/08; este não é nosso único ponto de dados aqui, mas é conveniente agora que mostra o ponto). Para esses usuários, uma alteração significativa que transforma um script perfeitamente funcional em um script não funcional é inaceitável.

Também devo à comunidade um blog sobre como penso sobre o impacto das alterações significativas: ou seja, trocando a prevalência do uso existente e a gravidade da quebra pela facilidade de identificar e corrigir a quebra. Este é extremamente prevalente em scripts existentes, confuso para identificar e corrigir, e o comportamento de quebra vai do sucesso total ao fracasso total, daí minha extrema reticência em fazer qualquer coisa aqui.

Acho que é justo dizer que não faremos nada aqui no 7.1, mas estou definitivamente aberto a fazer disso uma prioridade investigativa para o 7.2 (ou seja, gastamos mais do que apenas o tempo do nosso Comitê discutindo isso).

Que tal nomear subcomitês de assuntos específicos que o comitê consulte e que tenham o entendimento necessário das questões envolvidas?

Estamos trabalhando nisso. Eu sei que já disse isso antes, mas estamos extremamente próximos (como em, estou criando o blog e você provavelmente verá alguns novos rótulos aparecerem em breve com os quais estaremos brincando).

Agradeço a paciência de todos e reconheço que é irritante obter uma resposta enérgica do Comitê a cada duas semanas, quando as pessoas estão colocando uma quantidade imensa de pensamento e consideração na discussão. Sei que parece que isso significa que não estamos pensando profundamente sobre as coisas, mas acho que simplesmente não estamos expressando a profundidade de nossas discussões com tantos detalhes como as pessoas fazem aqui. Em meu próprio backlog, tenho todo um conjunto de tópicos de blog, como a mudança repentina, em torno de como penso sobre como tomar decisões dentro do Comitê, mas nunca tive a chance de sentar e colocá-los para fora. Mas posso ver aqui que talvez as pessoas achem muito valor nisso.

Espero não ter levado os trilhos muito longe nesta discussão. Não quero que esse problema se torne totalmente um meta-problema sobre o gerenciamento do projeto, mas queria abordar parte da frustração compreensível que vejo aqui. Imploro a qualquer pessoa que queira falar sobre isso com mais detalhes conosco que participe da Chamada da aqui e com certeza vou respondê-las na chamada).

Apenas uma nota rápida sobre o meta-ponto: agradeço a resposta atenciosa, @joeyaiello.

Quanto à gravidade da alteração significativa: As seguintes afirmações parecem estar em desacordo:

Alguém tem um exemplo de como isso realmente importa em um cenário do mundo real

vs.

Este é extremamente prevalente em scripts existentes, confuso para identificar e corrigir

Se já for predominante, a estranheza e a obscuridade das soluções alternativas necessárias são mais uma razão para finalmente corrigir isso, especialmente considerando que devemos esperar que o número de casos aumente.

Sei que todas as soluções alternativas existentes serão interrompidas.

Se evitar isso for fundamental, esta abordagem sugerida anteriormente é o caminho a percorrer:

fornecer um _novo operador ou uma função_ como a função ie do módulo Native que corrige o problema e divulgá-lo amplamente como a única maneira confiável de invocar executáveis ​​externos.

Uma _função_ como ie permitiria que as pessoas optassem pelo comportamento correto com _funco mínimo_, _como um paliativo_, sem sobrecarregar a _linguagem_ com um novo elemento sintático (um operador), cuja única razão de ser seria para contornar um bug legado considerado muito problemático para corrigir:

  • O paliativo forneceria acesso _autorizado oficialmente_ ao comportamento correto (sem dependência de recursos _experimentais_).
  • Enquanto o paliativo for necessário, ele precisaria ser amplamente divulgado e devidamente documentado.

Se / quando o comportamento padrão for corrigido:

  • a função pode ser modificada para apenas adiá-la, de modo a não quebrar o código que a usa.
  • novo código pode ser escrito sem precisar mais da função.

Uma função como ie permitiria que as pessoas optassem pelo comportamento correto com o mínimo de barulho, como um paliativo

Podemos simplificar a adoção por meio do # 13428. Podemos injetar isso com as investigações de @ mklement0 no Engine de forma transparente.

@Dabombber

Acho que acomodar exceções por padrão só vai levar a uma situação semelhante à atual, em que as pessoas precisam reverter a "utilidade" do PowerShell. Se houver exceções, deve ser óbvio que estão sendo aplicadas.

As acomodações que estou propondo fazem a grande maioria das chamadas "simplesmente funcionar" - são abstrações úteis da anarquia da linha de comando do Windows da qual devemos fazer o nosso melhor para proteger os usuários.

A suposição justificável é que essa proteção é um esforço único para executáveis ​​_legacy_ e que os recém-criados irão aderir às convenções C / C ++ da Microsoft.

É impossível fazer isso em _todos_ os casos; para aqueles que não podem ser acomodados automaticamente, há --% .

Pessoalmente, não quero ter que pensar se um determinado utilitário foo é implementado como foo.bat ou foo.cmd , ou se requer foo="bar none" argumentos especificamente, sem também aceitar "foo=bar none" , que para executáveis ​​em conformidade com a convenção são equivalentes.

E certamente não quero um formulário de sintaxe separado para várias exceções, como &[bat]
Em vez disso, --% é a ferramenta abrangente (somente para Windows) para formular a linha de comando exatamente como você deseja que ela seja passada - sejam quais forem os requisitos específicos e não convencionais do programa de destino.

Especificamente, as acomodações propostas são as seguintes:

Nota:

  • Conforme declarado, eles são necessários _no Windows_ apenas; no Unix, delegar a ProcessStartInfo.ArgumentList é suficiente para resolver todos os problemas.

  • Pelo menos em um nível alto, essas acomodações são fáceis de conceituar e documentar.

  • Observe que eles serão aplicados _após_ a análise usual do PowerShell, na etapa de tradução para o processo de linha de comando (somente Windows). Ou seja, a análise de parâmetro do próprio PowerShell não estará envolvida - e _shouldn't_ be , @ yecril71pl.

  • Quaisquer casos verdadeiramente exóticos não cobertos por essas acomodações teriam que ser tratados pelos próprios usuários, com
    --% - ou, com o módulo Native instalado, com ins / Invoke-NativeShell , o que torna mais fácil incorporar a variável PowerShell e valores de expressão na chamada.

    • O comando dbea ( Debug-ExecutableArguments ) do módulo Native pode ajudar a diagnosticar e entender qual linha de comando de processo é usada - consulte a seção de exemplo abaixo.

Lista de acomodações:

  • Para arquivos em lote (conforme observado, a importância de acomodar esse caso automaticamente é a prevalência de CLIs de alto perfil que usam arquivos em lote _como seu ponto de entrada_, como az.cmd para Azure).

    • Aspas duplas incorporadas, se houver, são escapadas como "" (em vez de \" ) em todos os argumentos.
    • Qualquer argumento que não contenha cmd.exe metacaracteres, como & é colocado entre aspas duplas (enquanto o PowerShell, por padrão, apenas inclui argumentos com espaços entre aspas duplas); por exemplo, um argumento literal visto pelo PowerShell como a&b é colocado como "a&b" na linha de comando passada para um arquivo em lote.
  • Para CLIs de alto perfil, como msiexec.exe / msdeploy.exe e cmdkey.exe (sem exceções de codificação para eles):

    • Qualquer invocação que contenha pelo menos um argumento das seguintes formas aciona o comportamento descrito abaixo; <word> pode ser composto de letras, dígitos e sublinhados:

      • <word>=<value with spaces>
      • /<word>:<value with spaces>
      • -<word>:<value with spaces>
    • Se tal argumento estiver presente:

      • As aspas duplas incorporadas, se houver, são escapadas como "" (em vez de \" ) em todos os argumentos. - consulte https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 para saber por que _não_ devemos fazer isso; isso significa que no caso raro de <value with spaces> ter _embedded_ " chars., --% deve ser usado; por exemplo,
        msiexec ... --% PROP="Nat ""King"" Cole"
      • Apenas a parte <value with spaces> é colocada entre aspas duplas, não o argumento como um todo (o último sendo o que o PowerShell - justificadamente - faz por padrão); por exemplo, um argumento literal visto pelo PowerShell como foo=bar none é colocado como foo="bar none" na linha de comando do processo (em vez de "foo=bar none" ).
    • Nota:

      • Se o executável alvo _não_ for um CLI msiexec -style, nenhum dano será causado, porque CLIs que seguem a convenção consideram sensivelmente <word>="<value with spaces>" e "<word>=<value with spaces>" _equivalent_, ambos representando literalmente <word>=<value with spaces> .

      • Da mesma forma, a grande maioria dos executáveis ​​aceita "" indistintamente com \" para escapar de " chars incorporados., Com a notável exceção de CLI, Ruby e Perl do próprio PowerShell (_not_ desempenho a acomodação vale a pena, pelo menos, se a CLI do PowerShell está sendo chamada, mas acho que codificar Ruby e Perl também faria sentido). https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 mostra que todos os aplicativos que usam a função CommandLineToArgvW WinAPI _not_ suportam "" -escaping.

Todos os outros casos no Windows também podem ser tratados com ProcessStartInfo.ArgumentList , que aplica implicitamente a convenção Microsoft C / C ++ (que significa \" para " -escaping).


A função ie da versão atual ( 1.0.7 ) do módulo Native implementa essas acomodações (além de corrigir a análise de argumento quebrada) , para PowerShell versões 3 e superiores ( Install-Module Native ).

Convido você e todos aqui a aplicá-lo para testar a afirmação de que ele "simplesmente funciona" para a grande maioria das chamadas executáveis ​​externas

Limitações atualmente inevitáveis:

  • Observação: essas limitações técnicas vêm de ie sendo implementado como uma _função_ (uma correção adequada no próprio mecanismo _não_ teria estes problemas):

    • Embora $LASTEXITCODE esteja corretamente definido para o código de saída do processo, $? acaba sempre $true - o código do usuário atualmente não pode definir $? explicitamente, embora adicionando esta capacidade recebeu luz verde - consulte https://github.com/PowerShell/PowerShell/issues/10917#issuecomment -550550490. Infelizmente, isso significa que você não pode usar ie significativamente com && e || , os && , os operadores da cadeia de pipeline .
      No entanto, se _abortar_ um script para detectar um código de saída diferente de zero for desejado, a função iee wrapper pode ser usada.

    • -- como um argumento é invariavelmente "comido" pelo fichário de parâmetro do PowerShell; simplesmente passe-o _duas vezes_ para passar -- para o executável de destino ( foo -- -- invés de foo -- ).

    • Um token sem aspas com , deve ser colocado entre aspas para não ser interpretado como uma matriz e passado como vários argumentos; por exemplo, passe 'a,b' vez de a,b ; da mesma forma, passe -foo:bar (algo que se parece com um argumento nomeado do PowerShell) como '-foo:bar' (isso não deve ser necessário, mas é devido a um bug: # 6360); da mesma forma '-foo.bar must be passed as ' -foo.bar'` (outro bug, que também afeta chamadas diretas para executáveis ​​externos: # 6291)

  • Espero que a função funcione de forma robusta no PowerShell _Core_. Devido às mudanças no _Windows PowerShell_ ao longo do tempo, pode haver casos extremos que não são tratados corretamente, embora eu só saiba de dois:

    • A acomodação de citação parcial para CLIs do estilo msiexec não pode ser aplicada nas versões 3 e 4, porque essas versões envolvem o argumento inteiro em um conjunto adicional de aspas duplas; ele funciona na v5.1, no entanto.

    • "" -escaping é usado por padrão, para contornar problemas, mas nos casos em que \" é necessário (PowerShell CLI, Perl, Ruby), um token como 3" of snow é passado por engano como _3_ argumentos, porque todas as versões do Windows PowerShell negligenciam colocar esse argumento entre aspas duplas; isso parece acontecer para argumentos com caracteres não iniciais " que não são precedidos por um caractere de espaço.


Exemplos, com saída do PowerShell Core 7.1.0-preview.5 no Windows 10:

Nota: A função dbea ( Debug-ExecutableArguments ) é usada para ilustrar como os argumentos seriam recebidos por executáveis ​​externos / arquivos em lote.

Passagem de argumento atual quebrada:

  • Chamar um aplicativo compatível com a convenção (um aplicativo de console .NET usado por dbea nos bastidores por padrão):
# 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" }"
  • Chamando um arquivo em lote:

Observe o uso de -UseBatchFile para fazer dbea passar os argumentos para um auxiliar _arquivo em lote_.

# 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.
  • Chamando um CLI msiexec -style, 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.

Resolvendo o problema com ie :

Observe o uso de -ie nas chamadas de dbea , o que causa o uso de ie para as invocações.

  • Chamar um aplicativo compatível com a convenção (um aplicativo de console .NET usado por dbea nos bastidores por padrão):
# 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\" }"
  • Chamando um arquivo em lote:
# 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"" }">
  • Chamando um CLI msiexec -style, 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.

Para mostrar que as aspas duplas somente de valor foram aplicadas na linha de comando real, por meio de 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"

O código a seguir causa perda de dados:

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

1

@JamesWTruher tem uma proposta de correção e está validando se ela aborda as questões levantadas neste problema

Existe uma solicitação de pull dessa correção proposta? Seria bom se pudéssemos comentar sobre o PR. Porque consertar isso foi IMO nunca a parte complicada. A parte complicada era como lidar com a compatibilidade com versões anteriores. E seria bom se pudéssemos ver como a correção proposta lida com isso ...

É bom ouvir isso, @ SteveL-MSFT - uma solução para _todos_ os problemas discutidos aqui? Afinal, para a v7.1? E eu concordo com o pedido de

@ yecril71pl , é um bom achado, embora este realmente se relacione com a análise do próprio PowerShell (que tem _algumas_ caixas especiais para executáveis ​​externos), não com a forma como a linha de comando nativa é construída _após_ a análise (que é de onde vêm os problemas discutidos anteriormente de).

Uma reprodução mais sucinta do problema, no Unix:

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

Ou seja, apenas o elemento do array _first_ foi diretamente anexado a -a: , os outros foram passados ​​como argumentos separados.

Existem problemas relacionados com argumentos que _ parecem_ parâmetros do PowerShell, mas não são:

Há um problema relacionado que afeta apenas chamadas para comandos _PowerShell_ que usam $args / @args : # 6360

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

Também existe o # 6291, que afeta os comandos do PowerShell e os executáveis ​​externos (observe o . ).

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

Uma coisa a notar é que (...) como parte de uma palavra de barra normalmente resulta na saída de (...) como um todo se tornando um argumento _separate_, então o fato de que o primeiro elemento _está_ anexado ao printf comando
& { $args.Count; $args.ForEach({ "$_" }) } foo('bar', 'baz') produz 2, 'foo', 'bar baz' , sendo o segundo argumento a stringificação do array 'bar', 'baz' .

Quando o PowerShell precisa passar -A:(1,2) para um executável externo, ele descobre que -A: é uma string e (1,2) é um array que deve ser empacotado como '1 2'. O PowerShell tenta preservar a sintaxe original da invocação, então, juntando tudo, obtemos '-A: 1 2', enquanto o resultado correto seria '-A: "1 2"'. Parece uma omissão trivial no código de organização para mim.

Eu não diria que o problema específico de @ yecril71pl está relacionado à análise (embora eu concorde, que não tem nada a ver com o problema de "conversão de array em linha de comando", que é discutido nesta edição).

Quando o PowerShell tem que passar -A: (1,2) para um executável externo, ele descobre que -A: é uma string e (1,2) é uma matriz

Quase: -A: é um parâmetro nomeado e a matriz é o valor desse parâmetro (para testar isso: remova o - da frente e você verá que ele está entre aspas de forma diferente). Mas o problema não é que a matriz foi convertida incorretamente em uma string - o problema é que, para executáveis ​​nativos, os argumentos são (quase) sempre salpicados, mesmo quando se usa $ e não @ e mesmo se a matriz se originar de uma expressão como (1,2) .

Teste por exemplo printf '<%s>\n' -a:('a b',2) : como a string a b contém um espaço, ela está corretamente entre aspas, mas como 2 está no próximo elemento da matriz e a matriz está dividida, 2 não faz parte do primeiro argumento.


A mágica acontece em NativeCommandParameterBinder.cs

Na linha 170, o PowerShell tenta obter um enumerador para o valor do argumento atual.

IEnumerator list = LanguagePrimitives.GetEnumerator(obj);

Se list não for null , o powershell adiciona cada elemento da lista (possivelmente entre aspas se contiver espaços) ao lpCommandLine.

Os elementos são separados por espaços ( linha 449 ) por padrão. A única exceção é se a matriz for literal
(como em printf '<%s>\n' -a:1,2 ).
Em seguida, o PowerShell tenta usar o mesmo separador no lpCommandLine, que foi usado na linha do script.

Espero um PR quando estiver pronto. Se chegar ao 7.1, nós o pegaremos, caso contrário, será 7.2. Compatibilidade com versões anteriores é algo que ele está abordando. Talvez o que ajudaria seria alguma ajuda para escrever testes Pester (usando testexe -echoargs que podem ser construídos usando publish-pstesttools de build.psm1).

Espero um PR quando estiver pronto

Isso é exatamente o que eu queria evitar - mostre o código que não está pronto (marque PR como trabalho em andamento).

Ou pelo menos comentar, o que ele quer fazer.

Seria bom se pudéssemos discutir como ele deseja lidar com a compatibilidade com versões anteriores.

@TSlivede , observe que, como o PowerShell _CLI_ é chamado - um executável externo - -A:(1,2) é analisado _antes_ sabendo que esse token acabará se vinculando ao parâmetro _named_ -A - que tal parâmetro _eventualmente_ entra em jogo é secundário ao problema.

@ yecril71pl :

Ele descobre que -A: é uma string

Não, é um caso especial durante a análise, porque parece _parecer_ um parâmetro do PowerShell.

Este caso especial acontece para chamadas para comandos do PowerShell que usam $args também (ao contrário de ter parâmetros declarados reais que são vinculados), mas acontece _diferentemente_ para executáveis ​​externos (o que normalmente é um argumento separado permanece anexado, mas, no caso de uma coleção, apenas seu elemento _primeiro_).

Na verdade, você pode cancelar este caso especial se passar -- antemão, mas, é claro, isso também passará -- , que só é removido para chamadas para comandos _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>

Se o argumento _doesn't_ se parece com um parâmetro PowerShell, o comportamento usual (saída de (...) torna-se um argumento separado) entra em ação até mesmo para executáveis ​​externos (com o comportamento normal de um _array_ sendo espatifado, ou seja, sendo transformado em argumentos individuais no caso executável externo).

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

Aplicar este comportamento _consistentemente_ faria sentido - uma expressão (...) como parte de uma palavra de barra deve _sempre_ se tornar um argumento separado - consulte # 13488.

Para passar um único argumento '-A:1 2 3' , com a matriz _stringificada_, use uma (n implícita) _cadeia expansível_, caso em que você precisa de $(...) vez de (...) _and_ - surpreendentemente - atualmente também "..." :

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.

Você _não_ deve precisar de "..." neste caso - é novamente necessário por causa das anomalias relacionadas a tokens que _parecem_ parâmetros (que em geral se aplicam a _both_ PowerShell e chamadas executáveis ​​externas - consulte # 13489); se não, você não precisa citar:

# 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.

O mundo dos tokens compostos no modo de argumento é complexo, com várias inconsistências - consulte # 6467.

@ SteveL-MSFT

Em sua forma atual, testexe -echoArgs imprime apenas os argumentos individuais que o executável do .NET Core analisou na linha de comando bruta (no Windows), não na própria linha de comando bruta.

Portanto, ele não pode testar acomodações com cotação seletiva para arquivos em lote e CLIs no estilo msiexec - assumindo que tais acomodações serão implementadas, o que eu recomendo fortemente; por exemplo, você não poderá verificar se PROP='foo bar' foi passado como PROP="foo bar" , com aspas duplas em torno da parte do valor.

No entanto, para imprimir a linha de comando bruta, testexe não deve ser um executável .NET _Core_, porque o .NET Core _cria uma linha de comando hipotética_ que sempre usa \" -escaping para " incorporado "" foi usado, e geralmente não reflete fielmente quais argumentos foram citados duas vezes e quais não foram - para obter informações básicas, consulte https://github.com/ dotnet / runtime / issues / 11305 # issuecomment -674554010.

Apenas um executável compilado em .NET _Framework_ mostra a linha de comando verdadeira em Environment.CommandLine , então testexe teria que ser compilado dessa forma (e alterado para (opcionalmente) imprimir a linha de comando bruta).

Para testar as acomodações para arquivos em lote, um arquivo _batch_ de teste separado é necessário, para verificar se 'a&b' é passado como "a&b" e 'a"b' como "a""b" , para instância.

A compilação de

Como alternativa, podemos apenas confiar em um script bash simples para Linux / macOS para emitir os args?

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

E no Windows algo semelhante com um arquivo em lote.

Que tal usar o node com um script .js?

console.log(process.execArgv.join('\n') ou qualquer string que manipule você
quer fazer para que a saída tenha uma boa aparência?

@cspotcode , para obter a linha de comando bruta, precisamos de uma chamada WinAPI.

@ SteveL-MSFT:

No Windows , você pode delegar a compilação ao _Windows PowerShell_ por meio de sua CLI, que é o que faço em dbea ; aqui está um exemplo simples que produz um executável .NET _Framework_ que ecoa a linha de comando bruta (apenas), ./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);
  }
}
'@

}

Chamada de amostra:

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

Quanto a um _arquivo em lote_ que ecoa seus argumentos, dbea também cria um sob demanda .

No Unix , um script de shell simples, conforme mostrado em seu comentário, é de fato suficiente, e você pode até usar um script ad hoc que você passa para /bin/sh como um _argumento_ .

@ PowerShell / powershell-Committee discutiu isso hoje, estamos pedindo a @JamesWTruher para atualizar seu PR para incluir também como parte de seu recurso experimental para pular a etapa no processador de comando nativo que reconstrói a matriz de args de volta para uma string e apenas passa isso para os novos argumentos da matriz em ProcessStartInfo (há um pouco de código para garantir que os nomes e valores dos parâmetros sejam correspondidos apropriadamente). Além disso, aceitamos que podemos precisar de uma lista de permissões para comandos conhecidos de casos especiais que ainda falham com a alteração proposta e que é algo que pode ser adicionado posteriormente.

Para quem não deve ter notado: o PR foi publicado (como um WIP) e já está sendo discutido: https://github.com/PowerShell/PowerShell/pull/13482

PS, @ SteveL-MSFT, sobre como obter a linha de comando bruta no Windows: claro, uma alternativa para delegar a compilação ao Windows PowerShell / .NET _Framework_ é aprimorar o aplicativo de console .NET _Core_ existente para tornar um (plataforma condicional) P / Invoca a chamada para a função GetCommandLine() WinAPI, conforme demonstrado abaixo.

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

Compatibilidade com versões anteriores é algo que ele está abordando.

Acho que está implícito em seu esclarecimento posterior que ProcessStartInfo.ArgumentList (uma _coleção_ de _verbatim_ argumentos usados ​​no estado em que se encontram no Unix e traduzidos para um MS C / C ++ - linha de comando compatível com a convenção no Windows pelo próprio .NET Core ) deve ser usado, mas deixe-me declarar explicitamente:

  • Corrigir esse problema de forma adequada, de uma vez por todas, _exclui qualquer concessão à compatibilidade com versões anteriores_.

  • O PR de @JamesWTruher está no caminho certo no momento em que este argumentos vazios ainda não foram transmitidos .

    • Uma vez que isso seja resolvido, a correção está completa no _Unix_ - mas não possui as acomodações importantes para CLIs no _Windows_ (veja abaixo).

podemos precisar de uma lista de permissões para comandos conhecidos de casos especiais que ainda falham com a mudança proposta e que é algo que pode ser adicionado posteriormente.

Recomendo que você não adie isso para mais tarde .

Em vez de uma _allowlist_ (caixa especial para executáveis ​​específicos), regras gerais simples podem governar as acomodações , refinadas das acima após mais alguma discussão com @TSlivede .

Estas acomodações, que são _necessárias apenas no Windows_:

Especificamente, são:

  • Assim como no Unix, ProcessStartInfo.ArgumentList é usado por padrão - exceto se _uma ou ambas_ das seguintes condições forem atendidas, _neste caso, a linha de comando do processo deve ser construída manualmente_ (e atribuída a ProcessStartInfo.Arguments as atualmente):

    • Um arquivo em lote ( .cmd , .bat ) ou cmd.exe diretamente é chamado:
    • Nesse caso, _embedded_ " são escapados como "" (ao invés de \" ), e argumentos sem espaço que contêm qualquer um dos seguintes cmd.exe metacaracteres também estão entre aspas (normalmente, apenas os argumentos _com espaços_ são colocados entre aspas): " & | < > ^ , ; - isso fará com que as chamadas para arquivos em lote funcionem de maneira robusta, o que é importante, porque muitas CLIs de alto perfil usam arquivos em lote como _entry pontos_.
    • Independentemente (e possivelmente em adição), se pelo menos um argumento que corresponda ao regex
      '^([/-]\w+[=:]|\w+=)(.*? .*)$' está presente, todos esses argumentos devem aplicar aspas duplas _partial_ em torno da parte do valor _apenas_ (o que segue : ou = )

      • Por exemplo, argumentos de estilo msiexec.exe / msdeploy.exe e cmdkey.exe vistos pelo PowerShell literalmente como
        FOO=bar baz e /foo:bar baz / -foo:bar baz seriam colocados na linha de comando do processo como
        foo="bar baz" ou /foo:"bar baz" / -foo:"bar baz" tornando _qualquer_ CLIs que requerem este estilo de citação feliz.
    • Os caracteres \ literalmente nos argumentos devem ser tratados de acordo com as convenções MS C / C ++.

O que _não_ está coberto por estas acomodações:

  • msiexec.exe (e presumivelmente msdeploye.exe também) suporta _apenas_ "" -escaping de _embedded_ " chars. , que as regras acima _não_ cobririam - exceto se você chamar por meio de um arquivo em lote ou cmd /c .

    • Isso deve ser raro o suficiente para começar (por exemplo,
      msiexec.exe /i example.msi PROPERTY="Nat ""King"" Cole" ), mas provavelmente se tornou ainda mais raro devido ao fato de que misexec invocações são geralmente _ feitas síncronas_ para aguardar o final de uma instalação, caso em que você pode evitar o problema em um dos dois caminhos:
    • cmd /c start /wait msiexec /i example.msi PROPERTY='Nat "King' Cole' - depende de chamadas para cmd.exe (então) desencadeando "" -escaping
    • Start-Process -Wait msiexec '/i example.msi PROPERTY="Nat ""King"" Cole"' - confie no parâmetro -ArgumentList ( -Args ) passando um único argumento de string literalmente como a linha de comando do processo (mesmo que não seja assim que esse parâmetro deve funcionar - consulte # 5576).
  • Quaisquer outros CLIs não convencionais para os quais as acomodações acima não são suficientes - pessoalmente, não tenho conhecimento de nenhum.

No final do dia, há sempre uma solução alternativa: ligue via cmd /c , ou, para aplicativos que não são do console, via Start-Process , ou use --% ; se e quando fornecemos um cmdlet ins ( Invoke-NativeShell ), é outra opção; um dbea ( Debug-ExecutableArguments cmdlet com echoArgs.exe -like habilidades, mas sob demanda também para arquivos em lote, também ajudaria a _diagnose_ problemas.


Quanto ao caminho para uma mudança significativa vs. aceitação:

  • Implementar isso como um recurso experimental significa que, se for demonstrado interesse suficiente, ele se tornará o comportamento _padrão_ e, portanto, representará uma alteração significativa (não trivial)?

  • Você pode garantir que esse recurso experimental seja amplamente divulgado, dada sua importância?

    • Uma preocupação geral que tenho sobre os recursos experimentais é que seu uso muitas vezes pode ser _unwitting_ nas versões de visualização, visto que _todos_ os recursos experimentais estão ativados por padrão. Definitivamente, queremos que as pessoas conheçam e usem esse recurso deliberadamente.
Esta página foi útil?
0 / 5 - 0 avaliações