Расширение переменной и выражения свойства
Анализатор PowerShell интерпретирует строки, Расширение переменной и Выражения свойства в строках по-разному. Эта разница меняет способ создания списка аргументов и его передачи в исполняемый файл.
Именно поэтому генерируются списки аргументов:
# By Object Property Access
docker --tag $imageInfo.FullImageName`:$version
# -- Doesn't work --
# By Variable Expansion
docker --tag $imageInfo`:$version
# -- Works --
# By Sub Expression Operator
docker --tag $($imageInfo.FullImageName)`:$version
# -- Doesn't work --
# By Double quoted string + Sub Expression Operator
docker --tag "$($imageInfo.FullImageName):$version"
# -- Works --
Уровень 0: TLDR
Для доступа к свойству объекта необходимо использовать комбинацию оператора подвыражения $()
, и заключают всю строку в двойные кавычки, чтобы ее можно было правильно интерпретировать. Это также устраняет необходимость избежать обратного тика. Итак, полное решение:
docker image build --tag "$($imageInfo.FullImageName):$version" --file .\Dockerfile .
Уровень 1. Интерпретация аргументов
PowerShell запускает команды через собственный синтаксический анализатор перед передачей аргументов во внешние программы, например, так, чтобы он мог выполнить подстановку переменных et c. На высоком уровне мы можем увидеть, как PowerShell интерпретирует команду и какие аргументы передаются, запустив инструмент EchoArgs.exe и отображая, какие аргументы передаются (я упростил примеры, чтобы проиллюстрировать, что происходит ):
#Setup Variables for MCVE:
$imageInfo = New-Object -TypeName psobject
$imageInfo | Add-Member -MemberType NoteProperty -Name FullImageName -Value "ImageName"
$strImageName = "ImageName"
$version = "v1.2.3"
По доступу к свойству объекта
PS C:\> EchoArgs.exe docker --tag $imageInfo.FullImageName`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName>
Arg 3 is <:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName :v1.2.3
Здесь мы видим, что $imageInfo.FullImageName
вычисляется и передается как один аргумент (Arg 2) и обратите внимание, что и двоеточие и строка версии объединяются и интерпретируются как новый аргумент (аргумент 3). Это приводит к тому, что конечная результирующая командная строка, которая будет выполняться, будет иметь пробел между именем образа и версией: ImageName :v1.2.3
Это, очевидно, приведет к путанице docker и любого другого аналогичного исполняемого файла.
По расширению переменной
PS C:\> EchoArgs.exe docker --tag $strImageName`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName:v1.2.3
Используя строковую переменную, мы видим, что аргументы интерпретируются правильно. Он расширяет строковые переменные и правильно сохраняет все вместе как один аргумент (Arg 2).
По оператору подвыражения
PS C:\> EchoArgs.exe docker --tag $($imageInfo.FullImageName)`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName>
Arg 3 is <:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName :v1.2.3
Как и в первом примере, мы можно увидеть, что $($imageInfo.FullImageName)
был оценен и передан как один аргумент (Arg 2) и что двоеточие и строка версии интерпретируются как новый аргумент (Arg 3). Это не работает.
По строке в двойных кавычках + оператор подвыражения
PS C:\> EchoArgs.exe docker --tag "$($imageInfo.FullImageName):$version"
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName:v1.2.3
В этом случае в двойных кавычках содержится вся строка. Подвыражение $($imageInfo.FullImageName)
вычисляется первым. Поскольку строки с двоеточием и $version
также содержатся в одних и тех же двойных кавычках, вся строка должна быть полностью оценена, прежде чем она будет возвращена в качестве одного аргумента (Arg 2).
Уровень 2: Аннотация Синтаксическое дерево
Я думал, что $imageInfo.FullImageName
и $strImageName
обе строки, так почему они обрабатываются по-разному?
Мы можем использовать EchoArgs.exe чтобы увидеть, что с ними обращаются по-разному, но чтобы эффективно ответить на этот вопрос, нам нужно понять, как PowerShell анализирует команду. В конце концов, если мы знаем, что это проблема анализатора PowerShell, как анализатор интерпретирует то, что мы делаем?
Не пытайтесь анализировать код PowerShell в PowerShell
Вместо этого используйте анализатор PowerShell!
Давайте использовать собственное абстрактное синтаксическое дерево PowerShell для интерпретации происходящего!
Доступ к свойству объекта
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $imageInfo.FullImageName`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $imageInfo.FullImageName`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $imageInfo.FullImageName`:$version
Expression : $imageInfo
Member : FullImageName
Static : False
StaticType : System.Object
Extent : $imageInfo.FullImageName
Parent : docker --tag $imageInfo.FullImageName`:$version
Value : :$version
StringConstantType : BareWord
NestedExpressions : {$version}
StaticType : System.String
Extent : `:$version
Parent : docker --tag $imageInfo.FullImageName`:$version
Здесь мы видим, что docker
и --tag
интерпретируются как StringConstantType : BareWord
токены. $imageInfo.FullImageName
интерпретируется как Expression : $imageInfo
, что требует его оценки. И наконец, :$version
также интерпретируется как StringConstantType : BareWord
. Это напрямую соответствует нашим 4 аргументам, которые мы видели ранее. Итак, как насчет простой переменной?
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $strImageName`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $strImageName`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $strImageName`:$version
Value : $strImageName:$version
StringConstantType : BareWord
NestedExpressions : {$strImageName, $version}
StaticType : System.String
Extent : $strImageName`:$version
Parent : docker --tag $strImageName`:$version
Ну, это другое. Он видит $strImageName:$version
как один тип StringConstantType : BareWord
токена. PowerShell понимает, как обрабатывать простые переменные раскрытия строк и при этом возвращать один строковый объект. т.е. все переменные строки StaticType : System.String
, которые могут быть объединены в одну против предыдущий пример, где у нас были и StaticType : System.Object
, и System.String
, которые, хотя на следующем шаге превращаются в строки, здесь они разных типов и поэтому не будут объединяться.
Оператором подвыражения
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $($imageInfo.FullImageName)`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $($imageInfo.FullImageName)`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $($imageInfo.FullImageName)`:$version
SubExpression : $imageInfo.FullImageName
StaticType : System.Object
Extent : $($imageInfo.FullImageName)
Parent : docker --tag $($imageInfo.FullImageName)`:$version
Value : :$version
StringConstantType : BareWord
NestedExpressions : {$version}
StaticType : System.String
Extent : `:$version
Parent : docker --tag $($imageInfo.FullImageName)`:$version
SubExpression : $imageInfo.FullImageName
интерпретируется как StaticType : System.Object
, который нельзя комбинировать с System.String
.
By Double строка в кавычках + оператор подвыражения
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag "$($imageInfo.FullImageName):$version"', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag "$($imageInfo.FullImageName):$version"
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag "$($imageInfo.FullImageName):$version"
Value : $($imageInfo.FullImageName):$version
StringConstantType : DoubleQuoted
NestedExpressions : {$($imageInfo.FullImageName), $version}
StaticType : System.String
Extent : "$($imageInfo.FullImageName):$version"
Parent : docker --tag "$($imageInfo.FullImageName):$version"
Здесь мы видим разницу. "$($imageInfo.FullImageName):$version"
интерпретируется как одиночный StringConstantType : DoubleQuoted
строковый токен. У него есть вложенные выражения, но он анализируется как один токен и, следовательно, будет возвращен как один аргумент.