Токены без кавычек в режиме аргументов, включающие ссылки на переменные и подвыражения: почему они иногда разделяются на несколько аргументов?
Примечание. Резюме этого вопроса с тех пор было опубликовано в репозитории PowerShell GitHub, поскольку заменено этой более полной проблемой.
Аргументы, передаваемые команде в PowerShell, анализируются в режиме аргумента (в отличие от режима выражения - см. Get-Help about_Parsing
).
Удобно, что (двойное) цитирование аргументов, которые не содержат пробелов или метасимволов, обычно необязательно, даже если эти аргументы включают ссылки на переменные (например, $HOME\sub
) или подвыражения (например, version=$($PsVersionTable.PsVersion)
,
По большей части такие аргументы без кавычек обрабатываются так, как если бы они были строками в двойных кавычках, и применяются обычные правила интерполяции строк (за исключением того, что метасимволы, такие как ,
нужно убежать).
В этом ответе я попытался обобщить правила разбора токенов без кавычек в режиме аргумента, но есть любопытные крайние случаи:
В частности (с Windows PowerShell v5.1), почему маркер аргумента без кавычек в каждой из следующих команд НЕ распознается как одна расширяемая строка, и в результате передается 2 аргумента (при этом переменная ссылка / подвыражение сохраняет свой тип)?
$(...)
в начале токена:Write-Output $(Get-Date)/today # -> 2 arguments: [datetime] obj. and string '/today'
Обратите внимание, что следующая работа, как ожидалось:
Write-Output $HOME/sub
- простой вар. ссылка в началеWrite-Output today/$(Get-Date)
- подвыражение не в начале
.$
в начале токена:Write-Output .$HOME # -> 2 arguments: string '.' and value of $HOME
Обратите внимание, что следующая работа, как ожидалось:
Write-Output /$HOME
- другой начальный символ. предшествующий$
Write-Output .-$HOME
- начальный.
непосредственно не сопровождается$
Write-Output a.$HOME
-.
это не начальный символ
Как в стороне: Начиная с PowerShell Core v6.0.0-alpha.15, =
после простой вар. ссылка в начале токена также, кажется, разбивает токен на 2 аргумента, чего нет в Windows PowerShell v5.1; например, Write-Output $HOME=dir
,
Замечания:
В первую очередь я ищу обоснование дизайна для описанного поведения или, в зависимости от обстоятельств, подтверждение того, что это ошибка. Если это не ошибка, я хочу, чтобы что-то помогло мне осмыслить поведение, чтобы я мог запомнить его и избежать его ошибок.
Все эти крайние случаи можно избежать с помощью явных двойных кавычек, которые, учитывая неочевидное поведение, описанное выше, могут быть самым безопасным выбором для повседневного использования.
Дополнительное чтение: состояние документации и проектные размышления
На момент написания статьи v5.1 Get-Help about_Parsing
страница:
не полностью описывает правила
используются термины, которые не определены ни в теме, ни обычно используются в мире PowerShell ("расширяемая строка", "выражение значения" - хотя можно догадаться об их значении)
Со связанной страницы (выделение добавлено):
В режиме аргумента каждое значение рассматривается как расширяемая строка, если только оно не начинается с одного из следующих специальных символов: знак доллара (
$
), в знак (@
), одинарная кавычка ('
), двойная кавычка ("
) или открывающая скобка ((
).Если ему предшествует один из этих символов, значение рассматривается как выражение значения.
Как в стороне: токен, который начинается с "
это, конечно, по определению также расширяемая строка (интерполирующая строка).
Любопытно, что концептуальная тема справки о цитировании, Get-Help about_Quoting_Rules
, удается избежать как терминов "расширить" и "интерполировать".
Обратите внимание, что в отрывке не говорится о том, что происходит, когда (немета) символы непосредственно следуют за токеном, который начинается с этих специальных символов, а именно $
,
Однако страница содержит пример, который показывает, что токен, начинающийся со ссылки на переменную, также интерпретируется как расширяемая строка:
- С
$a
содержащий4
,Write-Output $a/H
оценивается как (строковый аргумент)4/H
,
Обратите внимание, что этот отрывок подразумевает, что ссылки на переменные / подвыражения внутри маркера без кавычек (который не начинается со специального символа) расширяются, как будто внутри строки в двойных кавычках ("рассматривается как расширяемая строка").
Если это работает:
$a = 4
Write-Output $a/H # -> '4/H'
Write-Output H/$a # -> 'H/4'
Write-Output H/$(2 + 2) # -> 'H/4'
почему бы не Write-Output $(2 + 2)/H
расширить до '4/H'
тоже (вместо того, чтобы рассматриваться как 2 аргумента?
Почему подвыражение в начале трактуется иначе, чем ссылка на переменную?
Такие тонкие различия трудно запомнить, особенно при отсутствии обоснования.
Правило, которое имеет для меня больше смысла, - это безоговорочно обрабатывать токен, который начинается с $
и имеет дополнительные символы, следующие за переменной / подвыражением в виде расширяемой строки.
(В отличие от этого, для автономной ссылки на переменную / подвыражения имеет смысл сохранить свой тип, как это происходит сейчас.)
Обратите внимание, что случай токена, который начинается с .$
разделение на 2 аргумента вообще не рассматривается в разделе справки.
Еще более необязательное чтение: после токена, который начинается с одного из других специальных символов с дополнительными символами.
Среди других специальных символов, начинающих токен, следующие безоговорочно обрабатывают любые символы, которые следуют за концом конструкции, как отдельный аргумент (что имеет смысл): ( ' "
Write-Output (2 + 2)/H # -> 2 arguments: 4 and '/H'
Write-Output "2 + $a"/H # -> 2 arguments: '2 + 4' and '/H', assuming $a equals 4
Write-Output '2 + 2'/H # -> 2 arguments: '2 + 2' and '/H'
Как в сторону: это показывает, что bash
- конкатенация строк в стиле - размещение любого набора токенов в кавычках и без кавычек рядом друг с другом - обычно не поддерживается в PowerShell; это работает, только если первая ссылка на подстроку / переменную не заключена в кавычки. Например, Write-Output H/'2 + 2'
в отличие от приведенного выше примера с изменением подстрок выдает только один аргумент.
Исключение составляет @
: в то время как @
имеет особое значение (см. Get-Help about_Splatting
) когда следует только синтаксически допустимое имя переменной (например, @parms
), все остальное заставляет токен снова обрабатываться как расширяемая строка:
Write-Output @parms # splatting (results in no arguments if $parms is undefined)
Write-Output @parms$a # *expandable string*: '@parms4', if $a equals 4
1 ответ
Я думаю, что вы что-то вроде удара здесь больше типа "намека", чем что-либо еще.
Вы используете Write-Output, который в своем резюме указывает, что
Отправляет указанные объекты следующей команде в конвейере.
Эта команда предназначена для приема в массиве. Когда он попадает в первый элемент как строка, как сегодня / он обрабатывает его как строку. Когда первый элемент заканчивается результатом вызова функции, это может быть или не быть строкой, поэтому он запускает массив.
Это говорит о том, что если вы запустите ту же команду для Write-Host (которая предназначена для получения строки для вывода), она будет работать так, как вы ожидаете:
Write-Host $(Get-Date)/today
Выходы
25.07.2008 13:30:43 / сегодня
Так что я думаю, что вы - крайние случаи, с которыми вы сталкиваетесь, - это не столько анализ, а больше о типизации, которую использует PowerShell (и пытается скрыть).