Powershell: Аргументы для внешних исполняемых файлов экранированы неправильно

Созданный на 21 авг. 2016  ·  170Комментарии  ·  Источник: PowerShell/PowerShell

Действия по воспроизведению

  1. написать программу на C native.exe которая получает ARGV
  2. Запустить native.exe "`"a`""

    Ожидаемое поведение

ARGV [1] == "a"

Фактическое поведение

ARGV [1] == a

Данные окружающей среды

Windows 10 x64

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

Самый полезный комментарий

Создание специального оператора для этого вообще не имеет смысла для оболочки командной строки, поскольку ее основная задача - запускать программы и передавать им аргументы. Представление нового оператора, который выполняет system() для этого задания, похоже на то, как в Matlab вводится способ вызова calc.exe потому что он имеет ошибку в арифметике. Вместо этого следует сделать следующее:

  • Команда pwsh готовится к выпуску нового основного выпуска, в котором исправлены ошибки командной строки, а текущее поведение перемещено за встроенный командлет.
  • В качестве временного решения предстоящая версия pwsh получит встроенный командлет, который использует новое правильное поведение при передаче из командной строки.

То же самое относится к Start-Process . (На самом деле это довольно хороший кандидат на «новый» командлет с некоторыми параметрами, например -QuotingBehavior Legacy ...) См. # 13089.

Все 170 Комментарий

Как вы думаете, почему "\"a\"" ожидаемое поведение? Насколько я понимаю, экранирование PowerShell говорит о том, что реальное поведение является правильным и ожидаемым. " "a "" - это пара кавычек, окружающих экранированную пару кавычек, окружающих a , поэтому PowerShell интерпретирует внешнюю неэкранированную пару как« это строковый аргумент »и поэтому отбрасывает их, а затем интерпретирует экранированную пару как экранированные кавычки и сохраняет их, оставляя вам "a" . Ни разу не было добавлено \ в строку.

Тот факт, что Bash использует \ в качестве escape-символа, не имеет значения. В PowerShell escape-символ - это обратная кавычка. См. Escape- символы PowerShell.

Если вы хотите передать буквально "\"a\"" , я думаю, вы бы использовали:

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

@andschwa
Да, экранирование отлично работает для внутренних командлетов, но все становится странно при взаимодействии с собственными двоичными файлами , особенно в Windows.
При запуске native.exe " "a "" ARGV [1] должен быть

"a"

(три символа)

вместо

a

(один персонаж).

В настоящее время, чтобы заставить native.exe правильно получать ARGV с двумя кавычками и символом a , вы должны использовать этот странный вызов:

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

Ах я вижу. Повторное открытие.

Что произойдет, если вы попробуете построить сборку с # 1639 из-за сильного любопытства?

@andschwa То же самое. Вам НЕОБХОДИМО дважды выполнить esacpe, чтобы удовлетворить как PowerShell, так и CommandLineToArgvW . Эта строка:

native.exe "`"a`""

приводит к StartProcess, равному cmd

native.exe ""a""

@ be5invis @douglaswth это решено через https://github.com/PowerShell/PowerShell/pull/2182?

Нет, нам все равно нужно добавить обратную косую черту перед двойной кавычкой с обратным апострофом? Это не решает проблему двойного выхода. (То есть мы должны избегать двойных кавычек как для PowerShell, так и для CommandLineToArgvW.)

Поскольку " "a "" равно '"a"' , вы предлагаете, чтобы native.exe '"a"' приводило к "\"a\"" ?

Это похоже на запрос функции, реализация которого может нарушить работу большого количества уже существующих сценариев PowerShell, использующих необходимое двойное экранирование, поэтому при любом решении потребуется особая осторожность.

@vors Да.
@douglaswth Двойное экранирование действительно глупо: зачем нам «внутренние» экранирования, созданные в эпоху DOS?

@vors @douglaswth
Это код C, используемый для отображения результатов GetCommandLineW и 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);
}

Вот результат

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

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

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

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

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

@ be5invis Я согласен с вами в том, что двойное экранирование раздражает, но я просто говорю, что изменение этого должно быть обратно совместимо с тем, что используют существующие сценарии PowerShell.

Сколько их? Я не думаю, что есть сценаристы, которые знают о таком двойном цитировании. Это ошибка, а не функция, и она не задокументирована.

???? iPhone

? 2016? 9? 21 ?? 01:58? Дуглас Трифт < [email protected] [email protected] > ???

@ be5i nvish. https://github.com/be5invis. Я не

Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на Gi tHubhttps: //github.com/PowerShell/PowerShell/issues/1995#issuecomment -248381045 или отключите его чтение

PowerShell существует уже 9 лет, поэтому, скорее всего, существует большое количество сценариев. Я нашел много информации о необходимости двойного экранирования из StackOverflow и других источников, когда столкнулся с необходимостью в этом, поэтому я не знаю, согласен ли я с вашими утверждениями о том, что никто не знает о необходимости этого или что это не задокументировано .

В качестве дополнительного контекста я хотел бы немного поговорить о реализации.
PowerShell вызывает .NET API для создания нового процесса, который вызывает Win32 API (в Windows).

Здесь PS создает StartProcessInfo, который использует
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs#L1063

Предоставляемый API принимает одну строку в качестве аргументов, а затем повторно анализирует ее в массив аргументов для выполнения.
Правила этого повторного анализа не контролируются PowerShell. Это Win32 API (и, к счастью, он совместим с ядром dotnet и правилами unix).
В частности, этот контракт описывает поведение \ и " .

Хотя PowerShell может пытаться быть умнее и удобнее, текущее поведение согласуется с cmd и bash: вы можете скопировать из них собственную исполняемую строку и использовать ее в powershell, и она работает так же.

@ be5invis Если вы знаете способ https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md

Это применимо к Windows, но при запуске команд в Linux или Unix странно, что нужно использовать двойные escape-кавычки.

В Linux процессы не имеют единой командной строки, а имеют массив аргументов.
Поэтому аргументы в PowerShell должны быть такими же, как те, которые передаются исполняемому файлу, вместо объединения всех аргументов и последующего разделения.

Даже в окнах текущее поведение несовместимо:
Если аргумент не содержит пробелов, он передается без изменений.
Если аргумент содержит пробелы, если он будет заключен в кавычки, чтобы сохранить его вместе с помощью вызова CommandLineToArgvW . => Аргумент изменен в соответствии с требованием CommandLineToArgvW .
Но если аргумент содержит кавычки, они не экранируются. => Аргумент не изменяется, хотя этого требует CommandLineToArgvW .

Я думаю, что аргументы не следует менять ни в коем случае, либо всегда менять в соответствии с требованиями CommandLineToArgvW , но не в половине случаев.

В отношении нарушения контракта:
Поскольку мне не удалось найти официальную документацию о двойном экранировании, я бы рассмотрел это как категорию «Bucket 2: Reasonable Gray Area», так что есть шансы изменить это, или я ошибаюсь?

@vors Это очень раздражает, если ваш аргумент является переменной или чем-то еще: вам нужно вручную экранировать его перед отправкой в ​​собственное приложение.
Может помочь оператор "автоэкранирования". как ^"a " " -> "a\ " "`

Думаю, @TSlivede поправил несогласованность в поведении.

Я думаю, что аргументы не следует менять ни в коем случае, либо всегда изменять в соответствии с требованиями CommandLineToArgvW, но не в половине случаев.

Я не уверен насчет корзины, но даже корзину с "явно критическими изменениями" потенциально можно изменить. Мы хотим сделать PowerShell лучше, но обратная совместимость - один из наших главных приоритетов. Вот почему это не так просто.
У нас отличное сообщество, и я уверен, что мы сможем прийти к консенсусу.

Кто-нибудь захочет начать процесс RFC?

Стоит исследовать использование P / Invoke вместо .Net для запуска процесса, если это позволяет избежать необходимости добавления кавычек в аргументы PowerShell.

@lzybkr, насколько я могу судить, PInvoke не поможет.
И именно здесь API-интерфейсы unix и Windows отличаются:

https://msdn.microsoft.com/en-us/library/20y988d2.aspx (обрабатывает пробелы как разделители)
https://linux.die.net/man/3/execvp (не обрабатывает пробелы как разделители)

Я не предлагал менять реализацию Windows.

Я бы попытался избежать здесь поведения, зависящего от платформы. Это повредит переносимости скриптов.
Я думаю, что мы можем рассмотреть возможность изменения поведения окон без сбоев. Т.е. с переменной предпочтения. И тогда у нас могут быть разные значения по умолчанию или что-то в этом роде.

Мы говорим о вызове внешних команд - в любом случае, несколько зависящих от платформы.

Что ж, я думаю, что это не может быть действительно платформенно-независимым, поскольку Windows и Linux просто имеют разные способы вызова исполняемых файлов. В Linux процесс получает массив аргументов, в то время как в Windows процесс получает только одну командную строку (одну строку).
(сравните более простые
CreateProcess -> командная строка (https://msdn.microsoft.com/library/windows/desktop/ms682425)
и
execve -> массив команд (https://linux.die.net/man/2/execve)
)

Поскольку Powershell добавляет эти кавычки, когда в аргументах есть пробелы, мне кажется, что PowerShell пытается ** передать аргументы таким образом, что CommandLineToArgvW разбивает командную строку на аргументы, которые были изначально заданы в PowerShell. (Таким образом, типичная c-программа получает те же аргументы в своем массиве argv, что и функция powershell, как $ args.)
Это идеально соответствовало бы простой передаче аргументов системному вызову linux (как предлагается через p / invoke).

** (и терпит неудачу, так как не экранирует кавычки)

PS: Что необходимо для запуска RFC-процесса?

Точно - PowerShell пытается убедиться, что CommandLineToArgvW создает правильную команду и _after_ повторно анализирует то, что PowerShell уже проанализировал.

Это давняя проблема для Windows, и я вижу причины перенести эту проблему на * nix.

Для меня это похоже на деталь реализации, на самом деле не нуждающуюся в RFC. Если мы изменим поведение в Windows PowerShell, это может потребовать RFC, но даже в этом случае правильное изменение может считаться (возможно, рискованным) исправлением ошибки.

Да, я также думаю, что изменение его в Linux на использование прямого системного вызова сделает всех более счастливыми.

Я все еще думаю, что это также следует изменить в окнах,
(Возможно, добавив предпочтительную переменную для тех, кто не хочет менять свои скрипты)
потому что сейчас это просто неправильно - это ошибка. Если бы это было исправлено, прямой системный вызов в linux даже не потребовался бы, потому что любой аргумент дошел бы до следующего процесса без изменений.

Но поскольку есть исполняемые файлы, которые разделяют командную строку способом, несовместимым с CommandLineToArgvW , мне нравится идея @ be5invis об операторе для аргументов, но я бы не стал создавать оператор автоматического выхода (должен быть по умолчанию для всех аргументов), но вместо этого добавьте оператор, чтобы не экранировать аргумент (не добавляйте кавычек, ничего не экранируйте).

Эта проблема возникла у нас только сегодня, когда кто-то попробовал следующую команду в PowerShell и отверг PowerShell, когда она не работала, а CMD - работала:

wmic useraccount where name='username' get sid

Из PSCX echoargs wmic.exe видит следующее:

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

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

Итак, какой API использует CMD.exe для вызова процесса / формирования командной строки? В таком случае, что делает -%, чтобы эта команда работала?

@rkeithhill CreateProcessW . прямой вызов. действительно.

Почему Powershell ведет себя по-разному в этих двух ситуациях? В частности, он непоследовательно заключает аргументы, содержащие пробелы, в двойные кавычки.

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

Кажется, нет ни рифмы, ни повода. В первом случае он заключает мой аргумент в двойные кавычки, но во втором - нет. Мне нужно точно знать, когда он будет и не будет заключен в двойные кавычки, чтобы я мог вручную заключить (или нет) в свой скрипт.

.echoargs.exe создается путем компиляции следующего с 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");
}

РЕДАКТИРОВАТЬ: Вот моя таблица $ PSVersionTable:

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

Поведение относительно котировок менялось несколько раз, поэтому я бы предложил использовать что-то вроде этого:

Изменить: обновленная форма ниже
Старая версия:

# call helper

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

# some test cases

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

Вывод:

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

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

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

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

Первый -replace удваивает обратную косую черту перед кавычками и добавляет одну дополнительную обратную косую черту, чтобы избежать qoute.
Второй -replace удваивает обратную косую черту в конце аргумента, так что закрывающая кавычка не экранируется.

При этом используется --% (PS v3 и выше), что, как AFAIK, является единственным надежным способом передачи кавычек в собственные исполняемые файлы.


Редактировать:

Обновленная версия Run-Native , теперь называется Invoke-NativeCommand (как предлагается )

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

(с некоторой долей вдохновения из

  • не цитирует простые переключатели
  • все еще работает с пустыми аргументами
  • работает, если нет аргументов
  • в основном должен работать с msiexec, cmdkey и т. д.
  • по-прежнему всегда работает для программ, которые следуют общим правилам
  • не использует нестандартные "" как экранированные " - поэтому по-прежнему не будет работать для встроенных кавычек в аргументы .bat или msiexec

Спасибо, я не знал о --% . Есть ли способ сделать это, не передавая переменную среды в собственный процесс? (и для любых процессов, которые он может вызвать)

Есть ли модуль PowerShell, который реализует командлет Run-Native может использовать каждый? Это похоже на то, что должно быть в галерее Powershell. Если бы он был достаточно хорош, он мог бы стать основой для RFC.

"утечка" звучит так, будто вы беспокоитесь о безопасности. Обратите внимание, однако, что командная строка в любом случае видна дочерним процессам. (Например: gwmi win32_process |select name,handle,commandline|Format-Table в Windows и ps -f в Linux)

Если вы все еще хотите избежать использования переменной окружения, вы можете что-то построить, используя выражение-вызов.

Что касается RFC:
Я не думаю, что такой командлет должен быть необходим, вместо этого это должно быть поведение по умолчанию:

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

Я согласен с тем, что поведение PowerShell по умолчанию должно быть исправлено. Я пессимистично предполагал, что это никогда не изменится по причинам обратной совместимости, поэтому я предложил написать модуль. Однако мне очень нравится, как ваш RFC позволяет повторно включить старое поведение экранирования с помощью переменной предпочтения.

Позвольте мне подвести итог дискуссии с правильной долей мнения:

  • Понятно, что у нас есть проблема с обратной совместимостью, поэтому старое поведение должно оставаться доступным.

  • RFC-предложение @TSlivede объясняет это и при этом похвально указывает путь в будущее.
    К сожалению, на момент написания этой статьи его предложение томится как PR_, и еще даже не было принято как RFC _draft_.


Под «будущим» я имею в виду:

  • PowerShell - это самостоятельная оболочка, которая, как мы надеемся, скоро избавится от своего багажа, связанного с cmd.exe , поэтому единственные соображения, которые имеют значение, когда дело доходит до вызова внешних утилит (исполняемых файлов, которые (обычно) консольные / терминальные приложения): :

    • Передаваемые аргументы должны определяться правилами анализа _only_ в режиме аргументов _PowerShell_.

    • Какие бы _литеральные_ результаты этого процесса ни были переданы _ как есть_ целевому исполняемому файлу, как _индивидуальные_ аргументы.

    • Другими словами: все, что вам, как пользователю, когда-либо нужно сосредоточить на том, каким будет результат синтаксического анализа _PowerShell_, и иметь возможность полагаться на то, что этот результат будет передан как есть, а PowerShell позаботится обо всех проблемах. -кодирование сцен - при необходимости.


_ Реализация_ будущего:

  • В _Windows_:

    • По _историческим_ причинам Windows _не_ разрешает передачу аргументов _ в виде массива литералов_ в целевой исполняемый файл; вместо этого требуется _один_строка, кодирующая _все_ аргументы с использованием _псевдоболочки_ синтаксиса. Что еще хуже, это _в конечном итоге - задача отдельного целевого исполняемого файла интерпретировать эту единственную строку_ и разбивать ее на аргументы.

    • Лучшее, что может сделать PowerShell, - это сформировать эту единственную строку - «за кулисами», после выполнения ее _ собственного_ разделения командной строки на отдельные аргументы _предсказуемым стандартизированным способом_.

    • Предложение @TSlivede RFC предлагает именно это, предлагая PowerShell синтезировать командную строку псевдоболочки таким образом, чтобы среда выполнения Windows C / C ++ восстанавливала входные аргументы как есть при выполнении синтаксического анализа :

      • Учитывая, что интерпретация командной строки в конечном итоге зависит от каждого целевого исполняемого файла, нет _гарантии_, что это будет работать во всех случаях, но указанные правила являются наиболее разумным выбором, потому что _ большинство_ существующих утилит используют эти соглашения.

      • Единственными заметными исключениями являются _batch files_, которые могут получить особую обработку, как предполагает предложение RFC.

  • На платформах _Unix_:

    • Строго говоря, проблемы, которые мешают синтаксическому анализу аргументов Windows _необходимости_ никогда_, никогда не возникают_, потому что встроенные в платформу вызовы для создания новых процессов _ принимают аргументы как массивы литералов_ - какие бы аргументы PowerShell ни получил после выполнения своего _ собственного_ синтаксического анализа, следует просто передать _ как есть_ .
      Процитирую @lzybkr : «Я не вижу причин

    • К сожалению, из-за текущих ограничений .NET Core (CoreFX) эти проблемы _do_ вступают в игру, потому что CoreFX API без нужды заставляет анархию аргументов _Windows_, передаваемую также и в мир Unix, требуя использования псевдокомандной строки даже на Unix.

    • Я создал эту проблему с

    • Между тем, учитывая, что CoreFX разбивает псевдокомандную строку обратно на аргументы на основе правил C / C ++, приведенных выше, предложение @TSlivede должно работать и на платформах Unix.

Поскольку https://github.com/PowerShell/PowerShell/issues/4358 был закрыт как дубликат этого, вот краткое изложение этой проблемы:

Если аргумент внешнего исполняемого файла с обратной косой чертой в конце содержит пробел, в настоящее время он наивно цитируется (добавьте кавычки до и после аргумента). Любой исполняемый файл, который следует обычным правилам, интерпретирует это так:
Из комментария @ mklement0 :

Второй " в ".\test 2\" из-за того, что ему предшествует \ интерпретируется как экранированный ", в результате чего оставшаяся часть строки - несмотря на отсутствующее на тот момент закрытие" интерпретируется как часть тот же аргумент.

Пример:
(из комментария @akervinen )

PS X:\scratch> .\ps-args-test.exe '.\test 2\'
Полученный аргумент: .\test 2"

Проблема возникает очень часто, потому что PSReadLine добавляет завершающую обратную косую черту при автозаполнении для каталогов.

Поскольку corefx, кажется, открыт для создания необходимого нам API, я откладываю это до версии 6.1.0. В 6.0.0 я посмотрю, сможем ли мы исправить # 4358

@TSlivede Я взял вашу функцию, переименовал ее в Invoke-NativeCommand (поскольку Run недействителен), добавил псевдоним ^ и опубликовал его как модуль в PowerShellGallery:

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

@ SteveL-MSFT:

Приятно иметь временный промежуток, но менее обременительным было бы - пока мы ждем решения CoreFX - реализовать четко определенные официальные правила цитирования / анализа аргументов, как подробно описано в RFC-предложении @TSlivede предварительно - что не делает Звучит не слишком сложно.

Если мы исправим только проблему \" , передача аргументов по-прежнему будет в корне нарушена, даже в простых сценариях, таких как следующие:

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

Я думаю, что на данный момент существует достаточное согласие относительно того, каким должно быть поведение, поэтому нам не нужен полный процесс RFC, не так ли?

Единственное нерешенное решение - как справиться с проблемами обратной совместимости в _Windows_.

@ mklement0 @ SteveL-MSFT
Мы уже нарушили совместимость?

Единственное нерешенное решение - как справиться с проблемами обратной совместимости в Windows.

Да, но это же самое сложное, правда?

@ be5invis, что вы имеете в виду под «уже

Кроме того, если CoreFX находится на грани исправления в своем слое, я бы предпочел не создавать временный промежуток в нашем слое, прежде чем они это сделают.

И, как кто-то сказал выше в теме, это раздражает, но это также довольно хорошо задокументировано в сообществе. Я не уверен, что в следующих двух релизах мы должны дважды его сломать.

@joeyaiello :

Разве исправление для # 4358 не является критическим изменением для тех, кто работал над решением проблемы, удвоив окончательную \ ; например, "c:\tmp 1\\" ? Другими словами: если вы ограничите количество изменений в этом исправлении, будут гарантированы _два_ критических изменения: одно сейчас, а другое позже, после перехода на будущий CoreFx API; и хотя это тоже могло бы случиться, если бы сейчас была реализована полная временная мера, это маловероятно, учитывая то, что мы знаем об этом грядущем изменении.

И наоборот, это может помешать внедрению в Unix, если распространенные сценарии цитирования, такие как
bash -c 'echo "hi there"' не работают должным образом.

Однако я понимаю, что исправление этого - гораздо более серьезное изменение.

@ PowerShell / powershell-Committee обсудили это и согласились с тем, что минимально использование --% должно иметь то же поведение, что и bash, в том смысле, что кавычки экранируются, чтобы их принимала собственная команда. Что все еще открыто для обсуждения, так это то, должно ли это быть поведением по умолчанию без использования --%

Заметка:

  • Я предполагаю, что вызов фактического исполняемого файла оболочки необходим при использовании --% в _Unix_, в отличие от попытки _эмулировать_ поведение оболочки, как это происходит в _Windows_. Эмулировать в Windows несложно, но в Unix было бы намного сложнее, учитывая многие другие функции, которые необходимо эмулировать.

  • Затем при использовании реальной оболочки возникает вопрос, какую оболочку использовать: хотя bash является повсеместным, его поведение по умолчанию не соответствует POSIX и не требуется POSIX, поэтому для переносимости другие языки сценариев обращаются к /bin/sh , исполняемый файл оболочки, установленный POSIX (который _может быть Bash, работающим в режиме совместимости (например, в macOS), но определенно не обязательно (например, Dash в Ubuntu)).

Возможно, мы также должны нацеливаться на /bin/sh - что, однако, означает, что некоторые функции Bash - в частности, расширение скобок, некоторые автоматические переменные, ... - будут недоступны.


Использование --%

Я буду использовать команду echoargs --% 'echo "hi there"' в качестве примера ниже.

то же поведение, что и bash, в том, что кавычки экранируются, чтобы их принимала собственная команда.

Способ сделать _в будущем, когда API CoreFX будет расширен_, будет заключаться в том, чтобы не выполнять экранирования вообще, а вместо этого делать следующее:

  • Создайте процесс следующим образом:

    • /bin/sh как исполняемый файл, (эффективно) назначенный ProcessStartInfo.FileName .

    • Следующий массив _literal_, _individual_ токенов аргументов как ProcessStartInfo.ArgumentList :

    • -c в качестве 1-го аргумента

    • echoargs 'echo "hi there"' в качестве 2-го аргумента - т. Е. Исходная командная строка использовалась _ буквально_, точно так, как указано, за исключением того, что --% был удален.

Фактически, командная строка передается через _as-is_ исполняемому файлу оболочки, который затем может выполнять _its_ синтаксический анализ.

Я понимаю, что при нынешнем отсутствии способа передачи буквальных аргументов на основе массивов нам необходимо объединить -c и echoargs 'echo "hi there"' в _симметричную_ строку _с экранированием_, к сожалению _ исключительно в интересах CoreFX API_, который, когда приходит время создать фактический процесс, затем _ отменяет_ этот шаг и разбивает одну строку обратно на буквальные токены - и обеспечение того, чтобы это изменение всегда приводило к исходному списку буквальных токенов, является сложной задачей.

Опять же: единственная причина для побега здесь вообще связана с текущим ограничением CoreFX.
Для работы с этим ограничением, следующий сингл, избегали строка , следовательно , должны быть назначены на .Arguments свойство ProcessStartInfo , например, с маскирование выполняется , как указано Аргументы Синтаксический C ++ командной строки :

  • /bin/sh как исполняемый файл, (эффективно) назначенный ProcessStartInfo.FileName .
  • Следующая одиночная экранированная строка как значение ProcessStartInfo.Arguments :
    -c "echoargs 'echo \"hi there\"'"

## Поведение по умолчанию

Что все еще открыто для обсуждения, так это то, должно ли это быть поведением по умолчанию без использования -%

Поведение по умолчанию в Unix должно сильно отличаться:

  • Никакие другие соображения, кроме собственных возможностей PowerShell, никогда не должны вступать в игру (кроме _Windows_, где, к сожалению, этого нельзя избежать; но там правила MS C ++ - это путь, _должны применяться за кулисами_; в противном случае --% обеспечивает аварийный люк).

  • Независимо от того, какие аргументы получает _PowerShell_ после его собственного синтаксического анализа, он должен быть передан как _массив литералов_ через следующее свойство ProcessStartInfo.ArgumentList .

Применяется к примеру без --% : echoargs 'echo "hi there"' :

  • PowerShell выполняет свой обычный синтаксический анализ и получает следующие 2 аргумента:

    • echoargs
    • echo "hi there" (одинарные кавычки - которые имели только синтаксическую функцию для _PowerShell_, удалены)
  • Затем ProcessStartInfo заполняется следующим образом с готовым расширением CoreFX:

    • echoargs как (эффективное) значение свойства .FileName
    • _Literal_ echo "hi there" как единственный элемент, добавляемый к экземпляру Collection<string> предоставленному .ArgumentList .

Опять же, при отсутствии .ArgumentList это не вариант _ еще_, но _ в промежуточном__ можно использовать такое же вспомогательное экранирование, совместимое с MS C ++, как описано выше.

@ SteveL-MSFT
Как я уже упоминал в разделе «Заставить символ остановки анализа (-%) работать в Unix (# 3733), я настоятельно рекомендую не изменять поведение --% .

Если некоторые специальные функции для /bin/sh -c нужны , пожалуйста , используйте другой символ и оставить --% , как она есть!

@TSlivede :

_Если_ что-то --% -_like_ реализовано в Unix - а с нативной подстановкой и, как правило, более опытной командой в Unix, я ощущаю меньшую потребность в этом - затем выбираю _ другой символ_, например --$ - вероятно, имеет смысл (извините, я потерял все аспекты этой долгой дискуссии с множеством вопросов).

Различные символы также будут служить визуально заметным напоминанием о том, что вызывается непереносимое _платформенное_ поведение.

Остается вопрос, что делать PowerShell при обнаружении --% в Unix и --$ в Windows.

Я могу оставить --% как есть. Представление чего-то вроде --$ которое обращается к / bin / sh, и я думаю, cmd.exe в Windows может быть хорошим способом решить эту проблему.

Нет шанса создать командлет для такого поведения?

@iSazonov , вы предлагаете что-то вроде Invoke-Native ? Не уверен, что мне это нравится.

Да, вроде Start-Native .
В шутку :-), а вам не нравятся командлеты в PowerShell?

В Build.psm1 у нас есть Start-NativeExecution со ссылкой на https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/

@ SteveL-MSFT

Я хорошо ухожу -% как есть.

Я думаю, мы все согласны с тем, что --% должен продолжать вести себя так же, как в _Windows_.

В _Unix_, напротив, такое поведение не имеет смысла, как я пытался здесь продемонстрировать - короче:

  • одинарные кавычки обрабатываются неправильно
  • единственный способ ссылаться на переменную среды - _cmd.exe-style_ ( %var% )
  • важные встроенные функции, такие как глобализация и разделение слов, не работают.

Если я правильно понимаю, основной мотивацией для введения --% было включение повторного использования существующих командных строк cmd.exe как есть.

  • Таким образом, --% бесполезен в Unix с его текущим поведением.
  • _Если_ нам нужна _аналогичная_ функция в Unix, она должна быть основана на /bin/sh -c , как предложено выше, возможно, с использованием другого символа.

Я не думаю, что в Windows есть необходимость в функции, основанной на cmd /c , поскольку --% имеет это _почти__ покрытие, возможно, достаточно _хорошо_.
@TSlivede указал, что не все функции оболочки эмулируются, но на практике это не вызывает беспокойства (например, подстановки переменных значений, такие как %envVar:old=new% , не поддерживаются, ^ не является escape-символом, и использование --% ограничено командой _single_ - без использования операторов перенаправления и управления cmd.exe ; при этом я не думаю, что --% когда-либо предназначалась для имитации всей команды _lines_).

Таким образом, что-то вроде --$ - если оно реализовано - будет _counterpart_ Unix к --% .

В любом случае, по крайней мере, раздел помощи about_Parsing заслуживает заметного предупреждения о том, что --% будет бесполезным в Unix во всех, кроме нескольких случаев.

@iSazonov Может иметь смысл иметь Start-Native для обработки некоторых конкретных сценариев, но мы должны попытаться улучшить PowerShell, чтобы использование собственных exes было более естественным и предсказуемым

@ PowerShell / комитет по PowerShell рассмотрел это и согласился с тем, что --% должно означать обработку аргументов так же, как и на соответствующих платформах, что означает, что они ведут себя по-разному в Windows и Linux, но согласованно в Windows и согласованно в Linux. Для пользователя было бы более запутанным вводить новую сигилу. Мы оставим разработку инженеру, как это сделать.

Кросс-платформенные скрипты должны знать об этой разнице в поведении, но вряд ли пользователи это заметят. Если по отзывам пользователей существует потребность в более широком использовании кроссплатформенности, мы можем вернуться к введению новой сигилы.

Второй раз меня укусили различия в парсинге строковых аргументов нативных командлетов и командлетов при использовании

Вот результат вызовов Powershell (echo.exe находится из "C: \ Program Files \ Git \ usrbin \ echo.exe")

Теперь я знаю, что мне следует остерегаться этой причуды " :

> echo.exe '"test'
test

Но эта причуда мне не по плечу ...

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

Во втором случае мне нужно поставить double \ чтобы передать \ собственной команде, в первом случае мне это не нужно 😑

Я понимаю, что это критическое изменение, чтобы изменить такое поведение, поэтому не будет разницы между командлетами и собственными командами. Однако я думаю, что подобные причуды - это то, что отталкивает людей от использования PowerShell в качестве оболочки по умолчанию.

@mpawelski Я только что попробовал это на своем компьютере с Windows с помощью git 2.20.1.vfs.1.1.102.gdb3f8ae, и он не воспроизводит для меня с 6.2-RC.1. Прогнал его несколько раз, и он постоянно повторяет ^\+.+;

@ SteveL-MSFT, я думаю, что @mpawelski случайно дважды указал одну и ту же команду. Вы увидите проблему, если передадите, например, '^\"+.*;' - обратите внимание на часть \" - с - разумным - ожиданием того, что содержимое строки в одинарных кавычках будет передано как есть , так что внешняя целевая программа видит ^\"+.*; как значение аргумента:

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

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

-% был, если я правильно понимаю, для повторного использования существующих командных строк cmd.exe как есть.

Это не совсем так. --% было введено, потому что многие утилиты командной строки Windows принимают аргументы, которые интерпретируются PowerShell, что полностью препятствует вызову утилиты. Это может быстро огорчить людей, если они больше не могут легко использовать свои любимые родные утилиты. Если бы у меня была четверть за каждый раз, когда я отвечал на вопросы о SO и других штатных исполняемых командах RE, которые не работают должным образом из-за этой проблемы, я, вероятно, мог бы пригласить семью на ужин в Qoba. :-) Например, tf.exe позволяет ; как часть указания рабочего пространства. Git позволяет {} и @~1 и т. Д. И т. Д. И т. Д.

--% было добавлено, чтобы указать PowerShell НЕ анализировать остальную часть командной строки - просто отправьте ее "как есть" в собственный исполняемый файл. С одной стороны, разрешите переменные, используя синтаксис cmd env var. Это немного некрасиво, но, черт возьми, это действительно очень удобно в Windows.

RE делает это командлетом, я не уверен, что понимаю, как это работает. --% - это сигнал синтаксическому анализатору, чтобы он заглушил синтаксический анализ до EOL ..

Честно говоря, как давний пользователь PowerShell и этой функции в частности, для меня имеет смысл использовать тот же оператор на других платформах, чтобы просто означать - упростить синтаксический анализ до EOL. Возникает вопрос, как разрешить какую-либо форму замены переменных. Несмотря на то, что это выглядит немного уродливо, в macOS / Linux вы можете взять% envvar% и заменить значение любого соответствующего env var. Тогда его можно будет переносить между платформами.

Дело в том, что если вы этого не сделаете, вы получите условный код - не совсем то, что я бы назвал переносимым:

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

Я бы предпочел эту работу на всех платформах:

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

Поведение в Windows должно оставаться как есть из-за совместимости.

@rkeithhill

Это не совсем так.

В процессе исключения командные строки, предшествующие PowerShell в мире Windows, были написаны для cmd.exe - и это именно то, что --% намеревается эмулировать , о чем свидетельствует следующее:

  • --% расширяет cmd.exe -style %...% ссылки на переменные среды, такие как %USERNAME% (если не задействована оболочка и специальная логика, такие токены будут переданы _verbatim_)

  • прямо из уст PowerShell (если хотите; курсив мой):

В Интернете полно командных строк, написанных для Cmd.exe . Эти строки команд работают достаточно часто в PowerShell, но когда они включают определенные символы, например точку с запятой (;), знак доллара ($) или фигурные скобки, вам необходимо внести некоторые изменения, возможно, добавив кавычки. Казалось, это было источником множества мелких головных болей.

Чтобы помочь решить этот сценарий, мы добавили новый способ «избежать» синтаксического анализа командных строк. Если вы используете волшебный параметр -%, мы останавливаем обычный синтаксический анализ вашей командной строки и переключаемся на что-то более простое. Цитаты не совпадают. Мы не останавливаемся на точке с запятой. Мы не расширяем переменные PowerShell. Мы расширяем переменные среды, если вы используете синтаксис Cmd.exe (например,% TEMP%). Помимо этого, аргументы до конца строки (или канала, если вы используете конвейер) передаются как есть. Вот пример:

Обратите внимание, что этот подход плохо подходит для мира _Unix_ (

  • Подстановка аргументов без кавычек, таких как *.txt , работать не будет.

  • Традиционные (похожие на POSIX) оболочки в мире Unix _не_ используют %...% для ссылки на переменные среды; они ожидают синтаксиса $... .

  • К счастью, в мире Unix нет _raw_ командной строки: любой внешний исполняемый файл должен быть передан _array_ из _literals_, так что PowerShell или CoreFx должны сначала разобрать командную строку на аргументы.

  • Традиционные (похожие на POSIX) оболочки в мире Unix принимают строки '...' (одинарные кавычки), которые --% не распознает - см. # 10831.

Но даже в мире Windows --% имеет серьезные, неочевидные ограничения :

  • Совершенно очевидно, что вы не можете напрямую ссылаться на переменные PowerShell в командной строке, если используете --% ; единственный - громоздкий - обходной путь - временно определить переменные _environment_, на которые вы затем должны ссылаться с помощью %...% .
  • Вы не можете заключить командную строку в (...) - потому что закрывающая ) интерпретируется как буквальная часть командной строки.
  • Вы не можете следовать за командной строкой с помощью ; и другого оператора - потому что ; интерпретируется как буквальная часть командной строки.
  • Вы не можете использовать --% внутри однострочного блока сценария, потому что закрывающий } интерпретируется как буквальная часть командной строки.
  • Вы не можете использовать перенаправления - потому что они рассматриваются как буквальная часть командной строки - однако вы можете использовать cmd --% /c ... > file , чтобы позволить cmd.exe обрабатывать перенаправление.
  • Вы не можете использовать символы продолжения строки - ни PowerShell ( ` ), ни cmd.exe ( ^ ) - они будут рассматриваться как _literals_.

    • --% анализирует только (максимум) до конца строки.


К счастью, у нас уже есть кроссплатформенный синтаксис: собственный синтаксис PowerShell.

Да, использование этого требует, чтобы вы знали, что _PowerShell_ считает метасимволами, что является _superset_ того, что cmd.exe и POSIX-подобные оболочки, такие как Bash, считают метасимволами, но это цена, которую нужно заплатить за более богатую платформу. независимый опыт работы с командной строкой.

Как прискорбно, что PowerShell так плохо обрабатывает цитирование символов " - что является самой темой этой проблемы и кратко описывается в этой проблеме с документами .

@rkeithhill , я начал немного касательно; позвольте мне попытаться закрыть это:

  • --% фактически уже _is_ реализовано в PowerShell Core 6.2.0; например,
    /bin/echo --% %HOME% выводит значение переменной окружения HOME ; напротив,
    /bin/ls --% *.txt не будет работать должным образом, потому что *.txt передается как литерал.

  • В конечном итоге, когда мы не используем --% , нам нужно помочь пользователям диагностировать, как выглядит массив командной строки / аргументов, который _в конечном итоге_ за кулисами_ (что возвращает нас к почтенному # 1761):

    • Ваш полезный echoArgs.exe делает именно это, и в связанной проблеме вы разумно призвали, чтобы такая функциональность была частью самой PowerShell.
    • @ SteveL-MSFT задумался о включении получившейся командной строки в запись об ошибке.

Наконец, чтобы применить мой предыдущий аргумент - используя собственный синтаксис PowerShell в качестве изначально переносимого - к вашему примеру:

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

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

Да, это требует, чтобы вы знали, что начальный символ @ - это метасимвол, который вы, следовательно, должны цитировать, но по мере того, как PowerShell становится все более широко используемым, понимание таких требований также должно стать более распространенным.

FYI, эта проблема должна быть почти одинаковой в Windows и Unix-подобных. В реализации Unix SDProcess в CoreFx есть вещь под названием ParseArgumentsIntoList , которая реализует CommandLineToArgvW почти без переключателей _setargv (и с недокументированным "" в кавычках → " функция). Unix не должен быть дополнительной проблемой, потому что в текущей форме он сломан так же, как и Windows.

_setargv - это не то, что в конце концов используется в каждой программе, и, вероятно, не стоит его рассматривать, потому что, ну, это как бы сказывается на поведенческих изменениях среди версий CRT . Лучшее, что мы можем и должны сделать, - это заключить все в двойные кавычки, добавить несколько красивых обратных слогов и все.

Другой пример, когда аргументы анализируются некорректно:

az "myargs&b"

В этом случае az получает myargs и b пытается выполнить как новую команду.

Обходной путь: az -% "myargs & b"

@ SteveL-MSFT, мы можем удалить метку Waiting - DotNetCore , учитывая, что необходимая функция - свойство ProcessStartInfo.ArgumentList на основе коллекции доступно с .NET Core 2.1.

@TSlivede знает о новом методе и планирует его использовать, но связанный с ним RFC, https://github.com/PowerShell/PowerShell-RFC/pull/90 , к сожалению, не работает.

Предлагаю продолжить обсуждение _ реализации_ там.

Тем не менее, в обсуждении RFC @joeyaiello говорит о том, что изменения являются экспериментальной функцией, но мне становится все более очевидным, что вы не можете исправить поведение цитирования без массового нарушения существующего кода:

Всем, кому приходилось _работать_:

  • невозможность передать аргумент пустой строки ( foo.exe "" настоящее время передает _no_ аргументов)
  • неожиданное эффективное удаление двойных кавычек из-за отсутствия автоматического экранирования встроенных двойных кавычек ( foo.exe '{ "foo": "bar" }' передается как неправильно экранированный "{ "foo": "bar" }" )
  • особенности некоторых интерфейсов командной строки, таких как msiexec которые не принимают определенные аргументы, заключенные в двойные кавычки _ как целое_ ( foo.exe foo="bar none" передается как "foo=bar none" ).
    Примечание. Здесь виноват msiexec , и после применения предложенных изменений для передачи требуемой формы цитирования foo="bar none" потребуется --% .

возникнут проблемы, потому что обходные пути _ползут_ с применением предложенных изменений.

Поэтому дополнительный вопрос:

  • Как мы можем сделать правильное поведение доступным хотя бы как функцию _opt-in_?

    • В случае неработающего цитирования с помощью Start-Process , у нас, по крайней мере, есть возможность ввести новый параметр для правильного поведения , но с прямым вызовом, который явно не вариант (если мы не введем другой «волшебный символ», например как --% ).
  • Фундаментальная проблема с такими механизмами - обычно по переменной предпочтения, но все чаще и с помощью операторов using - это динамическое определение области действия PowerShell; то есть, поведение по умолчанию будет применяться к коду, который называется _из_ выбранного кода, что проблематично.

    • Возможно, настало время для общего представления _lexical_ scoping функций, обобщения предложения using strict с лексической областью видимости в - столь же томном - лексическом RFC строгого режима, автором которого является @lzybkr.

    • Что-то вроде лексической области видимости using preference ProperArgumentQuoting ? (Название, очевидно, подлежит обсуждению, но я изо всех сил пытаюсь его придумать).

Учитывая все эти предостережения и то, что это проблема и с прямым вызовом, когда мы не можем просто добавить новый параметр, я твердо выступаю за нарушение старого поведения.

Да, наверное, сильно сломается. Но на самом деле только потому, что он изначально был так основательно сломан. Я не думаю, что особенно целесообразно уделять приоритетное внимание поддержанию того, что составляет огромную кучу обходных путей для неправильного поведения, вместо наличия функции, которая _ фактически работает_.

Как новая мажорная версия, я думаю, что v7 - это действительно единственный шанс, что мы сможем исправить эту ситуацию должным образом в течение некоторого времени, и мы должны воспользоваться этой возможностью. Если мы проинформируем пользователей, я не думаю, что в целом переход будет воспринят плохо.

Если мы чувствуем, что критическое изменение неизбежно, то, возможно, нам следует разработать идеальное решение, принимая во внимание, что его должно быть легко распечатать в интерактивном сеансе, и есть возможность иметь версию сценария, которая лучше работает на всех платформах.

Согласен, @ vexx32 : сохранение существующего поведения, даже если оно используется по умолчанию, останется постоянной проблемой. Пользователи не ожидают, что им понадобится согласие, и, как только они это сделают, они, скорее всего, либо забудут время от времени, либо возмутятся необходимостью применять его каждый раз.

Оболочка, которая ненадежно передает аргументы внешним программам, не выполняет одно из своих основных требований.

У вас, безусловно, есть _my_ голос за внесение критического изменения, но я боюсь, что другие будут думать иначе, не в последнюю очередь потому, что версия 7 рекламируется как позволяющая долгосрочным пользователям WinPS перейти на PSCore.


@iSazonov : https://github.com/PowerShell/PowerShell-RFC/pull/90 описывает правильное решение.

Чтобы повторить его дух:

PowerShell, как оболочка, должен анализировать аргументы в соответствии с _ своими_ правилами, а затем передавать полученные расширенные значения аргументов _verbatim_ целевой программе - пользователям никогда не нужно думать о том, как PowerShell делает это; все, о чем им следует когда-либо беспокоиться, - это правильность синтаксиса _PowerShell_.

  • На Unix-подобных платформах ProcessStartInfo.ArgumentList теперь дает нам возможность идеально реализовать это, учитывая, что _array_ расширенных значений аргументов может быть передан _as-is_ целевой программе, потому что именно так передача аргументов - разумно - работает в этом мире.

  • В Windows нам приходится иметь дело с прискорбной реальностью анархии, которая представляет собой синтаксический анализ командной строки Windows , но, как оболочка, нам надлежит _просто заставить ее работать за кулисами, насколько это возможно_, что и является тем, что RFC описывает - хотя я только что обнаружил морщину, которая делает использование только ProcessStartInfo.ArgumentList недостаточно хорошо, к сожалению (из-за широкого распространения _batch files_ в качестве точек входа CLI, как продемонстрировано @ SteveL-MSFT az[.cmd] пример выше). Для тех крайних случаев, когда делать разумные вещи недостаточно, есть --% .

Возможно, PSSA может помочь смягчить критическое изменение, предупредив пользователей, что они используют формат аргумента, который будет изменен.

Я думаю, нам нужно подумать о принятии чего-то вроде дополнительных функций, чтобы продвигаться вперед по этому критическому изменению вместе с некоторыми другими .

Есть ли какая-то ценность в сохранении существующего поведения? Я имею в виду, кроме обратной совместимости.

Я не думаю, что для этого стоит поддерживать два пути кода, просто чтобы сохранить сломанную реализацию, потому что время от времени может понадобиться некоторый старый код. Я не думаю, что ожидать, что folx будет время от времени обновлять свой код, неразумно. 😅

Вдвойне, если мы ожидаем, что PS7 станет заменой WinPS; единственная причина, по которой я вижу, чтобы сохранить поведение для v7, заключается в том, что мы ожидаем, что люди будут использовать один и тот же сценарий для запуска команд как на 5.1, так и на 7, что (надеюсь) должно быть довольно редким случаем, если PS7 является хорошей заменой 5.1.

И даже тогда пользователям не составит труда учесть и то, и другое. Если мы не меняем фактический синтаксис языка, сделать что-то вроде этого должно быть довольно легко:

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

Если мы проинформируем пользователей о разнице, я думаю, это будет долгожданное облегчение от боли, связанной с обработкой странных нативных исполняемых файлов в PS до сих пор. 😄

Как упоминал @TylerLeonhardt при обсуждении дополнительных функций, реализация этого означает, что теперь вы поддерживаете _множество_ различных реализаций, каждую из которых необходимо поддерживать и тестировать, а также поддерживать и тестировать фреймворк дополнительных функций. На самом деле, похоже, это не стоит того, tbh.

Обратная совместимость https://github.com/PowerShell/PowerShell/issues/1761 и https://github.com/PowerShell/PowerShell/issues/10675. «Исправление» взаимодействия с собственными командами - это то, что я хотел бы решить для vNext. Так что, если кто-то увидит какие-либо существующие или новые проблемы в этой категории, отправьте мне копию и я помечу их соответствующим образом (или, если у вас есть разрешение на сортировку, пометьте их, как другие).

Дополнительные функции - это модули :-) Наличие дополнительных функций в Engine - большая головная боль для поддержки, особенно для поддержки Windows. Мы могли бы модулировать Engine, уменьшив внутренние зависимости и заменив внутренние API общедоступными - после этого мы могли бы легко реализовать дополнительные функции в Engine.

@iSazonov одна из вещей, над

Какое решение здесь рекомендуется для конечных пользователей?

Это надумано, но это наиболее простой способ получить правильную обработку * ArgumentList из самого .NET framework:

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

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

Конечно, вы можете сделать его менее хитрым для использования здесь, используя позиционные параметры и некоторые хитрости get-command .

* ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: не существует единственно правильного способа разобрать командную строку в Windows. Под «правильным» я подразумеваю стиль MSVCRT преобразования cmdline из / в argv, реализованный .NET на всех платформах для обработки ArgumentList, обработки main (string[] args) и внешних вызовов порождения в Unix. Этот образец предоставляется «КАК ЕСТЬ» без гарантии общей совместимости. См. Также раздел «командная строка Windows» в предлагаемой документации по NodeJS child_process .

@ Artoria2e5 Именно к такому выводу я пришел. System.Diagnostics.Process - единственный надежный способ запустить внешний исполняемый файл, но экранирование аргументов может оказаться сложным из-за правил stdlib:

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

В результате я придумал следующую логику, позволяющую избежать аргументов, заключив их в двойные кавычки " для выполнения процесса:
https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L244

Также может быть сложно получить STDOUT и STDERR в реальном времени, пока выполняется внешний исполняемый файл, поэтому я создал этот пакет

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

что я активно использую в Windows, Linux и Mac и пока без проблем, и я могу передавать аргументы с новой строкой и другими специальными символами в них.

GitHub
Участвуйте в разработке choovick / ps-invoke-externalcommand, создав учетную запись на GitHub.
GitHub
Участвуйте в разработке choovick / ps-invoke-externalcommand, создав учетную запись на GitHub.

@choovick, перенаправление на stdio просто великолепно!

Однако я немного не согласен с экранирующей частью, так как уже есть вещь, которая делает это за вас, под названием ArgumentList. Я понимаю, что это относительно недавнее (?) Добавление, и оно вызывает разочарование, поскольку MS забыла поставить инициализатор String, String [] для SDProcessStartInfo. (Есть ли место для этих… предложений по интерфейсу .NET?)


болтовня

Моя escape-функция из этого примера NodeJS немного отличается от вашей: она использует недокументированный (но найденный в ядре .NET и MSVCRT) "" escape для кавычек. Это как бы упрощает работу по подбору косой черты. Я сделал это главным образом потому, что он использовался для могущественного cmd, который не понимает, что \" не должен отменять кавычки остальной части строки. Вместо того, чтобы бороться с \^" я подумал, что мне будет лучше с чем-то, что тайно использовалось с незапамятных времен.

@ Artoria2e5, к сожалению, ArgumentList недоступен в PowerShell 5.1 для Windows, используя ваш пример, я получаю:

`` Вы не можете вызвать метод для выражения с нулевым значением.
В C: Users \ yser \ dev \ test.ps1: 7 символов: 37

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

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

    • FullyQualifiedErrorId: InvokeMethodOnNull

      ``

Поскольку настраиваемая логика выхода аргументов ....

болтовня

Что касается `\ ^" `в NodeJS, я думаю, что мне пришлось сделать это несколько лет назад :) и я думаю, он заработал

System.Diagnostics.Process - единственный надежный способ запустить внешний исполняемый файл

Проблема в том, что вы не получите интеграции с выходными потоками PowerShell и не получите потокового поведения в конвейере.

Вот краткое изложение необходимых обходных путей, если вы все еще хотите, чтобы PowerShell выполнял вызов (что определенно предпочтительнее) :

  • Если вам нужно передать аргументы с _embedded_ " chars., _Double_ их в Windows, если возможно, или \ -экранировать их:

    • В Windows при вызове _batch files_ и если вы знаете, что целевая программа понимает "" как экранированный " , используйте $arg -replace '"', '""'

      • Использование "" предпочтительнее в Windows (это позволяет избежать проблемы Windows PowerShell и работает с интерфейсами командной строки, которые используют пакетные файлы как _stubs_, такими как Node.js и Azure), но не все исполняемые файлы поддерживают его (особенно Ruby и Perl).
    • В противном случае (всегда в Unix) используйте $arg -replace '"', '\"'

    • Примечание. В _Windows PowerShell_ это все еще не всегда работает должным образом, если значение также содержит _spaces_, потому что присутствие буквального \" в значении ситуативно не вызывает _not_ триггера, заключающего в себе двойные кавычки, в отличие от PowerShell Core; например, передача '3\" of snow' break.

    • Кроме того, перед указанным выше экранированием вы должны удвоить экземпляр \ непосредственно предшествующий " , если они должны рассматриваться как литералы:

      • $arg = $arg -replace '(\\+)"', '$1$1"'
  • Если вам нужно передать аргумент _empty_, передайте '""' .

    • '' -eq $arg ? '""' : $arg (альтернатива WinPS: ($arg, '""')['' -eq $arg]
  • Только Windows PowerShell, не делайте этого в PS Core (где проблема исправлена):

    • Если ваш аргумент _ содержит пробелы_ и _ заканчивается в_ (одном или нескольких) \ , удвойте завершающие экземпляры \ .

      • if ($arg -match ' .*?(\\+)$') { $arg = $arg + $Matches[1] }
  • Если cmd / вызывается командный файл с аргументами, которые _не_ пробелов (поэтому _не_ запускает автоматическое двойное цитирование PowerShell), но содержат любое из &|<>^,; (например, a&b ) используйте _embedded, заключающие в двойные кавычки_, чтобы PowerShell передавал токен в двойных кавычках и, следовательно, не нарушал вызов cmd / пакетного файла:

    • $arg = '"' + $arg + '"'
  • Если вам нужно иметь дело с исполняемыми файлами с плохим поведением, такими как msiexec.exe , заключите аргумент в одинарные кавычки:

    • 'foo="bar none"'

Как уже говорилось, эти обходные пути будут _срок_, как только основная проблема будет исправлена.


Ниже приведена простая (не расширенная) функция iep (для «вызова внешней программы»), которая:

  • Выполняет все описанные выше экранирования, включая автоматическое преобразование в специальный регистр для msiexec и предпочтение экранирования "" перед \" зависимости от целевой программы.

    • Идея состоит в том, что вы можете передать любой аргумент, сосредоточившись только на строковом синтаксисе _PowerShell_, и полагаться на функцию для выполнения необходимого экранирования, чтобы дословное значение, которое видит PowerShell, также было видно целевой программе.

    • В PowerShell _Core_ это должно работать довольно надежно; в Windows PowerShell у вас все еще есть крайние случаи со встроенными двойными кавычками, которые прерываются, если необходимо использовать экранирование \" (как описано выше).

  • Сохраняет синтаксис вызова команд оболочки.

    • Просто добавьте iep  в командную строку.

  • Как и при прямом вызове, он:

    • интегрируется с потоками PowerShell

    • отправляет выходные данные построчно по конвейеру

    • устанавливает $LASTEXITCODE основе кода выхода внешней программы; однако на $? нельзя положиться _не_.

Примечание: функция намеренно минималистична (без объявлений параметров, без помощи командной строки, короткое (нерегулярное) имя), потому что она должна быть максимально ненавязчивой: просто добавьте iep в командную строку и все такое. должно сработать.

Пример вызова с использованием EchoArgs.exe (устанавливается через Chocolatey из сеанса _elevated_ с 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"

Выше показан вывод PowerShell Core. Обратите внимание, как все аргументы были правильно переданы, как дословно видно из PowerShell, включая пустой аргумент.

В Windows PowerShell аргумент 3" of snow не будет передан правильно, поскольку экранирование \" используется из-за вызова неизвестного исполняемого файла (как обсуждалось выше).

Чтобы убедиться, что пакетные файлы правильно передают аргументы, вы можете создать echoargs.cmd в качестве оболочки для echoargs.exe :

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

Вызвать как iep .\echoargs.cmd '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'

Поскольку теперь вызывается командный файл, используется "" -эскапирование, которое устраняет проблему 3" of snow при вызове из Windows PowerShell.

Функция одинаково работает на Unix-подобных платформах, что вы можете проверить, создав сценарий оболочки sh именем echoargs :

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

Вызвать как iep ./echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'


Важно : с тех пор более полная версия этой функции была опубликована как ie ( I nvoke (external) E xecutable). Я только что опубликовал модуль Native , который я рекомендую вам использовать. вместо. Установите модуль с
Install-Module Native -Scope CurrentUser .
Модуль также содержит команду ins ( Invoke-NativeShell ), которая обращается к варианту использования, обсуждаемому в # 13068 - см. Https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -671572939 для подробностей.

Исходный код функции iep (вместо этого используйте модуль Native - см. Выше):

function iep {

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

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

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

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

@ mklement0 Впечатляет, но вот пара, которая у меня не работает в Windows:

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

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

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

вот мой тестовый массив аргументов :)

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

System.Diagnostics.Process действительно имеет ограничения при использовании, поэтому мне пришлось написать свой собственный бегун с возможностью захвата STDOUT STDERR и объединения их в переменные, что достаточно для моих случаев использования, но не идеально.

Изменить: как вы сказали, у него есть крайние случаи в Windows Poweshell 5. Я тестировал Powershell 6, и он отлично работает! к сожалению, мне приходится иметь дело с Poweshell 5, пока 7 не возьмет на себя его в случае Windows ...

Впечатляет, но вот пара, которая у меня не работает в Windows:

Да, эти ограничения применяются к _Windows PowerShell_ с исполняемыми файлами, для которых нельзя предполагать поддержку "" экранирования встроенного " , как подробно описано в моем предыдущем комментарии.
Если вы готовы предполагать поддержку экранирования "" во всех ваших вызовах (_most_, но не все исполняемые файлы в Windows поддерживают его), вы можете легко настроить эту функцию.

И, чтобы подтвердить то, что вы сказали в своей редакции: В PowerShell _Core_:

  • iep echoargs 'somekey="value with spaces"' 'te\" st' отлично работает.
  • Ваш тестовый массив аргументов тоже работает нормально.

Если вы готовы предполагать поддержку экранирования "" во всех ваших вызовах (_most_, но не все исполняемые файлы в Windows поддерживают его), вы можете легко настроить эту функцию.

Спасибо, я попробую в ближайшем будущем. Иногда я имею дело с sqlcmd, и он точно не поддерживает "" . В этом случае - легко предоставить возможность пропустить логику выхода для определенных аргументов.

То, как Windows анализирует аргументы командной строки, можно найти в разделе Анализ аргументов командной строки

Код запуска Microsoft C / C ++ использует следующие правила при интерпретации аргументов, заданных в командной строке операционной системы:

  • Аргументы разделяются пробелом, который является пробелом или табуляцией.

  • Символ каретки (^) не распознается как escape-символ или разделитель. Перед передачей в массив argv в программе символ полностью обрабатывается синтаксическим анализатором командной строки в операционной системе.

  • Строка, заключенная в двойные кавычки (« строка »), интерпретируется как единственный аргумент, независимо от содержащегося внутри пробела. Строка в кавычках может быть встроена в аргумент.

  • Двойная кавычка, перед которой стоит обратная косая черта (\ "), интерпретируется как буквальный символ двойной кавычки (").

  • Обратные косые черты интерпретируются буквально, если они непосредственно не предшествуют двойным кавычкам.

  • Если за четным числом обратных косых черт следует двойная кавычка, одна обратная косая черта помещается в массив argv для каждой пары обратных косых черт, а двойные кавычки интерпретируются как разделитель строк.

  • Если за нечетным числом обратных косых черт следует двойная кавычка, одна обратная косая черта помещается в массив argv для каждой пары обратных косых черт, а двойные кавычки «экранируются» оставшейся обратной косой чертой, вызывая буквальный двойные кавычки (") для размещения в argv .

Это также обсуждается в _exec, _wexec Functions :

Пробелы, встроенные в строки, могут вызвать неожиданное поведение; например, передача _exec строки "hi there" приведет к тому, что новый процесс получит два аргумента: "hi" и "there" . Если бы целью было заставить новый процесс открыть файл с именем «привет там», процесс завершился бы ошибкой. Этого можно избежать, указав строку: "\"hi there\"" .

Как Python вызывает собственные исполняемые файлы

subprocess.Popen Python может правильно обрабатывать экранирование, преобразовывая args в строку способом, описанным в разделе "Преобразование последовательности аргументов в строку в Windows" . Реализация - subprocess.list2cmdline .

Надеюсь, это может пролить свет на то, как PowerShell может справиться с этим более элегантно, вместо использования --% или двойного экранирования (следуя синтаксису PowerShell и CMD). Текущие обходные пути действительно вызывают ошибки у клиентов Azure CLI (который основан на python.exe).

У нас до сих пор нет четкого и лаконичного метода, чтобы понять, как решить эту проблему. Может ли кто-нибудь из команды Powershell пролить свет на то, что это упрощается с помощью v7?

Что ж, хорошая новость заключается в том, что одна из основных задач PS 7.1 - упростить запуск собственных команд. Из сообщения в

Большинство встроенных команд отлично работают в PowerShell, однако в некоторых случаях анализ аргументов не идеален (например, правильная обработка кавычек). Цель состоит в том, чтобы позволить пользователям вырезать образцы командных строк для любого популярного нативного инструмента, вставить его в PowerShell, и он будет работать без необходимости экранирования, специфичного для PowerShell.

Так что, возможно (надеюсь) это будет рассмотрено в 7.1.

Спасибо, @rkeithhill , но нет:

Цель состоит в том, чтобы позволить пользователям вырезать образцы командных строк для любого популярного нативного инструмента, вставить его в PowerShell, и он будет работать без необходимости экранирования, специфичного для PowerShell.

звучит как еще один подход к концептуально проблематичной --% (символ остановки анализа)?

@ SteveL-MSFT, не могли бы вы рассказать нам больше об этой новой функции?


Напомним, предлагаемое здесь исправление состоит в том, что все, на чем вам нужно сосредоточиться, - это удовлетворить синтаксические требования _PowerShell_, и что PowerShell позаботится обо всех экранированиях _ за кулисами_ (в Windows; в Unix это даже больше не нужно, теперь, когда .NET позволяет нам передать массив дословных токенов в целевую программу), но нет никаких сомнений в том, что исправление должно быть добровольным, если необходимо поддерживать обратную совместимость.

С этим исправлением по-прежнему потребуется _некоторые_ возиться с командными строками для других оболочек, но это будет проще - и, по сравнению с --% , вы сохраните всю мощь синтаксиса PowerShell и расширений переменных (выражения с (...) , перенаправления, ...):

  • Вам нужно заменить \" на `" , но _ только внутри "..." _.

  • Вы должны знать, что _no (другая) оболочка_ задействована, поэтому ссылки на переменные среды в стиле Bash, такие как $USER , _не_ будут работать (они будут интерпретироваться как переменные _PowerShell_), если вы не замените их на эквивалентный синтаксис PowerShell, $env:USER .

    • В качестве отступления: --% пытается компенсировать это - в частности _invariably_ - расширением ссылок на переменные среды в cmd.exe -стиле, таких как %USERNAME% , но обратите внимание, что это не только ' t поддержка ссылок в стиле Bash ( $USER ) передается _verbatim_ целевой программе, но также неожиданно расширяет ссылки в стиле cmd.exe на Unix-подобных платформах и не распознает '...' -цитирование.
    • См. Ниже альтернативу, которая _использует_ оболочку соответствующей платформы.
  • Вы должны знать, что PowerShell имеет _дополнительные_ метасимволы, требующие цитирования / экранирования для дословного использования; это (обратите внимание, что @ проблематично только как _first_ char аргумента.):

    • для POSIX-подобных оболочек (например, Bash): @ { } `$ , если вы хотите предотвратить предварительное расширение с помощью PowerShell)
    • для cmd.exe : ( ) @ { } # `
    • Индивидуально ` экранирование таких символов. достаточно (например, printf %s `@list.txt ).

Несколько надуманный пример:

Возьмите следующую командную строку Bash:

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

С предлагаемым исправлением на месте, все , что необходимо , чтобы заменить \" экземпляров внутри "..." -enclosed аргумента с `" :

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

То есть вам не нужно беспокоиться о встроенных " внутри '...' , а внутри "..." вам нужно только избежать их, чтобы сделать _PowerShell_ счастливым (что вы также можете сделать с "" здесь).

Вышеупомянутая функция iep реализует это исправление (как временное решение), так что iep printf '"%s"\n' "3`" of snow" работает по назначению.


Сравните это с текущим некорректным поведением, когда вам нужно перепрыгнуть через следующие обручи, чтобы заставить команду работать, как в Bash (необъяснимо необходимость в _ дополнительном_ раунде экранирования с помощью \ ):

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

После исправления те, кто хочет использовать заданную командную строку _as-is_ через _default shell_ платформы, смогут использовать дословную _here-string_ для перехода к sh -c (или bash -c ) / cmd /c ; например:

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

Обратите внимание, что использование --% здесь _не_ работает ( printf --% '"%s"\n' "3\" of snow" ), а дополнительное преимущество подхода на основе здесь-строки заключается в том, что различные ограничения --% не применяются , в частности невозможность использовать перенаправление вывода ( > ).

Если вы переключитесь на строку, заключенную в двойные_ кавычки ( @"<newline>....<newline>"@ ), вы можете даже встроить переменные и выражения _PowerShell_, в отличие от --% ; однако затем вам нужно убедиться, что расширенные значения не нарушают синтаксис целевой оболочки.

Мы можем подумать о выделенном командлете с кратким псевдонимом (например, Invoke-NativeShell / ins ) для таких вызовов (так что sh -c / cmd /c не нужно быть указанным), но для того, чтобы передавать сложные командные строки как есть, я не думаю, что можно обойтись с помощью здесь-строк:

# 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

Конечно, если вы полагаетесь на функции оболочки, родной для платформы, такие вызовы по определению будут зависеть от [-семейства] платформы - они не будут работать на _обих__ платформах Windows и Unix-подобных.

По этой причине использование PowerShell _alone_ с его _собственным синтаксисом_ предпочтительнее в долгосрочной перспективе : он обеспечивает предсказуемый кроссплатформенный интерфейс для вызова внешних программ - даже если это означает, что вы не можете использовать командные строки, созданные для других оболочек, как: является; по мере роста популярности PowerShell я ожидаю, что боль от обнаружения и знания необходимых модификаций уменьшится, и я ожидаю, что все больше и больше документации будет показывать версии командных строк PowerShell (тоже).

  • Вам нужно заменить \" на `" , но _ только внутри "..." _.

Это не везде работает.

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

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

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

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

Синтаксис Bash:

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

Предложение Powershell:

Если команда PowerShell просто ищет символ, который не нарушает предыдущий синтаксис, почему бы просто не использовать что-то вроде обратных кавычек `для поведения, подобного Bash, которое автоматически экранирует литералы и допускает интерполяцию строк. Это тоже похоже на синтаксис JavaScript.

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

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

почему бы просто не использовать что-то вроде обратных кавычек `для поведения, подобного Bash

Обратные кавычки уже используются для экранирования, с той же целью, что и обратные слэши в оболочке POSIX. Комментарий, на который вы ссылаетесь, уже указывает на это. Мы израсходовали весь код ASCII, похожий на кавычки. Добавление префикса $ к обычным строковым литералам может сработать, но я не думаю, что это имеет достаточно смысла.


О том, как Windows анализирует аргументы командной строки, можно узнать по адресу ...

Проблема в том, что Windows MSVCR не просто делает это: он обрабатывает угловые случаи недокументированными способами . Компонент "" настолько твердо настроен, что они даже помещают его в CoreFX при портировании .NET в Unix. Но в любом случае этого всегда достаточно для побега, по крайней мере, до тех пор, пока кто-нибудь не попросит подброса.

Существует также классическая проблема, когда все делают это по-разному, но нам не нужно об этом беспокоиться, потому что у нас всегда есть .NET для необработанной cmdline.

почему бы просто не использовать что-то вроде обратных кавычек `для поведения, подобного Bash

Обратные кавычки уже используются для экранирования, с той же целью, что и обратные слэши в оболочке POSIX. Мы израсходовали весь код ASCII, похожий на кавычки.

Есть возможность использовать комбинацию символов, если синтаксический анализатор не может обнаружить, что здесь `вводит строку. Что-то вроде '' может даже сработать.

@aminya вот решение, если вы не используете старые Windows Powershell 5.1 и 6+:
https://github.com/PowerShell/PowerShell/issues/1995#issuecomment -562334606

Поскольку мне приходится иметь дело с PowerShell 5,1 в Windows и 6+ в linux / mac, у меня есть собственная реализация, которая годами работает без проблем и позволяет работать с такими инструментами, как kubectl, helm, terraform и другими, передавая сложный JSON. объекты в пределах параметров:
https://github.com/choovick/ps-invoke-externalcommand

GitHub
Участвуйте в разработке choovick / ps-invoke-externalcommand, создав учетную запись на GitHub.

@choovick несколько более короткая реализация была дана выше в этой ветке , мне все еще предстоит найти случай, когда она потерпит неудачу для меня. Это работает в PS, начиная с v3.

@AndrewSav Функция Run-Native в @TSlivede очень умна и лаконична, а также, что похвально, надежно работает в _Windows PowerShell_; Следует отметить несколько моментов: при необходимости дочерний процесс видит aux. commandlineargumentstring переменная среды (вероятно, редко, если вообще проблема на практике), вызовы без аргументов в настоящее время обрабатываются некорректно (легко исправляется и даже не проблема, если вы убедитесь, что вы всегда используете только функцию _с_ аргументами, для чего он предназначен), _все_ аргументы заключаются в двойные кавычки (например, что-то вроде 42 передается как "42" ), что в Windows может (к сожалению) иметь побочные эффекты для программы, которые интерпретируют аргументы в двойных кавычках (или частично в двойных кавычках, как в случае msiexec ) по-разному.

@aminya , единственная причина, по которой node -e 'console.log(`"hey`")' (вроде как) работает, - это текущее неправильное поведение (см. ниже). Я предполагаю, что вы хотели передать _verbatim_ console.log("hey") , который, если PowerShell в Windows правильно экранирует вещи, как предлагается здесь, вы передадите как есть в одинарных кавычках: node -e 'console.log("hey")' . Это должно _автоматически_ переводиться в node -e "console.log(\"hey\")" (двойные кавычки, \ -экранировать дословно " ) _за кулисами_.

Учитывая, насколько длинной стала эта тема, позвольте мне подвести итог:
Вам следует беспокоиться только о синтаксических требованиях _PowerShell_, и задача PowerShell _ в качестве оболочки_ - гарантировать, что _verbatim значения аргументов_, полученные в результате собственного синтаксического анализа PowerShell, передаются внешней программе _as-is_.

  • На Unix-подобных платформах, теперь, когда у нас есть поддержка .NET Core для него, сделать это тривиально, поскольку дословные значения могут быть просто переданы как есть как элементы _array_ аргументов, так программы изначально получают аргументы там. .
  • В Windows внешние программы получают аргументы в виде _ единой строки командной строки_ (прискорбное историческое дизайнерское решение) и должны выполнять свой собственный синтаксический анализ командной строки. Передача нескольких аргументов как части одной строки требует правил цитирования и синтаксического анализа, чтобы правильно разграничить аргументы; хотя это, в конечном итоге, является бесплатным для всех (программы можно анализировать, как им нравится), наиболее широко используемое синтаксическое соглашение (как указано ранее и также предлагается в, к сожалению, теперь заброшенном RFC ) - это то, что реализуют компиляторы Microsoft C / C ++ , так что имеет смысл пойти с этим.

    • _Update_: даже в Windows мы можем использовать свойство collection-of-verbatim-tokens ArgumentList свойства System.Diagnostics.ProcessStartInfo в .NET Core: в Windows оно автоматически преобразуется в правильно заключенную в кавычки и экранированную команду -строчная строка при запуске процесса; однако для _batch-файлов_ нам может потребоваться особая обработка - см. https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -552231174

Внедрение вышеизложенного, несомненно, является серьезным критическим изменением, поэтому, по-видимому, требуется согласие.
Я думаю, что это необходимо, если мы хотим избавиться от всех текущих проблем с цитированием, которые продолжают возникать и мешают внедрению PowerShell, особенно в мире Unix.


Что касается node -e 'console.log(`"hey`")' : внутри '...' , не используйте ` - если вы не хотите, чтобы этот символ передавался как есть. Поскольку PowerShell в настоящее время _не_ экранирует дословные символы " . в вашем аргументе \" за кулисами node видит в командной строке console.log(`"hey`") , который анализируется как два непосредственно смежных строковых литерала: без кавычек console.log(` и "hey`" двойных кавычках. После удаления " , которые имеют функцию _syntactic_ из-за того, что \ -экранированы, выполняемый код JavaScript в конечном итоге будет console.log(`hey`) , и это работает только потому, что `....` закрытый токен - это форма строкового литерала в JavaScript, а именно _template literal_.

@AndrewSav Я тестировал свой сумасшедший тестовый объект, и у меня это сработало! Очень элегантное решение, протестированное на Windows 5.1 и PS 7 на linux. Я согласен с двойными кавычками, я не имею дело с msiexec или sqlcmd известно, явно относятся к " .

Моя личная реализация также имеет простую логику выхода, аналогичную той, которую вы упомянули: https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L278

но я написал кучу кода для отображения и захвата потоков STDOUT и STDERR в реальном времени в этом модуле ... Возможно, это можно значительно упростить, но мне не было необходимости ...

@ mklement0 этот поток никогда не закончится (: нам нужно либо предоставить опубликованный модуль PowerShell в галерее ps, который будет соответствовать большинству

GitHub
Участвуйте в разработке choovick / ps-invoke-externalcommand, создав учетную запись на GitHub.

@ mklement0 этот поток никогда не закончится (: нам нужно либо предоставить опубликованный модуль PowerShell в галерее ps, который будет соответствовать большинству

Если команда PowerShell решит не исправлять это в самой программе, я бы сказал, что оболочка, которая не может запускать внешние программы изначально и правильно, не будет моей оболочкой! Это основные вещи, которых не хватает в PowerShell.

@aminya да, я обнаружил, что эта проблема меня очень раздражает, когда мне приходилось к ней переходить. Но такие функции, как ниже, того стоили.

  • Гибкая структура параметров, легко создавать надежные CMDlet.
  • Модули и внутренние репозитории модулей, которые используют общую логику для всей организации, избегая большого количества дублирования кода и централизации основных функций, которые можно реорганизовать в одном месте.
  • Кросс-платформа. У меня есть люди, использующие мои инструменты в Windows в PS 5.1 и в Linux / Mac в PS 6/7.

Я действительно надеюсь, что команда PS улучшит это в будущем, чтобы сделать его менее запутанным.

Внедрение вышеизложенного, несомненно, является серьезным критическим изменением, поэтому, по-видимому, требуется согласие.

Вы имеете в виду реализацию https://github.com/PowerShell/PowerShell-RFC/pull/90?

@aminya , я согласен с тем, что вызов внешних программ с аргументами является основной

Модуль, предложенный @choovick , по-прежнему имеет смысл для _Windows PowerShell_, которая находится в режиме обслуживания только для критически важных для безопасности исправлений.
Кроме того, если / когда будет написан предлагаемый концептуальный раздел справки о вызове внешних программ - см. Https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152 - вспомогательную функцию, которая исправляет проблемы в более ранних версиях / Windows PowerShell, такие как @TSlivede выше, могут быть размещены там напрямую.

@iSazonov Да, я имел в виду реализацию https://github.com/PowerShell/PowerShell-RFC/pull/90 .

Что касается бесконечной нити и проблем с изменением:

Последним официальным ответом на связанный RFC был этот комментарий @joeyaiello от 8 июля 2019 г. (выделено мной):

но мы думаем, что этот [RFC] имеет много смысла независимо от существующего поведения и без учета его взлома . Теперь, когда у нас есть экспериментальные функции, мы думаем, что вполне разумно пойти и реализовать это сегодня за флагом экспериментальной функции, и мы можем выяснить в дальнейшем, является ли это поведение согласия или отказа , есть ли какой-либо переход путь, и если предпочтительная переменная является правильным механизмом для ее включения и выключения.

_Персонально_, я бы не прочь исправить поведение _по умолчанию_, даже если это критическое изменение; RFC действительно предлагает это и предлагает отказаться, если вы хотите _old_ (сломанное) поведение.

Я подозреваю, что те, у кого есть унаследованный код, будут возражать, однако, поскольку _все существующие обходные пути перестанут работать_ - см. Выше, - и поддержание обратной совместимости по-прежнему кажется всеобъемлющей целью.

Если новое фиксированное поведение сделано по подписке, у вас все еще будет неловкость, связанная с необходимостью делать что-то только для того, чтобы добиться правильного поведения, но, по крайней мере, существующий код не сломается.

Но существующие механизмы отказа сами по себе проблематичны:
Теперь, когда RFC дополнительных функций @KirkMunro был отклонен, в значительной степени

Здесь требуется _Lexical_ объем подписки, которого у нас в настоящее время нет. RFC для _лексической_ области видимости строгого режима предлагает реализацию функции с лексической областью видимости (или другой цели) с помощью оператора using (который, в частности, обычно имеет _ динамическую_ область видимости). Следуя этому шаблону, стоит подумать о выражении using ProperExternalArgumentQuoting с лексической областью видимости (имя - WIP :) - если это технически возможно.

Нам нужно, чтобы комитет PowerShell взвесил (снова) и дал четкие указания относительно дальнейших действий с своевременной обратной связью по вопросам по мере их возникновения. @ SteveL-MSFT?


Обратите внимание, что --% -подобное решение, на которое намекает сообщение в блоге 7.1 (см. Выше ) (которое я лично считаю не заслуживающим внимания - см. Выше и этот комментарий ), будет _separate_ функцией - исправлением собственного PowerShell (без эмуляции) поведение по-прежнему необходимо.

Лично я бы не прочь исправить поведение по умолчанию, даже если это критическое изменение; RFC действительно предлагает это и предлагает отказаться, если вы хотите старое (неработающее) поведение.

Если новое фиксированное поведение сделано по подписке, у вас все еще будет неловкость, связанная с необходимостью делать что-то только для того, чтобы добиться правильного поведения, но, по крайней мере, существующий код не сломается.

Согласен, на самом деле я бы сказал, что имеет меньший смысл иметь сломанное значение по умолчанию, чем правильно реализованное значение по умолчанию. Учитывая тот факт, что текущая реализация на самом деле является ошибкой , новое поведение должно включать согласие, а не отказ, поскольку на самом деле не имеет смысла продолжать поощрять неработающие вызовы внешней оболочки, которые склонны к неожиданному прерыванию. способами. В любом случае, PowerShell 7 должна стремиться к улучшению устаревшей Windows PowerShell.

@ SteveL-MSFT и я договорились, что мы должны закрыть это в пользу # 13068. Все, что мы здесь касаемся, является слишком серьезным изменением, и мы должны решить эту проблему с помощью нового оператора, который служит в качестве режима согласия.

Я абсолютно не понимаю, как # 13068 решит эту проблему: если этот оператор введен так, как задумано, у нас все равно не будет возможности правильно вызвать любой собственный исполняемый файл с заданным массивом аргументов или с некоторыми явными аргументами, содержимое которых происходит из переменных.

Пример, который @JustinGrote привел в этом потоке, в настоящее время не работает надежно (если в полезной нагрузке аргумента возможны встроенные кавычки), и добавление этого оператора не даст никакой альтернативы, которая что-либо улучшает.

@joeyaiello Можете ли вы хотя бы оставить эту проблему открытой до тех пор, пока этот оператор действительно не существует и кто-нибудь не сможет показать, как этот оператор может улучшить что-либо, о чем упоминалось в этой ветке?

А что насчет Linux? Эта проблема является глупой и неожиданной в Windows, но в Linux она имеет еще меньше смысла, тем более, что нет слишком длинной истории сценариев Linux PowerShell, которые могут сломаться.

Создание специального оператора для этого вообще не имеет смысла для оболочки командной строки, поскольку ее основная задача - запускать программы и передавать им аргументы. Представление нового оператора, который выполняет system() для этого задания, похоже на то, как в Matlab вводится способ вызова calc.exe потому что он имеет ошибку в арифметике. Вместо этого следует сделать следующее:

  • Команда pwsh готовится к выпуску нового основного выпуска, в котором исправлены ошибки командной строки, а текущее поведение перемещено за встроенный командлет.
  • В качестве временного решения предстоящая версия pwsh получит встроенный командлет, который использует новое правильное поведение при передаче из командной строки.

То же самое относится к Start-Process . (На самом деле это довольно хороший кандидат на «новый» командлет с некоторыми параметрами, например -QuotingBehavior Legacy ...) См. # 13089.

Почему Powershell ведет себя по-разному в этих двух ситуациях? В частности, он непоследовательно заключает аргументы, содержащие пробелы, в двойные кавычки.

Я получаю стабильные результаты в версии 7. Кажется исправленным.

PING 'A \"B'

Запрос Ping не смог найти хост A "B.

PING 'A\" B'

Запрос Ping не смог найти хост A "B.

Это не исправлено, потому что дословные имена хостов, которые должен видеть ping - это A \"B и A\" B - _с_ символами \ .

PowerShell, как оболочка, должен анализировать аргументы только в соответствии с _ своими_ правилами, а затем прозрачно гарантировать, что целевой процесс видит те же дословные значения, которые были результатом собственного синтаксического анализа PowerShell.

Эти _другие_ оболочки - и те бедные программы, работающие в Windows, которые должны действовать как их собственная оболочка, так сказать, анализируя _командную строку_ только для извлечения отдельных переданных аргументов - используйте \ как выход символ не должен входить в изображение здесь - размещение этого (необходимо только в Windows, в Unix вы просто передаете дословные аргументы непосредственно в виде массива) - это работа PowerShell как оболочки, _ за кулисами_.

Кстати, PowerShell не требует экранирования " _inside '...' _ (строки в одинарных кавычках), как и POSIX-совместимые оболочки, такие как bash : выполняется из bash , например, /bin/echo 'A \"B' (разумно) печатает A \"B ( \ обрабатываются как литералы в строках, заключенных в одинарные кавычки) - это выполнение та же самая команда из PowerShell (неожиданно) дает
A "B - отсутствует \ - это проявление проблем, обсуждаемых здесь.

Я должен уточнить:

  • С точки зрения желания в конечном итоге передать A "B _verbatim_, вы должны иметь возможность использовать 'A "B' из PowerShell.

  • Командная строка, которую PowerShell в настоящее время создает за кулисами, содержит "A "B" - который целевой процесс видит как A B - то есть слепое вложение в "..." , без экранирования _embedded_ " привело к эффективной потере встроенного " . Что PowerShell _должен_ использовать в скрытой командной строке в этом случае, так это "A \"B" то есть для встроенного " требуется \ -экранирование.

  • Точно так же та же слепая оболочка приводит к тому, что 'A \"B' будет представлен как "A \"B" в командной строке за кадром, что как раз так и превращает _embedded_ \" в _escaped_ " символ, который целевой процесс поэтому видит как A "B ; то есть отсутствие _автоматического_ экранирования привело к эффективной потере встроенного \ . PowerShell _должен_ использовать в скрытой командной строке в этом случае "A \\\"B" то есть как \ и " необходимо экранировать.

Это не исправлено, потому что дословные имена хостов, которые должен видеть ping - это A \"B и A\" B - _с_ символами \ .

«Это» относится к процитированной жалобе, которую я, к счастью, не могу воспроизвести.

@ yecril71pl , я вижу: мое (неправильное) предположение заключалось в том, что «непоследовательное обертывание аргументов, содержащих пробелы в двойных кавычках» относится к _ отсутствию автоматического экранирования встроенных символов " и \ , поскольку объяснил в моем предыдущем комментарии - и в этом суть этой проблемы.

В PowerShell Core были внесены незначительные исправления, которых нет в Windows PowerShell; Сейчас я могу думать только об одном:

  • Windows PowerShell использует слепые двойные кавычки в случае, если в конце \ : 'A B\' превращается в (сломанный) "A B\" - PS Core обрабатывает это правильно ( "A B\\" ) .

Учитывая, что это репо предназначено только для PS Core, достаточно сосредоточиться на том, что все еще не работает в PS Core (может быть полезно упомянуть различия _ в стороне_, но лучше сделать этот аспект явным).


И даже сценарий, который вы имели в виду, _is_ все еще не работает в PS Core - но только _ если вы опустите \ из аргумента_:

Передача 'A" B' прежнему приводит к _не_-двойным кавычкам A" B за кулисами (тогда как 'A\" B' приводит к "A\" B" - что, как уже говорилось, тоже не работает - по-другому).

Учитывая, что это репо предназначено только для PS Core, достаточно сосредоточиться на том, что все еще не работает в PS Core (может быть полезно упомянуть различия _ в стороне_, но лучше сделать этот аспект явным).

Мета в сторону: мне полезно знать, что неправильное поведение, упомянутое в комментарии другого пользователя, не применяется. Конечно, мы могли отклонить комментарий просто потому, что пользователь не удосужился проверить текущую версию. Хорошо. Непослушные репортеры, будучи непослушными, все же лучше убедиться. ИМХО.

Здесь нет аргументов, но предоставление _ надлежащего кадрирования и контекста_ для таких _asides_ важно, особенно в длинном потоке, где исходный комментарий, необходимый для контекста, был опубликован давно и фактически теперь _ скрыт_ по умолчанию (это никогда не повредит фактически _ссылка_ на цитируемый исходный комментарий).

Интересно, имеют ли значение эти обсуждения. Очевидно, что решения комитета не зависят от того, чего хочет сообщество. Посмотрите на теги: Resolution- won't fix. Но поскольку PowerShell является открытым исходным кодом (MIT), сообщество может исправить это в отдельной вилке и для краткости называть это PowerShellCommunity ( pwshc ).

Это устраняет необходимость в обратной совместимости. Позже в PowerShell 8 комитет может интегрировать вилку.

Об обратной совместимости: PowerShell не предустановлен ни в одной операционной системе, и для меня трудности с установкой PowerShellCommunity такие же, как и PowerShell . Я предпочитаю установить версию сообщества и использовать ее сразу, вместо того, чтобы ждать какой-то будущей версии 8 (или усложнять код с помощью нового оператора).

Поскольку комитет решил оставить все сломанными, лучше знать, насколько они сломаны. Я думаю, что сообщество может жить с Invoke-Native который поступает правильно. Спасать лицо Microsoft против их воли - не задача сообщества.

лучше знать, насколько они сломаны

Я полностью согласен - даже если проблема не может быть решена в данный момент, знание того, как делать вещи правильно _в принципе, важно, если и когда придет время_, даже если это время никогда не наступает в контексте данного языка.

сообщество может жить с Invoke-Native который поступает правильно

Чтобы было ясно:

  • Что-то вроде Invoke-NativeShell обращается к _различному варианту использования_, который является предметом # 13068, и для этого варианта использования такой командлет _не_ временное решение: это правильное решение - см. Https://github.com/ PowerShell / PowerShell / issues / 13068 # issuecomment -656781439

  • _Эта проблема касается исправления самого _PowerShell_ (речь идет не о _native_ функциональности платформы), а решение _stopgap_ состоит в том, чтобы обеспечить простую _opt-in_ - отсюда и предложение отправить функцию iep как встроенную -in в ожидании исправления _proper_ в будущей версии, которое может существенно нарушить обратную совместимость.

Сохранение лица Microsoft - не задача сообщества

Я не думаю, что @aminya заботится о спасении чьего-либо лица - речь идет об исправлении принципиально неправильного поведения в области, которая является основной задачей оболочки.

Тем не менее, я не уверен, что фрагментация экосистемы PowerShell с помощью вилки - правильный путь.

У меня нет времени отвечать на все это прямо сейчас, но я думаю, что это разумно, поэтому я снова открываю:

Можете ли вы хотя бы оставить эту проблему открытой до тех пор, пока этот оператор действительно не существует и кто-нибудь не сможет показать, как этот оператор может улучшить что-либо, о чем упоминалось в этой ветке?

Как я сказал в связанной проблеме, собственный оператор вызова усложняет UX, и это неправильное направление.
В то же время вся дискуссия здесь идет об упрощении взаимодействия с собственными приложениями.

Единственное, что нас останавливает, - это резкое изменение. Мы много раз говорили, что это слишком много разрушения, но давайте взвесим это.

Давайте посмотрим на интерактивную сессию. Пользователь установит новую версию PowerShell и обнаружит новое поведение при вызове собственных приложений. Что он скажет? Я предполагаю, что он скажет: «Спасибо, наконец-то я могу просто печатать, и это работает!».

Давайте посмотрим на сценарий исполнения / хостинга скрипта. Любая новая версия приложения (даже с одним незначительным изменением!) Может нарушить бизнес-процесс. Мы всегда этого ожидаем и проверяем наши процессы после любых обновлений. Если мы обнаружим проблему, то у нас есть несколько способов:

  • откатить обновление
  • подготовить быстрое исправление для скрипта
  • отключите функцию, которая ломает наш сценарий, пока эти вещи не будут исправлены или они сами не умрут со временем.

_Поскольку обновления версий всегда что-то ломают, последний вариант - лучшее, что мы могли иметь и принять.

(Я хочу отметить, что сейчас PowerShell Core не является компонентом Windows и, следовательно, не может напрямую портить хостинг-приложения, напрямую затрагиваются только сценарии.)

Я хочу напомнить вам, что сказал Джейсон выше - это исправление ошибки.
Давайте исправим и все упростим для всех. Пусть пользователи работают powershell-y .

Как я сказал в связанной проблеме, собственный оператор вызова добавляет сложности в UX, и это неправильное направление.

сложность ❓

Давайте посмотрим на интерактивную сессию. Пользователь установит новую версию PowerShell и обнаружит новое поведение при вызове собственных приложений. Что он скажет? Я предполагаю, что он скажет: «Спасибо, наконец-то я могу просто печатать, и это работает!».

У меня другой сценарий: новые пользователи пробуют PowerShell, понимают, что он загадочно не работает, перемещают его в корзину и больше не возвращаются. Я бы так и поступил.

подготовить быстрое исправление для скрипта

Это то, что мы должны сделать, чтобы охватить очевидные случаи в пользовательских скриптах.

понять, что это загадочно не удается

Вся дискуссия здесь как раз об избавлении от этой «загадочной неудачи». И лучше всего делать это, упрощая, но не добавляя новых загадочных вещей.

Вся дискуссия здесь как раз об избавлении от этого. И лучше всего делать это, упрощая, но не добавляя новых загадочных вещей.

Мы не хотим избавляться от возможности напрямую вызывать исполняемые программы.

Старый вызов также не направлен непосредственно на cmdline с его предположением о том, цитируется ли что-либо. Кроме того, указанная способность будет сохранена с -%.

Кроме того, указанная способность будет сохранена с -%.

--% требует предопределенной командной строки, поэтому его полезность ограничена.

@ yecril71pl

Мы не хотим избавляться от возможности напрямую вызывать исполняемые программы.

Я думаю, что @iSazonov означает избавление от _исправного поведения_, то есть исправление ошибки должным образом (без подписки через дополнительный синтаксис), даже если это означает нарушение существующих обходных путей. Правильно, @iSazonov?

@ Artoria2e5 :

Старый вызов также не является прямым для cmdline с его предположением о том, что что-то уже цитируется

[_Update_: Я неправильно прочитал цитируемую строку, но, надеюсь, информация все еще представляет интерес.]

  • Вам не нужно гадать, и правило простое:

    • _Только если_ имя вашей команды или путь _вотмечены_ - '/foo bar/someutil '- и / или содержат _переменные ссылки (или выражения) - $HOME/someutil - & _required_.
  • Хотя вы можете счесть эту потребность неудачной, она лежит в основе языка PowerShell и обусловлена ​​необходимостью синтаксического различия между двумя основными режимами синтаксического анализа, режимом аргумента и режимом выражения.

    • Обратите внимание, что проблема не связана с вызовом _внешних программ_; собственные команды PowerShell также требуют & для вызова, если они указаны через ссылку на строку / переменную в кавычках.
  • Если вы не хотите запоминать это простое правило, самое простое правило: _ всегда_ используйте & , и все будет в порядке - это несколько менее удобно, чем _не_ ничего_ не нужно, но не совсем трудности (и короче чем --% , что является абсолютно неправильным решением - см. ниже)

указанная способность будет сохранена при -%.

[_Update_: Я неправильно прочитал цитируемую строку, но, надеюсь, информация все еще представляет интерес.]

Нет, несмотря на неудачное сочетание # 13068 с _ этой_ проблемой, --% - возможность вызывать _ родную оболочку_, используя _its_, неизменно _платформенный_ синтаксис - никоим образом не является обходным путем для решения данной проблемы и обсуждения это как таковое только добавляет путаницы .

--% - это совершенно другой вариант использования и, как предлагается в настоящее время, имеет серьезные ограничения; Если есть что-то _не_ заслуживающее введения _оператора_ (или изменения поведения существующего), то это возможность передать дословную командную строку собственной оболочке (что, конечно, вы уже можете сделать самостоятельно, с помощью sh -c '...' в Unix и cmd /c '...' , _ но только надежно, если эта проблема устранена_; двоичная реализация командлета Invoke-NativeShell / ins , в основном абстрагируясь от деталей целевой оболочки Синтаксис CLI позволит избежать рассматриваемой проблемы (путем прямого использования System.Diagnostics.ProcessStartInfo.ArgumentList ) и, следовательно, может быть реализован независимо).

@ yecril71pl

У меня другой сценарий: новые пользователи пробуют PowerShell, понимают, что он загадочно не работает, перемещают его в корзину и больше не возвращаются. Я бы так и поступил.

Не могли бы вы пояснить: боитесь ли вы, что текущее поведение PowerShell может привести к такому неприятному опыту пользователя, или вы опасаетесь, что предлагаемое изменение приведет к такому пользовательскому опыту.

Потому что, на мой взгляд, текущее поведение PowerShell с большей вероятностью вызовет такой опыт. Как сказано выше: текущее поведение powershell следует рассматривать как ошибку.

Зачем новому пользователю, не зная о том, что PowerShell сломан, выдавать команды с искаженными аргументами?

@ yecril71pl Просто чтобы быть абсолютно уверенным: вы считаете, что

Я считаю, что ваш вопрос исходит из вашего предположения, что я неизлечимый ботаник. Это правильно, но я сохранил способность представить, что нормальный случайный парень посчитал бы искаженным.

@ mklement0
Я думаю, что @ Artoria2e5 говорил о преобразовании массива аргументов в одну строку lpCommandLine когда говорил

Старый вызов также не является прямым для cmdline с его предположением о том, что что-то уже цитируется

Потому что при звонке

echoargs.exe 'some"complicated_argument'

вам действительно нужно более или менее догадываться, добавляет ли PowerShell кавычки около some"complicated_argument .

Пример 1: В большинстве версий PowerShell
echoarg.exe 'a\" b' и echoarg.exe '"a\" b"' будут переведены на
"C:\path\to\echoarg.exe" "a\" b" (проверены версии 2.0; 4.0; 6.0.0-alpha.15; 7.0.1)
но мой PowerShell по умолчанию на Win10 (версия 5.1.18362.752) переводит
echoarg.exe 'a\" b' до "C:\path\to\echoarg.exe" a\" b и
echoarg.exe '"a\" b"' на "C:\path\to\echoarg.exe" ""a\" b"" .

Пример 2: перевод старых версий PowerShell
echoarg.exe 'a"b c"' до "C:\path\to\echoarg.exe" "a"b c"" (проверены версии 2.0; 4.0;)
в то время как более новые версии переводят
От echoarg.exe 'a"b c"' до "C:\path\to\echoarg.exe" a"b c" (проверены версии 5.1.18362.752; 6.0.0-alpha.15; 7.0.1).


Поскольку поведение явно уже менялось несколько раз, я не понимаю, почему его нельзя изменить еще раз, чтобы получить ожидаемое поведение.

Понятно ,

Что касается реальной проблемы: мы на 100% согласны - на самом деле, вам даже не нужно думать о том, что lpCommandLine конечном итоге используется за кулисами в Windows; если бы PowerShell поступил правильно, никому бы не пришлось (кроме крайних случаев, но они не являются ошибкой PowerShell, и тогда --% (как в настоящее время реализовано) может помочь; при правильном исправлении никогда не будет быть крайними случаями на Unix-подобных платформах).

Что касается простого решения проблемы должным образом: вы, безусловно, имеете мой голос (но существующие обходные пути не работают).

TL; DR: предположение, что мы можем надежно передать любое значение в качестве аргумента любой программе в подсистеме Microsoft Windows NT, неверно , поэтому мы должны перестать притворяться, что это наша цель. Однако есть еще много чего спасти, если мы рассмотрим масштабы аргументации.

При вызове собственных исполняемых файлов Windows мы должны сохранить исходное цитирование. Пример:

CMD /CSTART="WINDOW TITLE"

Система не может найти файл WINDOW.

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

StringConstantType
BareWord
Значение
/ CSTART = НАЗВАНИЕ ОКНА
StaticType
System.String
Степень
/ CSTART = "НАЗВАНИЕ ОКНА"
Родитель
CMD / CSTART = "НАЗВАНИЕ ОКНА"

Если мы возьмем экстент в качестве шаблона, мы ничего не потеряем и сможем вызвать собственный исполняемый файл, как и ожидалось. Здесь работает обходной путь использования строкового аргумента, но я не думаю, что это строго технически необходимо, при условии, что надлежащая поддержка реализована в PowerShell. Такой подход подойдет для всех случаев.

Кавычки внутри кавычек представляют собой непреодолимую проблему, потому что есть инструменты, которые интерпретируют экранирование обратной косой черты ( TASKLIST "\\\"PROGRAM FILES" ), и инструменты, которые этого не делают ( DIR "\""PROGRAM FILES" /B ), и инструменты, которые не беспокоят ( TITLE A " B ). Однако, если бы нам пришлось уйти, стандартный escape-символ с обратной косой чертой отравляет все обычные инструменты управления файлами, потому что они просто не поддерживают кавычки вообще, а двойные обратные косые черты \\ означают для них нечто совершенно иное (попробуйте DIR "\\\"PROGRAM FILES" /B ), поэтому отправка аргумента с кавычками внутри должна быть ошибкой во время выполнения. Но мы не можем выдать ошибку, потому что не знаем, какая из них какая. Хотя использование обычного механизма экранирования не должно причинить вред аргументам, не содержащим кавычек, мы не можем быть уверены, что при применении к аргументам, которые их содержат и переданы в инструмент, который не поддерживает кавычки в качестве значений, он будет обязательно вызовет ошибку прерывания, а не неожиданное поведение, и неожиданное поведение действительно будет очень плохим. Это серьезное бремя, которое мы возлагаем на пользователя. Кроме того, мы никогда не сможем предоставить инструменты «безразлично» ( CMD /CECHO='A " B' ).

Обратите внимание, что переменные среды не представляют значения в CMD , они представляют собой фрагменты кода, которые повторно анализируются при раскрытии переменных среды, и нет никаких условий для надежной обработки их в качестве аргументов для других команд. CMD просто не работает ни с какими объектами, даже со строками, что, по-видимому, является основной причиной настоящей головоломки.

TL; DR: предположение, что мы можем надежно передать любое значение в качестве аргумента любой программе в подсистеме Microsoft Windows NT, _ неверно_, поэтому мы должны перестать притворяться, что это наша цель.

Это должно быть целью, не так ли? Это не проблема PowerShell, если программа не может интерпретировать полученные ею аргументы.

При вызове собственных исполняемых файлов Windows мы должны сохранить исходное цитирование. Пример:

CMD /CSTART="WINDOW TITLE"

Вы предлагаете, чтобы вызов программы динамически изменял язык с PowerShell на то, что использует вызываемая программа? Вы написали этот пример в PowerShell, а это значит, что он должен быть эквивалентен любому из следующих

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

TL; DR: предположение, что мы можем надежно передать любое значение в качестве аргумента любой программе в подсистеме Microsoft Windows NT, _ неверно_, поэтому мы должны перестать притворяться, что это наша цель.

Это должно быть целью, не так ли? Это не проблема PowerShell, если программа не может интерпретировать полученные ею аргументы.

Программа CMD может интерпретировать аргумент /CECHO=A " B но PowerShell не может передать его, не искажая его.

При вызове собственных исполняемых файлов Windows мы должны сохранить исходное цитирование. Пример:

CMD /CSTART="WINDOW TITLE"

Вы предлагаете, чтобы вызов программы динамически изменял язык с PowerShell на то, что использует вызываемая программа? Вы написали этот пример в PowerShell, а это значит, что он должен быть эквивалентен любому из следующих

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

Я попытался предположить, что при взаимодействии с внешними программами в подсистеме Microsoft Windows NT у PowerShell есть множество способов кодирования аргументов, которые все эквивалентны PowerShell, но не эквивалентны принимающей программе. Прямолинейность и навязывание аргументов кодирования One True Way ™, не обращая внимания на то, какой порядок цитирования фактически использовал пользователь, бесполезны, мягко говоря.

@ yecril71pl Меня очень смущают твои комментарии. Что именно вы здесь предлагаете? Все ваши варианты использования покрываются --% . Вы отклонили это ранее, сказав

--% требует предопределенной командной строки, поэтому его полезность ограничена.

Но на самом деле вы можете использовать переменные среды с --% . Попробуй это:

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

Так что мне не хватает?

Нам не хватает синтаксиса CMD /CSTART="$mytitle" , без утечки информации в ENV: .

Как ужасная идея, у нас есть возможность заменить Environment.ExpandEnvironmentVariables чем-то другим. В любом случае в Unix нет собственной реализации, и я не верю, что обрабатываемые им материалы станут критичными для производительности при переписывании на C #.

Поскольку знаки равенства в любом случае недопустимы в именах переменных env, мы можем иметь %=$a% mean $a . Это не сломает ничего существующего, но позволит использовать некоторые очень гибкие (и, возможно, плохие) расширения, например, заставить его работать как строки шаблона JS. Черт, мы можем определить %VARNAME=$var% как своего рода запасной синтаксис.

Что до адской документации, это вызовет ... Прошу прощения.

  • У нас _нет_ проблемы с синтаксическим анализом.

  • У нас есть проблема с _ как PowerShell передает дословные строковые аргументы, полученные в результате _it_ синтаксического анализа, во внешние (собственные) исполняемые файлы_:

    • В _Windows_ проблема заключается в том, что командная строка для вызова внешнего исполняемого файла, который создается за кулисами, _не__ придерживается наиболее широко используемого соглашения для цитирования аргументов , как подробно описано в документации компилятора Microsoft C / C ++ " Команда синтаксического анализа C ++": Раздел

    • В настоящее время происходит даже не то, что используется _разное_ соглашение: предположительно из-за недосмотра создаваемые командные строки ситуативно _синтаксически фундаментально нарушены_, в зависимости от специфики аргументов, относящихся к комбинации встроенных двойных кавычек и пробелов а также аргументы с пустой строкой.

    • В конечном итоге проблема заключается в фундаментальной архитектуре создания процесса в Windows: вы вынуждены кодировать аргументы для передачи процессу _ как командную строку_ - единственную строку, представляющую _ все_ аргументы, - вместо того, чтобы передавать их как _массив_ аргументов (который как это делают Unix-подобные платформы). Необходимость передачи командной строки требует реализации _ правил цитирования и экранирования_, и, в конечном итоге, _ каждая программа_ зависит от того, как интерпретировать данную командную строку. По сути, это равносильно тому, что программы излишне превращаются в своего рода мини-оболочку: они вынуждены _повторно выполнять_ задачу, которую оболочка уже выполнила, а именно задачу, которая должна входить в компетенцию только _ оболочки_ (как и случай в Unix), а именно разбор командной строки на отдельные аргументы. Вкратце, это анархия, которая является аргументом передачи аргументов в Windows .

    • На практике анархия смягчается за счет _ самых_ программ, придерживающихся вышеупомянутого соглашения, и новые разрабатываемые программы, скорее всего, будут придерживаться этого соглашения, в первую очередь потому, что широко используемые среды выполнения, лежащие в основе консольных приложений, реализуют эти соглашения (например, Microsoft C / C ++ / Среды выполнения .NET). Поэтому разумное решение:

      • Заставьте PowerShell придерживаться этого соглашения при незаметном построении командной строки.
      • Для "мошеннических" программ, которые _не_ соблюдают это соглашение - в частности, cmd.exe , командные файлы и служебные программы Microsoft, такие как msiexec.exe и msdeploy.exe - предоставляют механизм для явного управлять командной строкой, передаваемой в целевой исполняемый файл; это то, что дает --% , символ остановки анализа, хотя и довольно неудобно .
    • В _Unix_ проблема в том, что _a командная строка создается вообще_ - вместо этого массив дословных аргументов должен передаваться _as-is_ , который .NET Core теперь поддерживает (начиная с версии 2.1, через ProcessStartInfo.ArgumentList ; он должен _ всегда_ поддерживать это, учитывая, что - разумно - _ здесь нет командных строк_, только массивы аргументов, когда процесс создается на Unix-подобных платформах).

    • Как только мы используем ProcessStartInfo.ArgumentList , все проблемы в Unix исчезнут.

Решение этих проблем - вот в чем суть https://github.com/PowerShell/PowerShell-RFC/pull/90 @TSlivede .

В https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -650242411 я предложил дополнительно автоматически компенсировать "мошенничество" командных файлов, учитывая их все еще очень широкое использование в качестве точек входа в интерфейс командной строки для высоких -профильное программное обеспечение, такое как Azure (CLI az реализован как пакетный файл, az.cmd ).
Точно так же нам следует подумать о том, чтобы сделать то же самое для msiexec.exe и msdeploy.exe и, возможно, других громких «мошеннических» интерфейсов командной строки Microsoft.


Я только что опубликовал модуль Native , ( Install-Module Native -Scope CurrentUser ), который решает все вышеперечисленное через свою функцию ie (сокращение от i nvoke (external) e xecutable; it является более полной реализацией представленной выше функции iep ).

Он также включает ins ( Invoke-NativeShell ) с адресом # 13068 и dbea ( Debug-ExecutableArguments ) для диагностики передачи аргументов - см. Https: // github. com / PowerShell / PowerShell / issues / 13068 # issuecomment -671572939 для получения дополнительных сведений.

Другими словами: ie может служить ненавязчивым временным промежутком, пока мы ждем, пока эта проблема не будет исправлена, просто добавив к вызовам префикса ie в качестве команды:

Вместо:

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

вы бы использовали следующее:

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

Что касается примера CMD /CSTART="WINDOW TITLE" (чья более идиоматическая форма - cmd /c start "WINDOW TITLE" , которая уже работает):

По сути, это та же проблема, что и с аргументами prop="<value with spaces>" для msiexec / msdeploy : PowerShell - справедливо - преобразует /CSTART="WINDOW TITLE" в "/CSTART=WINDOW TITLE" , что, однако, нарушает вызов cmd.exe .

Есть два способа решить эту проблему:

  • Делегировать ins / Invoke-NativeShell (обратите внимание, что использование cmd.exe /c фактически подразумевается):

    • ins 'START="WINDOW TITLE"'
    • Если вы используете строку _expandable_, вы можете встроить значения PowerShell в строку команды.

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

  • В качестве альтернативы используйте текущую реализацию --% , но помните о ее ограничениях :

    • cmd --% /CSTART="WINDOW TITLE"
    • Как обсуждалось, проблемное ограничение --% заключается в том, что единственный способ встраивать значения _PowerShell_ - использовать _aux. переменная среды_ и укажите на нее синтаксис %...% :

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

      • Чтобы избежать этого ограничения, --% всегда должно быть реализовано с аргументом _single_ string - например,

        cmd --% '/CSTART="WINDOW TITLE"' или cmd --% "/CSTART=`"$title`"" - но это не может быть изменено без нарушения обратной совместимости, поэтому придется ввести символ _new_ - лично я не вижу необходимости в нем.

  • они вынуждены повторно выполнить задачу, которую оболочка уже выполнила

Я не думаю, что CMD.EXE разбивает командные строки на аргументы, единственное, что нужно, - это выяснить, какой исполняемый файл вызывать, а остальное - это просто командная строка, написанная пользователем (после подстановок переменных среды, которые выполняются без учета границ аргументов). Конечно, внутренние команды оболочки здесь являются исключением.

По сути, это та же проблема, что и с prop="<value with spaces>" arguments для msiexec / msdeploy

Я не являюсь уверенным пользователем ни того, ни другого, поэтому я предпочел упомянуть то, с чем я более знаком.

Чтобы было ясно: следующее не влияет на моменты, сделанные в моем предыдущем комментарии.

Я не думаю, что CMD.EXE разбивает командные строки на аргументы

  • Он может обойтись без _ явного_ разделения при вызове _ внешних исполняемых файлов_ (команды, выполняемые другим исполняемым файлом в дочернем процессе), но он должен делать это для _batch файлов_.

  • Даже при вызове внешних исполняемых файлов он должен _ знать_ о границах аргументов, чтобы определить, имеет ли данный метасимвол (например, & ) _syntactic_ функцию или является ли он частью аргумента в двойных кавычках и, следовательно, должен обрабатываться как буквальное:

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

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

Кроме того, cmd.exe распознает _embedded_ " chars. в "..." строки распознаются, если экранированные как "" :

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

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

К сожалению, cmd.exe _only_ поддерживает (только для Windows) "" а не более широко используемые \" (которые используются _shells_ в POSIX_ в Unix _exclusively_ - примечание: _shells_, а не _programs_, потому что программы просто видят массив дословных аргументов, полученных в результате синтаксического анализа оболочки).

Хотя большинство интерфейсов командной строки в Windows поддерживают _both_ "" и \" , некоторые _only_ понимают \" (особенно Perl и Ruby), и тогда у вас проблемы:

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

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

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

Следовательно:

  • По возможности избегайте прямого вызова cmd.exe .

    • Вызов внешних исполняемых файлов напрямую (как только эта проблема будет устранена) или через ie (на данный момент), используя синтаксис _PowerShell_.
  • Если вам действительно нужно вызвать cmd.exe , используйте ins / Invoke-NativeShell для общей простоты и, в частности, для того, насколько легко встраивать значения переменных и выражений PowerShell в командную строку. .

    • Законная причина по-прежнему вызывать cmd.exe напрямую - это компенсировать отсутствие поддержки PowerShell необработанных байтовых данных в конвейере - см. Этот ответ SO в качестве примера.

Я знаю, что здесь будет много критики, и я действительно ценю глубину происходящего обсуждения, но ... утки ... есть ли у кого-нибудь пример того, что это действительно имеет значение в реальном сценарии ?

Я считаю, что в PowerShell мы не наделены полномочиями решать «анархию», которая в настоящее время существует при синтаксическом анализе аргументов Windows. И по многим из тех же причин, по которым мы не можем решить проблему, есть веская причина, по которой Windows и компиляторы VC ++ решили не нарушать это поведение. Это безудержно, и мы создадим действительно длинный хвост новых (и в значительной степени не поддающихся расшифровке) проблем, если изменим ситуацию.

Для тех утилит, которые уже являются кроссплатформенными и активно используются между Windows и Linux (например, Docker, k8s, Git и т. Д.), Я не вижу, чтобы эта проблема проявлялась в реальном мире.

И для тех «мошеннических» приложений, которые плохо справляются со своей задачей: это в основном устаревшие утилиты только для Windows.

Я согласен с тем, что то, что вы описали @ mklement0 , во многом является «правильным» решением. Я просто не знаю, как туда добраться, не напортачить.

Довольно простой перерыв в использовании:

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

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

git.exe - это приложение с хорошим поведением, поэтому исправление в PowerShell будет простым, хотя и потребует от всех скриптеров вернуть свои умные обходные пути. cmd.exe сложнее приспособиться и требует гораздо более внимательного подхода, который может решить несколько проблем, но, вероятно, не все из них. Что на самом деле ужасно, учитывая, что PowerShell начинался как инструмент Windows NT. Я понимаю этот вопрос как _, существует ли реальный сценарий, когда некорректная устаревшая утилита, такая как cmd.exe будет вызываться из PowerShell таким образом, чтобы вызвать проблемы в интерфейсе_. PowerShell попытался решить эту проблему, продублировав большую часть функций в cmd.exe , чтобы сделать cmd.exe избыточным. Это также возможно для других инструментов, например, MSI может работать через ActiveX, хотя для этого требуются значительные знания. Так есть ли что-нибудь существенное, что не покрыто?

@ PowerShell / комитет по PowerShell обсудил это. Мы ценим пример git, который ясно показывает убедительный пример из реального мира. Мы согласились с тем, что нам нужно иметь экспериментальную функцию в начале 7.2, чтобы проверить влияние такого критического изменения. Дополнительный тестовый пример показывает, что даже в --% есть проблема, даже если его следовало не проанализировать:

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

Похоже, это проблема в связке параметров собственной команды.

Да, спасибо @cspotcode. Этот пример определенно был для меня моментом ага (особенно учитывая, что я действительно попал в него в реальном мире).

Меня по-прежнему беспокоит аспект критических изменений, и я считаю, что это возможный кандидат на экспериментальную функцию, которая может оставаться экспериментальной в нескольких версиях PowerShell, и мы абсолютно не уверены, что в конечном итоге это произойдет.

Мне также нужно вникнуть в подробности, чтобы понять аспект разрешенного списка / "Rouge app" вашего RFC, @ mklement0 , так как я не уверен, сколько мы хотим подписаться, чтобы поддерживать такой список.

@joeyaiello и @ SteveL-MSFT, позвольте мне сначала сделать мета-наблюдение:

Хотя приятно видеть, что пример @cspotcode дал вам _glimpse_ проблемы, ваши ответы по-прежнему выдают фундаментальное отсутствие понимания и оценки (масштабности) основной проблемы (я буду аргументировать это в следующем комментарии) .

Это не личное суждение: я полностью осознаю, насколько сложно, должно быть, быть очень натянутым и принимать решения по очень широкому кругу вопросов за короткий промежуток времени.

Однако это указывает на _структурную_ проблему: мне кажется, что решения обычно принимаются @ PowerShell / powershell-комитетом на основе поверхностного понимания обсуждаемых проблем в ущерб сообществу в целом.

Для меня ответ комитета на обсуждаемую здесь проблему является наиболее убедительным примером этой структурной проблемы на сегодняшний день.

Поэтому я прошу вас учесть это:

Как насчет того, чтобы назначить _суб_ комитеты по конкретным предметам, с которыми комитет консультируется, чтобы _до_ иметь необходимое понимание затронутых вопросов?

Можете ли вы поделиться содержанием testexe SteveL-MSFT, просто хочу убедиться!

@TSlivede точно резюмировал проблему в https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -665125375:

PowerShell, с другой стороны, утверждает, что это оболочка (до решения # 1995 я не буду говорить, что это _is_ оболочка)

Как уже неоднократно говорилось ранее, основная задача оболочки - вызывать внешние исполняемые файлы с аргументами.

PowerShell в настоящее время не выполняет этот мандат, поскольку аргументы со встроенными двойными кавычками и аргументы с пустой строкой передаются неправильно.

Как указывалось ранее, это могло быть меньшей проблемой в те дни, когда только для Windows, когда отсутствие способных внешних интерфейсов командной строки редко вызывало эту проблему, но в наши дни уже нет, и если PowerShell хочет зарекомендовать себя как надежный кросс-платформенный shell, он должен решить эту проблему.

Пример git @cspotcode - хороший; любой исполняемый файл, которому вы хотите передать строку JSON - например, curl - является другим:

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

Оставляя в стороне обратную совместимость:

  • В Unix проблема тривиально и _ полностью_ решается с помощью ProcessStartInfo.ArgumentList за кулисами.

  • В Windows проблема тривиальна и _ в основном_ решается с помощью ProcessStartInfo.ArgumentList за кулисами.

    • Для крайних случаев ("мошеннические" интерфейсы командной строки) есть ( плохо реализованный ) --%
    • В качестве _вежливости_ мы можем компенсировать некоторые хорошо известные крайние случаи, чтобы уменьшить потребность в --% - см. Ниже.

Поэтому _ как можно скорее_ необходимо сделать один из следующих вариантов:

  • Осознайте важность правильной работы передачи аргументов и _ исправьте это за счет обратной совместимости_.

  • Если обратная совместимость действительно имеет первостепенное значение, предоставьте новый оператор или функцию, такую ​​как ie из модуля Native который устраняет проблему, и широко расскажите об этом как о единственном надежном способе вызова внешних исполняемые файлы.

Совершенно неадекватно предлагать _экспериментальную_ функцию для устранения неисправной фундаментальной функции.


@ SteveL-MSFT

Даже рассмотрение использования --% в качестве решения этой проблемы в корне ошибочно:

Это функция _Windows_, которая знает только "..." цитирование и %...% -стайл ссылки на переменные среды.

В Unix концепция «остановки синтаксического анализа» принципиально не применима: _нет командной строки_ для передачи дочерним процессам, только массивы аргументов.

Таким образом, _кто-то_ должен проанализировать командную строку на аргументы _ перед_ вызовом, который неявно делегируется классу ProcessStartInfo через его свойство .Arguments , которое в Unix использует соглашения _Windows_ для синтаксического анализа командной строки. - и поэтому распознает только цитирование "..." (с экранированием встроенного " как "" или \" ).

--% - это функция только для Windows, единственная законная цель которой - вызывать «мошеннические» интерфейсы командной строки.


@joeyaiello

что компиляторы Windows и VC ++ решили не нарушать это поведение.

Компилятор VC ++ _ применяет разумное, широко соблюдаемое соглашение_ , чтобы навести порядок в анархии.

Здесь пропагандируется именно соблюдение этого соглашения, что автоматически даст нам использование ProcessStartInfo.ArgumentList .

_ Только это покроет подавляющее большинство звонков. Покрытие ВСЕХ вызовов невозможно, и PowerShell не отвечает за это.

Как уже говорилось, для «мошеннических» интерфейсов командной строки, требующих нетрадиционных форм цитирования, необходимо использовать --% (или ins / Invoke-NativeShell из модуля Native ).

_В качестве любезности_ мы можем автоматически компенсировать хорошо известные «мошеннические» сценарии, а именно вызов командных файлов и некоторые громкие интерфейсы командной строки Microsoft:

  • Случай пакетного файла является общим, его легко объяснить и концептуализировать (например, передать a&b как "a&b" , даже если это не требует цитирования) - это позволит избежать необходимости использования --% со всеми интерфейсами командной строки, которые используют пакетные файлы в качестве точки входа (что довольно часто), например az.cmd в Azure.

  • Альтернативой жесткому кодированию исключений для определенных интерфейсов командной строки - что, по общему признанию, может сбивать с толку - является обнаружение следующего _pattern_ в аргументах, которые являются результатом синтаксического анализа PowerShell - <word>=<value with spaces> - и вместо передачи "<word>=<value with spaces>" , как это происходит сейчас, передать <word>="<value with spaces>" ; последний удовлетворяет «мошенническим» интерфейсам командной строки, а также принимается соответствующими конвенциями интерфейсами командной строки; например, echoArgs "foo=bar baz" конечном итоге видит тот же первый аргумент, что и echoArgs --% foo="bar baz"

@musm Вы можете найти исходный код TestExe на https://github.com/PowerShell/PowerShell/blob/master/test/tools/TestExe/TestExe.cs.

GitHub
PowerShell для любой системы! Участвуйте в разработке PowerShell / PowerShell, создав учетную запись на GitHub.

Я думаю, что добавление исключений по умолчанию просто приведет к ситуации, аналогичной нынешней, когда людям нужно вернуть «полезность» PowerShell. Если есть исключения, должно быть очевидно, что они применяются.

Может быть что-то вроде:

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

Я действительно изо всех сил пытаюсь найти синтаксис для этого, который не нарушен. По крайней мере, это имеет смысл, если вы думаете об этом как о приведении программы, но приведение фактической переменной, содержащей программу, потребует заключения в круглые скобки.

Это, помимо разрешения людям добавлять исключения для любого нарушенного поведения, которое они желают, должно, надеюсь, устранить необходимость в --% . Класс исключения, который они регистрируют, затем будет иметь метод для определения, применимо ли он к программе (для интеллектуального вызова), и метод экранирования, в котором вы просто бросаете ему абстрактное синтаксическое дерево команды, и оно возвращает массив / строку аргументов.

  • вместо передачи "<word>=<value with spaces>" , как это происходит сейчас, передать <word>="<value with spaces>"

Кавычки, используемые для создания командной строки, должны соответствовать тому, как вызов цитируется в PowerShell. Следовательно:

  1. Вызов, не содержащий двойных кавычек в тексте, должен заключать в двойные кавычки все значение.
  2. Вызов, содержащий двойные кавычки, должен сохранять их в том виде, в котором они написаны в сценарии, _ если возможно_.

В частности:
| аргумент сценария | командная строка |
| ----------------- | ---------------- |
| p=l of v | «р = 1 из v» |
| p=l` of` v | «р = 1 из v» |
| p="l of v" | р = "л из v" |
| p="l of v"'a s m' | p = "l of v" a "sm" |
| p="l of v"' s m' | p = "l vsm" |

В последней строке показан пример, в котором невозможно сохранить исходные двойные кавычки.

Я знаю, что здесь будет много неприятностей, и я действительно ценю глубину происходящего обсуждения, но ..._ утки _... есть ли у кого-нибудь пример того, что это действительно имеет значение в реальном сценарии ?

Я уже упоминал об этом в этой теме год назад (сейчас он скрыт ...), что я получал много моментов WFT при использовании ripgrep в Powershell. Я не мог понять, почему я не могу искать строки в кавычках. Он проигнорировал мои цитаты:

rg '"quoted"'

а в git bash этого не произошло.

Теперь у меня меньше этих моментов WTF, потому что, к сожалению, я обнаружил эту длинную проблему с github и обнаружил, что передача " в Powershel полностью не работает. Недавний пример "git.exe" тоже хорош.

Если честно, теперь я даже не осмеливаюсь использовать Powershell для вызова собственной команды, когда знаю, что могу передать " в строке в качестве параметра. Я знаю, что могу получить неправильный результат или ошибку.

На самом деле, @ mklement0 здорово подвел итог (это должно быть где-то высечено на камне)

Как уже неоднократно говорилось, основная задача оболочки - вызывать внешние исполняемые файлы с аргументами .
PowerShell в настоящее время не выполняет этот мандат, поскольку аргументы со встроенными двойными кавычками и аргументы с пустой строкой передаются неправильно.
Как указывалось ранее, это могло быть меньшей проблемой во времена, когда только Windows использовалась, когда отсутствие функциональных внешних интерфейсов командной строки редко вызывало эту проблему, но в наши дни уже нет, и если PowerShell хочет зарекомендовать себя как надежный кроссплатформенный shell, он должен решить эту проблему .

И о критических изменениях.
Недавно коллега написал мне, что мой сценарий не работает на его машине. Я запускал его только на Powershel Core, а он запускал его на Windows Powershell. Выводит файл в кодировке Out-File -Encoding utf8 с "BOM" в Windows Powershell и без BOM в Powershel Core. Это как-то неопубликованный пример, но он показывает, что в Powershel уже есть тонкие критические изменения, и это хорошо, потому что мы устраняем причуды и интуитивное поведение из языка, который им известен. Было бы здорово, если бы команда Powershel была немного более снисходительной, когда дело доходит до критических изменений, теперь, когда у нас есть кроссплатформенный Powershell, который поставляется за пределами Windows, и что мы знаем, что Windows Powershell находится в режиме «обслуживания» и будет использоваться вечно, если вы действительно хотите, чтобы он запускал какой-нибудь старый скрипт, который сломался в более новой версии Powershell.

RE: последний пункт критических изменений - полностью согласен. Мы допустили _много_ критических изменений по разным причинам. Однако все чаще и чаще кажется, что некоторые критические изменения просто не одобряются из соображений предпочтения и им не уделяется должного внимания с точки зрения их фактической ценности.

Есть некоторые изменения, подобные этому, которые _массивно_ улучшат общий опыт работы с оболочкой для всех, кому нужно выходить за пределы PowerShell для выполнения задач, что происходит постоянно. Снова и снова утверждается, что текущее поведение неприемлемо и уже в значительной степени нарушено для чего-либо, кроме простейшего использования. И тем не менее, мы все еще сталкиваемся с этой сдержанностью в отношении критических изменений, даже несмотря на то, что есть множество уже принятых критических изменений, некоторые из которых имеют такое же большое влияние.

Для тех, кто просит примеры - найдите минутку, чтобы хотя бы раз посетить Stack Overflow. Я уверен, что у @ mklement0 есть множество примеров, когда требуется помощь сообщества, чтобы объяснить критическое изменение в новых версиях. Это происходит все время_. У нас нет оправдания, чтобы не внести полезные критические изменения.

Каждый раз, когда команда MSFT повторяет одно и то же снова и снова, мы можем быть уверены, что они знают больше, чем могут сказать публично. Мы должны уважать их внутреннюю дисциплину, а не давить на них. _Можно найти компромисс. _ Надеюсь, у меня есть время сегодня описать альтернативный путь с отложенной миграцией.

Я осознаю это, и поэтому я редко ставлю под сомнение это.

Однако это проект с открытым исходным кодом; если эти решения невозможно увидеть, люди неизбежно будут разочарованы. Не стоит винить ни одну из сторон этой медали, это просто реальность ситуации, ИМО. Так что да, наличие пути миграции может несколько облегчить эту боль, но нам нужны четкие правила, определяющие, как это должно работать, чтобы все работало для как можно большего числа людей. Однако при недостатке информации трудно достичь компромисса.

Я с нетерпением жду того, чтобы увидеть, что у вас в рукаве. 😉

@ mklement0 , ты абсолютно прав, и настолько, что я могу ответить только на твою мета-точку прямо сейчас. К сожалению, в тех случаях, когда мы не можем достичь уровня глубины, необходимого для ответа на такой вопрос, более безопасным подходом часто является отложить или отклонить критическое изменение, пока у нас не будет больше времени на

Однако я хочу высказать еще одну мета-точку о критических изменениях: наша телеметрия подразумевает, что большинство пользователей PowerShell 7 не управляют своими собственными версиями. Они запускают автоматизированные сценарии в удобной управляемой среде, например, обновляют своих пользователей с 6.2 до 7.0 (см. Двухдневный скачок в 6.2, когда пользователи становятся пользователями версии 7.0, начиная с 8/3; это не единственная точка данных здесь, но это удобный момент, который подчеркивает суть). Для этих пользователей неприемлемо критическое изменение, которое превращает отлично работающий сценарий в неработающий.

Я также в долгу перед сообществом блог о том, как я думаю о влиянии критических изменений, а именно о том, как найти компромисс между распространенностью существующего использования и серьезностью нарушения и простотой выявления и исправления нарушения. Это чрезвычайно распространено в существующих сценариях, что затрудняет идентификацию и исправление, а поведение взлома - от полного успеха до полного отказа, отсюда моя крайняя сдержанность, чтобы что-либо здесь делать.

Я думаю, будет справедливо сказать, что мы не собираемся ничего делать здесь в 7.1, но я определенно готов сделать это следственным приоритетом для 7.2 (т.е. мы тратим больше, чем просто время нашего Комитета на обсуждение этого).

Как насчет назначения подкомитетов по конкретным предметам, с которыми комитет консультируется, у которых есть необходимое понимание затронутых вопросов?

Мы над этим работаем. Я знаю, что говорил это раньше, но мы очень близки (например, я занимаюсь созданием блога, и вы, вероятно, скоро увидите несколько новых лейблов, с которыми мы будем играть).

Я ценю терпение каждого и понимаю, что раздражает получение содержательного ответа от комитета каждые пару недель, когда люди вкладывают в обсуждение огромное количество мыслей и размышлений. Я знаю, похоже, это означает, что мы не задумываемся о вещах глубоко, но я думаю, что мы просто не выражаем глубину наших дискуссий так подробно, как здесь люди. В моем собственном бэклоге у меня есть целый набор тем блога, таких как критическое изменение, касающееся того, как я думаю о принятии решений внутри Комитета, но у меня просто никогда не было возможности сесть и выкачать их. Но я вижу здесь, что, возможно, люди найдут в этом большую ценность.

Надеюсь, я не слишком далеко зашел в этом обсуждении. Я не хочу, чтобы этот вопрос полностью превратился в мета-проблему управления проектом, но я действительно хотел решить некоторые из понятных проблем, которые я здесь вижу. Я умоляю всех, кто хочет поговорить с нами об этом более подробно, присоединиться к телеконференции сообщества на следующей неделе (добавьте сюда свои вопросы и мысли, и я обязательно обращусь к ним во время телеконференции).

Небольшое примечание по мета-точке: я ценю вдумчивый ответ, @joeyaiello.

Что касается серьезности критического изменения: следующие утверждения кажутся противоречивыми:

есть ли у кого-нибудь пример того, что это действительно имеет значение в реальном сценарии

vs.

Это чрезвычайно распространено в существующих сценариях, что затрудняет определение и исправление

Если это уже распространено, неуклюжесть и неясность необходимых обходных путей - еще одна причина для окончательного исправления этого, особенно с учетом того, что мы должны ожидать увеличения количества случаев.

Я понимаю, что все существующие обходные пути не работают.

Если избегать этого имеет первостепенное значение, лучше всего использовать этот ранее предложенный подход :

предоставьте _new оператор или функцию_, такую ​​как ie function из модуля Native который устраняет проблему, и широко расскажите об этом как о единственном надежном способе вызова внешних исполняемых файлов.

_Функция_, такая как ie , позволит людям выбрать правильное поведение с _минимальной суетой_, _ в качестве временной меры_, не обременяя _язык_ новым синтаксическим элементом (оператором), единственным смыслом существования которого будет чтобы обойти устаревшую ошибку, которую нельзя исправить:

  • Временное решение предоставит _официально санкционированный_ доступ к правильному поведению (не полагаясь на _экспериментальные_ функции).
  • Пока временное решение будет необходимо, его нужно будет широко публиковать и надлежащим образом задокументировать.

Если / когда поведение по умолчанию будет исправлено:

  • функция может быть изменена так, чтобы просто подчиняться ей, чтобы не нарушать код, который ее использует.
  • новый код может быть написан без использования функции.

Такая функция, как ie, позволила бы людям выбрать правильное поведение с минимальными усилиями в качестве временной меры.

Мы можем упростить принятие с помощью # 13428. Мы можем прозрачно внедрить это с помощью исследований

@Dabombber

Я думаю, что добавление исключений по умолчанию просто приведет к ситуации, аналогичной нынешней, когда людям нужно вернуть «полезность» PowerShell. Если есть исключения, должно быть очевидно, что они применяются.

Предлагаемые мною приспособления заставляют подавляющее большинство вызовов «просто работать» - они представляют собой полезные абстракции от анархии командной строки Windows, от которой мы должны делать все возможное, чтобы оградить пользователей.

Обоснованное предположение состоит в том, что это экранирование - единовременное усилие для _legacy_ исполняемых файлов, и что вновь созданные файлы будут придерживаться соглашений Microsoft C / C ++.

Однако это невозможно сделать в _все_ случаях; для тех, которые не могут быть размещены автоматически, есть --% .

Лично я не хочу думать о том, реализуется ли данная утилита foo как foo.bat или foo.cmd или требует ли она foo="bar none" arguments в частности, не принимая также "foo=bar none" , которые для исполняемых файлов, соответствующих соглашению, эквивалентны.

И мне, конечно, не нужна отдельная форма синтаксиса для различных исключений, таких как &[bat]
Вместо этого --% - это универсальный инструмент (только для Windows), позволяющий сформулировать командную строку точно так, как вы хотите, независимо от конкретных нестандартных требований целевой программы.

В частности, предлагаемые варианты размещения следующие:

Заметка:

  • Как указано, они требуются только _в Windows_; в Unix для решения всех проблем достаточно делегирования ProcessStartInfo.ArgumentList .

  • По крайней мере, на высоком уровне эти приспособления легко концептуализировать и задокументировать.

  • Обратите внимание, что они будут применяться _после_ обычного синтаксического анализа PowerShell на этапе (только для Windows) преобразования в командную строку процесса. То есть собственный синтаксический анализ параметров PowerShell не будет задействован - и _не__ должно быть @ yecril71pl.

  • Любые действительно экзотические случаи, на которые не распространяются эти приспособления, должны будут решаться самими пользователями, с
    --% - или, с установленным модулем Native , с ins / Invoke-NativeShell , что упрощает встраивание значений переменных и выражений PowerShell в вызов.

    • Команда dbea ( Debug-ExecutableArguments ) из модуля Native может помочь в диагностике и понимании того, какая командная строка процесса используется в конечном итоге - см. Раздел примеров ниже.

Перечень помещений:

  • Для пакетных файлов (как уже отмечалось, важность автоматического учета этого случая заключается в преобладании высокопрофильных интерфейсов командной строки, которые используют пакетные файлы _ в качестве точки входа_, например az.cmd для Azure).

    • Встроенные двойные кавычки, если они есть, экранируются как "" (а не \" ) во всех аргументах.
    • Любой аргумент, не содержащий пробелов, но содержащий либо двойные кавычки, либо метасимволы cmd.exe например & , заключен в двойные кавычки (тогда как PowerShell по умолчанию заключает аргументы только с пробелами в двойные кавычки); например, дословный аргумент, который PowerShell видит как a&b , помещается как "a&b" в командную строку, передаваемую в пакетный файл.
  • Для высокопрофильных CLI, таких как msiexec.exe / msdeploy.exe и cmdkey.exe (без исключений жесткого кодирования для них):

    • Любой вызов, содержащий хотя бы один аргумент из следующих форм, вызывает поведение, описанное ниже; <word> может состоять из букв, цифр и знаков подчеркивания:

      • <word>=<value with spaces>
      • /<word>:<value with spaces>
      • -<word>:<value with spaces>
    • Если такой аргумент присутствует:

      • Встроенные двойные кавычки, если они есть, экранируются как "" (а не \" ) во всех аргументах. - см. https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167, чтобы узнать, почему мы не должны этого делать; это означает, что в редких случаях, когда <value with spaces> имеет _embedded_ " chars., необходимо использовать --% ; например,
        msiexec ... --% PROP="Nat ""King"" Cole"
      • В двойные кавычки заключена только часть <value with spaces> , а не аргумент в целом (последнее является тем, что PowerShell - вполне оправданно - делает по умолчанию); например, дословный аргумент, который PowerShell видит как foo=bar none , помещается в командную строку процесса как foo="bar none" (а не как "foo=bar none" ).
    • Заметка:

      • Если целевой исполняемый файл _не_ является CLI в стиле msiexec , это не причинит вреда, потому что соответствующие соглашениям CLI разумно учитываются <word>="<value with spaces>" и "<word>=<value with spaces>" _equivalent_, оба представляют дословно <word>=<value with spaces> .

      • Точно так же подавляющее большинство исполняемых файлов принимают "" взаимозаменяемо с \" для экранирования встроенных символов " , за исключением, в частности, собственного интерфейса командной строки PowerShell, Ruby и Perl (_не_ выполнение приспособление того стоит, по крайней мере, если вызывается PowerShell CLI, но я думаю, что жесткое программирование Ruby и Perl также имеет смысл). https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 показывает, что все приложения, использующие функцию CommandLineToArgvW WinAPI, _не_ поддерживают "" -экранирование.

Все остальные случаи в Windows также могут обрабатываться с помощью ProcessStartInfo.ArgumentList , который неявно применяет соглашение Microsoft C / C ++ (что, в частности, означает \" для " -экранирования).


Функция ie из текущей версии ( 1.0.7 ) модуля Native реализует эти приспособления (помимо исправления неработающего синтаксического анализа аргументов) для PowerShell версии 3 и выше ( Install-Module Native ).

Я приглашаю вас и всех присутствующих протестировать его, чтобы проверить утверждение, что оно «просто работает» для подавляющего большинства внешних исполняемых вызовов.

В настоящее время неизбежные ограничения:

  • Примечание. Эти технические ограничения проистекают из того, что ie реализовано как _функция_ (правильное исправление в самом движке не _не_ приведет к этим проблемам):

    • В то время как $LASTEXITCODE правильно настроен на код выхода процесса, $? всегда заканчивается $true - код пользователя в настоящее время не может явно установить $? , хотя и добавляет эту возможность был отмечен зеленым светом - см. https://github.com/PowerShell/PowerShell/issues/10917#issuecomment -550550490. К сожалению, это означает, что в настоящее время вы не можете осмысленно использовать ie с && и || , && , операторами конвейерной цепочки .
      Однако, если требуется _аборт_ сценария при обнаружении ненулевого кода выхода, можно использовать функцию-оболочку iee .

    • -- в качестве аргумента неизменно «съедается» связывателем параметров PowerShell; просто передайте его _twice_, чтобы передать -- в целевой исполняемый файл ( foo -- -- вместо foo -- ).

    • Токен без кавычек с , должен быть заключен в кавычки, иначе он будет интерпретирован как массив и передан как несколько аргументов; например, передайте 'a,b' вместо a,b ; аналогично передайте -foo:bar (что-то похожее на именованный аргумент PowerShell) как '-foo:bar' (в этом нет необходимости, но это связано с ошибкой: # 6360); аналогично '-foo.bar must be passed as ' -foo.bar '' (еще одна ошибка, которая также влияет на прямые вызовы внешних исполняемых файлов: # 6291)

  • Я ожидаю, что эта функция будет надежно работать в PowerShell _Core_. Из-за изменений в _Windows PowerShell_ со временем могут быть крайние случаи, которые не обрабатываются должным образом, хотя мне известны только два:

    • Аккомодация частичного цитирования для интерфейсов командной строки в стиле msiexec не может применяться в версиях 3 и 4, потому что в этих версиях весь аргумент заключен в дополнительный набор двойных кавычек; Однако он работает в версии 5.1.

    • "" -экранирование используется по умолчанию для обхода проблем, но в случаях, когда требуется \" (PowerShell CLI, Perl, Ruby), используется токен, например 3" of snow ошибочно передано как _3_ аргумента, поскольку во всех версиях Windows PowerShell такой аргумент не заключен в двойные кавычки; похоже, это происходит для аргументов с не начальными символами " которым не предшествует пробел.


Примеры с выводом из PowerShell Core 7.1.0-preview.5 в Windows 10:

Примечание. Функция dbea ( Debug-ExecutableArguments ) используется для иллюстрации того, как аргументы будут получены внешними исполняемыми / пакетными файлами.

Текущая, неработающая передача аргумента:

  • Вызов приложения, соответствующего соглашению (консольное приложение .NET, используемое dbea за кулисами по умолчанию):
# Note the missing 2nd argument and the effective loss of embedded double quotes,
# due to the embedded " chars. not having been escaped.
PS> dbea -- 'a&b' '' '{ "foo": "bar" }'

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

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

Command line (helper executable omitted):

  a&b  "{ "foo": "bar" }"
  • Вызов командного файла:

Обратите внимание на использование -UseBatchFile чтобы dbea вместо этого передавал аргументы вспомогательному _batch file_.

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

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

  <a>

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

The command line parameters are incorrect.

Устранение проблемы с ie :

Обратите внимание на использование -ie в вызовах dbea , что приводит к использованию ie для вызовов.

  • Вызов приложения, соответствующего соглашению (консольное приложение .NET, используемое dbea за кулисами по умолчанию):
# OK
# Note that the empty 2nd argument is correctly passed, and that \" is used for embedded "-escaping.
PS> dbea -ie -- 'a&b' '' '{ "foo": "bar" }'

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

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

Command line (helper executable omitted):

  a&b "" "{ \"foo\": \"bar\" }"
  • Вызов командного файла:
# OK
# - `a&b` was enclosed in "...", due to the presence of metacharacter `&`
# - "" is used for escaping of embedded " chars.
# Note that `echo %1`, for instance, prints the argument exactly as passed on the command line, including quoting.
# `echo %~1` strips the surrounding double quotes, but embedded escaped ones still print as "".
# However, if you pass these arguments (`%*`) through to convention-compliant CLIs, they are parsed correctly.
PS> dbea -ie -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

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

  <"a&b">
  <"">
  <"{ ""foo"": ""bar"" }">
  • Вызов CLI в стиле msiexec , cmdkey.exe :
# The call now succeeds, because `ie` ensure the value-only double-quoting that cmdkey.exe requires.
# (Use `cmdkey /del:foo` to remove the credentials again.)
PS> ie cmdkey.exe /generic:foo /user:foo /password:'bar none'

CMDKEY: Credential added successfully.

Чтобы показать, что двойные кавычки только для значений были применены в реальной командной строке через dbea :

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

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

Command line (helper executable omitted):

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

Следующий код вызывает потерю данных:

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

1

@JamesWTruher предлагает исправление и

Есть ли запрос на извлечение предлагаемого исправления? Было бы хорошо, если бы мы могли прокомментировать пиар. Потому что исправление этого никогда не было сложной частью IMO. Сложная часть заключалась в том, как справиться с обратной совместимостью. И было бы хорошо, если бы мы могли увидеть, как предлагаемое исправление справляется с этим ....

Приятно слышать, @ SteveL-MSFT - исправление _все_ проблем, обсуждаемых здесь? Ведь для v7.1? И я второй запрос @TSlivede .

@ yecril71pl , это хорошая находка, хотя это действительно относится к собственному синтаксическому анализу PowerShell (который имеет _some_ специальный_ регистр для внешних исполняемых файлов), а не к тому, как создается собственная командная строка _after_ синтаксического анализа (именно здесь возникают проблемы, обсуждавшиеся ранее из).

Более краткое описание проблемы в Unix:

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

То есть только элемент массива _first_ был напрямую привязан к -a: , остальные были переданы как отдельные аргументы.

Есть связанные проблемы с аргументами, которые _ выглядят как_ параметры PowerShell, но не являются:

Существует связанная проблема, которая влияет только на вызовы команд _PowerShell_, которые используют $args / @args : # 6360

  • & { $args.Count; $args } -foo:bar дает 2, '-foo:', 'bar'

Также есть # 6291, который влияет как на команды PowerShell, так и на внешние исполняемые файлы (обратите внимание на . ).

  • & { $args.Count; $args } -foo.bar дает 2, '-foo', '.bar'

Следует отметить, что (...) как часть простого слова обычно приводит к тому, что вывод (...) в целом становится аргументом _separate_, поэтому тот факт, что первый элемент _is_ прикреплен к printf Вышеуказанная команда
& { $args.Count; $args.ForEach({ "$_" }) } foo('bar', 'baz') дает 2, 'foo', 'bar baz' , вторым аргументом которого является строковое преобразование массива 'bar', 'baz' .

Когда PowerShell должен передать -A:(1,2) для внешнего исполняемого файла, он определяет, что -A: - это строка, а (1,2) - это массив, который должен быть упорядочен как '1 2'. PowerShell пытается сохранить исходный синтаксис вызова, поэтому, сложив все вместе, мы получим '-A: 1 2', тогда как правильным результатом будет '-A: "1 2"'. Мне это кажется банальным упущением в коде маршаллинга.

Я бы не сказал, что конкретная проблема @ yecril71pl связана с синтаксическим

Когда PowerShell должен передать -A: (1,2) для внешнего исполняемого файла, он определяет, что -A: - это строка, а (1,2) - это массив.

Почти: -A: - это именованный параметр, а массив - это значение этого параметра (чтобы проверить это: удалите - впереди, и вы увидите, что он цитируется по-другому). Но проблема не в том, что массив неправильно преобразован в строку - проблема в том, что для собственных исполняемых файлов аргументы (почти) всегда разбиты, даже при использовании $ а не @ и даже если массив происходит от такого выражения, как (1,2) .

Протестируйте, например, printf '<%s>\n' -a:('a b',2) : поскольку строка a b содержит пробел, она правильно заключена в кавычки, но поскольку 2 находится в следующем элементе массива, и массив разбит, 2 не является частью первого аргумента.


Волшебство происходит в NativeCommandParameterBinder.cs

В строке 170 PowerShell пытается получить перечислитель для текущего значения аргумента.

IEnumerator list = LanguagePrimitives.GetEnumerator(obj);

Если list не равно null , PowerShell добавляет каждый элемент списка (возможно, в кавычки, если он содержит пробелы) в lpCommandLine.

По умолчанию элементы разделяются пробелами ( строка 449 ). Единственное исключение - если массив был буквальным
(как в printf '<%s>\n' -a:1,2 ).
Затем powershell пытается использовать тот же разделитель в lpCommandLine, который использовался в строке сценария.

Я жду пиара, когда буду готов. Если дойдет до версии 7.1, мы ее возьмем, иначе будет 7.2. Он занимается обратной совместимостью. Возможно, поможет некоторая помощь в написании тестов Pester (с использованием testexe -echoargs которые можно построить с помощью publish-pstesttools из build.psm1).

Жду пиара когда буду готов

Это именно то, чего я хотел избежать - покажите, пожалуйста, код, который не готов (отметьте PR как незавершенное).

Или хотя бы прокомментируйте, что он хочет делать.

Было бы неплохо, если бы мы могли обсудить, как он хочет справиться с обратной совместимостью.

@TSlivede , обратите внимание, что поскольку PowerShell вызывается _CLI_ - внешний исполняемый файл - -A:(1,2) анализируется _перед_, зная, что этот токен в конечном итоге будет привязан к параметру _ named_ -A - что такой параметр _в конце концов_ вступает в игру случайно к проблеме.

@ yecril71pl :

Он выясняет, что -A: - это строка

Нет, это особый случай во время синтаксического анализа, потому что он может _ выглядеть_ как_ параметр PowerShell.

Этот специальный регистр применяется для вызовов команд PowerShell, которые также используют $args (в отличие от фактических объявленных параметров, которые связаны), но это происходит _ по-разному_ для внешних исполняемых файлов (что обычно является отдельным аргументом, остается прикрепленным, но в случае коллекции только ее _первый_ элемент).

Фактически вы можете отказаться от этого специального корпуса, если заранее передадите -- , но, конечно же, он также передаст -- , который удаляется только для вызовов команд _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>

Если аргумент _не_ выглядит как параметр PowerShell, обычное поведение (вывод из (...) становится отдельным аргументом) вступает в силу даже для внешних исполняемых файлов (с обычным поведением _array_ разбрасывается, т. Е. Превращается в отдельные аргументы во внешне-исполняемом случае).

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

Применение этого поведения _согласованно_ имело бы смысл - выражение (...) как часть простого слова должно _всегда_ становиться отдельным аргументом - см. # 13488.

Чтобы передать единственный аргумент '-A:1 2 3' с массивом _stringified_, используйте (n неявно) _расширяемую строку_, и в этом случае вам понадобится $(...) а не (...) _and_ - что удивительно - в настоящее время также "..." :

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

В этом случае _не_ также_ нужно "..." - это опять же необходимо из-за аномалий, связанных с токенами, которые _подобны_ параметрам (которые в целом применимы к _both_ PowerShell и вызовам внешних исполняемых файлов - см. # 13489); если нет, цитировать не нужно:

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

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

Мир составных токенов в режиме аргументов сложен с несколькими несоответствиями - см. # 6467.

@ SteveL-MSFT

В своей текущей форме testexe -echoArgs выводит только отдельные аргументы, которые исполняемый файл .NET Core проанализировал из исходной командной строки (в Windows), но не саму исходную командную строку.

Поэтому он не может тестировать приспособления с выборочным цитированием для пакетных файлов и интерфейсов командной строки в стиле msiexec - при условии, что такие приспособления будут реализованы, что я настоятельно рекомендую; например, вы не сможете проверить, что PROP='foo bar' было передано как PROP="foo bar" , заключив в двойные кавычки только часть значения.

Однако для того, чтобы напечатать необработанную командную строку, testexe не должен быть исполняемым файлом .NET _Core_, потому что .NET Core _ воссоздает гипотетическую командную строку_, которая всегда использует \" -экранирование для встроенного " chars., Даже если был использован "" , и обычно не совсем точно отражает, какие аргументы были заключены в двойные кавычки, а какие нет - для справки см. Https://github.com/ dotnet / runtime / issues / 11305 # issuecomment -674554010.

Только исполняемый файл, скомпилированный с помощью .NET _Framework_, показывает настоящую командную строку в Environment.CommandLine , поэтому testexe нужно было бы скомпилировать таким образом (и изменить (необязательно) для печати необработанной командной строки).

Чтобы протестировать приспособления для пакетных файлов, необходим отдельный тестовый _batch_ файл, чтобы убедиться, что 'a&b' передается как "a&b" и 'a"b' как "a""b" , для пример.

Компиляция @ mklement0 для .NET Framework невозможна и, вероятно, потребуется запустить ее под .NET Framework, чтобы добиться правильного поведения. Мы намеренно удалили всю компиляцию нативного кода из репозитория PS, и я не думаю, что мы хотим добавить его обратно ... Один из вариантов - иметь предварительно созданный собственный тестовый код (который должен быть скомпилирован для Windows, macOS, и различные дистрибутивы Linux (например, Alpine отдельно). Написать testexe легко, вся работа по его публикации займет время ...

В качестве альтернативы, можем ли мы просто положиться на простой сценарий bash для Linux / macOS для выдачи аргументов?

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

А на винде нечто подобное с батником.

Как насчет использования узла со сценарием .js?

console.log(process.execArgv.join('\n') или любая другая строка, обрабатывающая вас
хотите сделать, чтобы результат выглядел красиво?

@cspotcode , чтобы получить необработанную командную строку, нам нужен вызов WinAPI.

@ SteveL-MSFT:

В Windows вы можете делегировать компиляцию _Windows PowerShell_ через его интерфейс командной строки, что я и делаю в dbea ; вот простой пример, который создает исполняемый файл .NET _Framework_, который повторяет исходную командную строку (только), ./rawcmdline.exe :

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

  param([string] $exePath)

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

}

Образец звонка:

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

Что касается _batch-файла_, который повторяет свои аргументы, dbea также создает его по запросу .

В Unix простого сценария оболочки, как показано в вашем комментарии, действительно достаточно, и вы даже можете

@ PowerShell / комитет PowerShell обсудил это сегодня, мы просим @JamesWTruher обновить его PR, чтобы также включить в его экспериментальную функцию пропустить шаг в собственном командном процессоре, который восстанавливает массив аргументов обратно в строку и просто передает это в новый массив args в ProcessStartInfo (есть немного кода, чтобы убедиться, что имена и значения параметров совпадают должным образом). Кроме того, мы согласны с тем, что нам может понадобиться список разрешений для особых случаев известных команд, которые по-прежнему не работают с предложенным изменением, и это то, что можно добавить позже.

Для тех, кто, возможно, не заметил: PR опубликован (как WIP) и уже обсуждается: https://github.com/PowerShell/PowerShell/pull/13482

PS, @ SteveL-MSFT, относительно получения необработанной командной строки в Windows: конечно, альтернативой делегированию компиляции Windows PowerShell / .NET _Framework_ является улучшение существующего консольного приложения .NET _Core_ для создания (платформенно-условного) P / Invoke вызывает функцию GetCommandLine() WinAPI, как показано ниже.

using System;
using System.Runtime.InteropServices;

namespace demo
{
  static class ConsoleApp
  {
    [DllImport("kernel32.dll")]
    private static extern System.IntPtr GetCommandLineW();

    static void Main(string[] args)
    {
      Console.WriteLine("\n{0} argument(s) received (enclosed in <...> for delineation):\n", args.Length);
      for (int i = 0; i < args.Length; ++i)
      {
        Console.WriteLine("  <{0}>", args[i]);
      }

      // Windows only: print the raw command line.
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        Console.WriteLine("\nCommand line:\n\n  {0}\n", Marshal.PtrToStringUni(GetCommandLineW()));
      }

    }

  }

}

@ SteveL-MSFT

Он занимается обратной совместимостью.

Я думаю, что из вашего более позднего пояснения подразумевается, что ProcessStartInfo.ArgumentList (_collection_ _verbatim_ аргументов, которые используются как есть в Unix и переведены на MS C / C ++ - совместимую с соглашением командную строку в Windows самим .NET Core ) следует использовать, но позвольте мне заявить об этом прямо:

  • Правильное решение этой проблемы раз и навсегда исключает любые уступки в отношении обратной совместимости.

  • PR @JamesWTruher находится на правильном пути на момент написания этой статьи, единственная проблема, похоже, заключается в том, что пустые аргументы все еще не передаются .

    • Как только это будет решено, исправление будет завершено в _Unix_, но в нем отсутствуют важные приспособления для интерфейса командной строки в _Windows_ (см. Ниже).

нам может понадобиться список разрешений для особых случаев известных команд, которые все еще не работают с предложенным изменением, и это то, что можно добавить позже.

Я призываю вас не откладывать это на потом .

Вместо _allowlist_ (специальный регистр для конкретных исполняемых файлов), простые общие правила могут управлять приспособлениями , уточненными из приведенных выше после некоторого дальнейшего обсуждения с @TSlivede .

Эти приспособления, которые _ необходимы только в Windows_:

В частности, это:

  • Как и в Unix, по умолчанию используется ProcessStartInfo.ArgumentList за исключением случаев, когда выполняются _один или оба_ из следующих условий, _ в этом случае командная строка процесса должна быть создана вручную_ (и назначена ProcessStartInfo.Arguments как В настоящее время):

    • Пакетный файл ( .cmd , .bat ) или cmd.exe напрямую вызывается:
    • В этом случае _embedded_ " экранируется как "" (а не как \" ), а аргументы без пробелов, содержащие любой из следующих метасимволов cmd.exe также заключаются в двойные кавычки (обычно только аргументы _ с пробелами_ заключаются в двойные кавычки): " & | < > ^ , ; - это заставит вызовы командных файлов работать надежно, что важно, потому что многие высокопрофессиональные интерфейсы командной строки используют командные файлы как _entry точки_.
    • Самостоятельно (и, возможно, дополнительно), если хотя бы один аргумент, соответствующий регулярному выражению
      '^([/-]\w+[=:]|\w+=)(.*? .*)$' присутствует, все такие аргументы должны применять _частичные_ двойные кавычки только для _значения_ (что следует за : или = )

      • Например, аргументы msiexec.exe / msdeploy.exe и cmdkey.exe -стилем рассматриваются PowerShell дословно как
        FOO=bar baz и /foo:bar baz / -foo:bar baz будут помещены в командную строку процесса как
        foo="bar baz" или /foo:"bar baz" / -foo:"bar baz" создание _любых_ CLI, требующих этого стиля цитирования.
    • Дословные символы \ в аргументах должны обрабатываться в соответствии с соглашениями MS C / C ++.

На что _не__ распространяется действие этих приспособлений:

  • msiexec.exe (и, вероятно, msdeploye.exe тоже) поддерживает _only_ "" -экранирование _embedded_ " символов. , который _не__ охватом приведенных выше правил - кроме случаев, когда вы вызываете через командный файл или cmd /c .

    • Это должно быть достаточно редким для начала (например,
      msiexec.exe /i example.msi PROPERTY="Nat ""King"" Cole" ), но, вероятно, встречается еще реже из-за того, что вызовы misexec обычно _ делаются синхронно_ для ожидания окончания установки, и в этом случае вы можете избежать проблемы в одном из два пути:
    • cmd /c start /wait msiexec /i example.msi PROPERTY='Nat "King' Cole' - полагаться на вызовы cmd.exe (затем), запускающие "" -эскапирование
    • Start-Process -Wait msiexec '/i example.msi PROPERTY="Nat ""King"" Cole"' - полагаться на параметр -ArgumentList ( -Args ), дословно передающий единственный строковый аргумент в качестве командной строки процесса (хотя это не так, как этот параметр должен работать - см. №5576).
  • Любые другие нетрадиционные интерфейсы командной строки, для которых указанных выше приспособлений недостаточно - лично мне ничего не известно.

В конце концов, всегда есть обходной путь: вызовите через cmd /c или, для неконсольных приложений, через Start-Process , или используйте --% ; если и когда мы предоставим командлет ins ( Invoke-NativeShell ), это будет другой вариант; dbea ( Debug-ExecutableArguments командлет с echoArgs.exe -подобными возможностями, но по запросу также и для пакетных файлов, также поможет _диагностировать_ проблемы.


Что касается пути к критическому изменению по сравнению с подпиской:

  • Означает ли реализация этой экспериментальной функции, что, если проявится достаточный интерес, она станет поведением _default_ и, следовательно, приведет к (нетривиальному) критическому изменению?

  • Не могли бы вы убедиться, что эта экспериментальная функция широко известна, учитывая ее важность?

    • Общее беспокойство по поводу экспериментальных функций вызывает то, что их использование может быть _неактуальным_ в предварительных версиях, учитывая, что _все_ экспериментальные функции включены по умолчанию. Мы определенно хотим, чтобы люди знали об этой функции и использовали ее сознательно.
Была ли эта страница полезной?
0 / 5 - 0 рейтинги