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

Примечание. Резюме этого вопроса с тех пор было опубликовано в репозитории 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 (и пытается скрыть).

Другие вопросы по тегам