PowerShell сбрасывает двойные кавычки из аргументов командной строки

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

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

PS C:\Documents and Settings\Nick> echo '"hello"'
"hello"
PS C:\Documents and Settings\Nick> echo.exe '"hello"'
hello
PS C:\Documents and Settings\Nick> echo.exe '\"hello\"'
"hello"

Обратите внимание, что двойные кавычки передаются в командлет PowerShell echo, но при передаче в качестве аргумента echo.exe двойные кавычки удаляются без экранирования обратная косая черта (даже несмотря на то, что escape-символ PowerShell - это обратная сторона, а не обратная косая черта).

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

Что здесь происходит?

В настоящее время исправление заключается в выводе аргументов командной строки в соответствии с этими правилами (которые, по-видимому, используются при вызове API CreateProcess, который PowerShell использует для вызова файлов .exe):

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

Обратите внимание, что для избежания двойных кавычек в экранированной строке Windows API в PowerShell может потребоваться дополнительное экранирование двойных кавычек.

Вот несколько примеров: echo.exe из GnuWin32:

PS C:\Documents and Settings\Nick> echo.exe "\`""
"
PS C:\Documents and Settings\Nick> echo.exe "\\\\\`""
\\"
PS C:\Documents and Settings\Nick> echo.exe "\\"
\\

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

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

Ответ 1

Это известная вещь:

Это FAR TOO HARD для передачи параметров приложениям, для которых требуются строки с кавычками. Я задал этот вопрос в IRC "вместительным" экспертам PowerShell, и для того, чтобы кто-то понял, мне потребовался час (я изначально начал публиковать здесь, что это просто невозможно). Это полностью разрушает способность PowerShell служить оболочкой общего назначения, потому что мы не можем делать простые вещи, такие как выполнение sqlcmd. В задании номер один командной оболочки должны быть запущены приложения с командной строкой... В качестве примера, пытаясь использовать SqlCmd из SQL Server 2008, существует параметр -v, который принимает ряд параметров имени: значение. Если значение имеет пробелы в нем, вы должны процитировать его...

... нет единственного способа написать командную строку для правильного вызова этого приложения, поэтому даже после того, как вы освоите все 4 или 5 разных способах цитирования и выхода из них, вы все еще догадываетесь о том, что будет работать, когда... или вы можете просто выложить на cmd и сделать с ним.

Ответ 2

Лично я избегаю использования "\" для экранирования вещей в PowerShell, потому что технически это не экранирующий символ оболочки. Я получил непредсказуемые результаты с ним. В строках с двойными кавычками вы можете использовать "", чтобы получить встроенную двойную кавычку, или экранировать ее обратной галочкой:

PS C:\Users\Droj> "string ""with'" quotes"
string "with" quotes

То же самое касается одинарных кавычек:

PS C:\Users\Droj> 'string ''with'' quotes'
string 'with' quotes

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

Итак, если у меня есть массив, я могу установить в значениях аргументов кавычки:

PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> echo $aa
arg="foo"
arg=""""bar""""

Аргумента 'bar' достаточно, чтобы покрыть дополнительную скрытую оценку. Как будто я отправляю это значение командлету в двойных кавычках, а затем отправляю этот результат снова в двойных кавычках:

PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one
arg=""bar""
PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level
arg="bar"

Можно ожидать, что эти аргументы будут передаваться во внешние команды как есть, как и в командлеты типа 'echo'/'write-output', но это не так, из-за этого скрытого уровня:

PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> start c:\cygwin\bin\echo $aa -nonew -wait
arg=foo arg="bar"

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

PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> iex "echo $aa"
arg=foo
arg="bar"

... именно это я и получаю, отправляя эти аргументы моему внешнему экземпляру Cygwin 'echo.exe':

PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""'
arg=foo arg="bar"

Ответ 3

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

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

Например, я обнаружил этот вопрос при исследовании проблемы исчезновения кавычек при запуске сценария PowerShell из кода С# с использованием Process.Start. На самом деле проблема заключалась в том, что С# Process Start нужны аргументы с двойными кавычками - они исчезают.

Ответ 4

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

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

$args = New-Object System.Collections.ArrayList
$args.Add("-U") | Out-Null
$args.Add($cred.UserName) | Out-Null
$args.Add("-P") | Out-Null
$args.Add("""$($cred.Password)""")
$args.Add("-i") | Out-Null
$args.Add("""$SqlScriptPath""") | Out-Null
& SQLCMD $args

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

При необходимости вы можете протестировать и отладить его с помощью EchoArgs из Расширений сообщества PowerShell.