Como você pode usar a propriedade de um objeto em uma string entre aspas?

96

Eu tenho o seguinte código:

$DatabaseSettings = @();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;

Quando tento usar uma das propriedades em uma string execute o comando:

& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
  "RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"

Ele tenta usar apenas o valor de $DatabaseSettingsem vez do valor de $DatabaseSettings[0].DatabaseName, que não é válido.
Minha solução alternativa é copiá-lo em uma nova variável.

Como posso acessar a propriedade do objeto diretamente em uma string entre aspas?

caveman_dick
fonte

Respostas:

163

Quando você coloca o nome de uma variável em uma string entre aspas duplas, ele será substituído pelo valor dessa variável:

$foo = 2
"$foo"

torna-se

"2"

Se você não quiser, use aspas simples:

$foo = 2
'$foo'

No entanto, se você deseja acessar propriedades ou usar índices em variáveis ​​em uma string entre aspas duplas, você deve colocar essa subexpressão em $():

$foo = 1,2,3
"$foo[1]"     # yields "1 2 3[1]"
"$($foo[1])"  # yields "2"

$bar = "abc"
"$bar.Length"    # yields "abc.Length"
"$($bar.Length)" # yields "3"

O PowerShell apenas expande variáveis ​​nesses casos, nada mais. Para forçar a avaliação de expressões mais complexas, incluindo índices, propriedades ou mesmo cálculos completos, você deve incluí-los no operador de subexpressão $( )que faz com que a expressão interna seja avaliada e incorporada na string.

Joey
fonte
Isso funciona, mas eu me pergunto se também posso apenas concatenar strings como estava tentando evitar em primeiro lugar
ozzy432836
2
@ ozzy432836 Você pode, é claro. Ou use strings de formato. Geralmente não importa e se resume à preferência pessoal.
Joey
15

@Joey tem a resposta correta, mas apenas para acrescentar um pouco mais por que você precisa forçar a avaliação com $():

Seu código de exemplo contém uma ambigüidade que aponta para o motivo pelo qual os criadores do PowerShell podem ter optado por limitar a expansão a meras referências de variáveis ​​e não oferecer suporte ao acesso às propriedades também (como um aparte: a expansão da string é feita chamando o ToString()método no objeto, que pode explicar alguns resultados "estranhos").

Seu exemplo contido no final da linha de comando:

...\$LogFileName.ldf

Se as propriedades dos objetos foram expandidas por padrão, o acima seria resolvido para

...\

uma vez que o objeto referenciado por $LogFileNamenão teria uma propriedade chamada ldf, $null(ou uma string vazia) seria substituída pela variável.

Steven Murawski
fonte
1
Belo achado. Na verdade, eu não tinha certeza de qual era o problema dele, mas tentar acessar as propriedades de dentro de uma string cheirava mal :)
Joey
Obrigado rapazes! Johannes, por que você acha que acessar propriedades em uma string cheira mal? Como sugeriria que isso seja feito?
caveman_dick
homem das cavernas: Foi algo que me chamou a atenção como uma causa potencial de erro. Você certamente pode fazer isso e não é nada estranho, mas ao omitir o operador $ () ele grita "Falha" porque não pode funcionar. Portanto, minha resposta foi apenas um palpite sobre qual poderia ser o seu problema, usando a primeira causa de falha que pude ver. Desculpe se parecia, mas aquele comentário não se referia a nenhuma prática recomendada ou algo assim.
Joey
Que bom que você me preocupou! ;) Quando comecei a usar o PowerShell, pensei que a variável em uma string era um pouco estranha, mas é bastante útil. Eu simplesmente não consegui encontrar um lugar definitivo onde posso procurar ajuda de sintaxe.
caveman_dick
9

@Joey tem uma boa resposta. Há outra maneira com uma aparência mais .NET com um equivalente String.Format, eu prefiro ao acessar propriedades em objetos:

Coisas sobre um carro:

$properties = @{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }

Crie um objeto:

$car = New-Object -typename psobject -Property $properties

Interpolar uma string:

"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package

Saídas:

# The red car is a nice sedan that is fully loaded
loonison101
fonte
Sim, é mais legível, especialmente se você estiver acostumado com o código .net. Obrigado! :)
caveman_dick
3
Há um problema a ser observado quando você usa strings de formato pela primeira vez, que geralmente é necessário colocar a expressão entre parênteses. Você não pode, por exemplo, simplesmente escrever, write-host "foo = {0}" -f $foopois o PowerShell tratará -fcomo o ForegroundColorparâmetro para write-host. Neste exemplo, você precisa write-host ( "foo = {0}" -f $foo ). Esse é o comportamento padrão do PowerShell, mas vale a pena observar.
andyb
Só para ser pedante, o exemplo acima não é "interpolação de strings", é "formatação composta". Uma vez que o Powershell está inextricavelmente ligado ao .NET, para o qual C # e VB.NET são as linguagens de programação primárias, os termos "interpolação de string" e "string interpolada" (e qualquer coisa semelhante) devem ser aplicados apenas às novas $ strings prefixadas encontradas nesses linguagens (C # 6 e VB.NET 14). Nessas strings, as expressões de parâmetro são incorporadas diretamente na string em vez de referências numéricas de parâmetros, com as expressões reais sendo argumentos para String.Format.
Uber Kluger
@andyb, em relação ao formato de string "pegadinhas". Esta é realmente a diferença entre os modos Expressão e Argumento (consulte about_Parsing ). Os comandos são analisados ​​em tokens (grupos de caracteres formando uma string significativa). O espaço separa os tokens que seriam mesclados, mas seriam ignorados. Se o primeiro token do comando é um número, variável, palavra-chave de instrução (if, while, etc), operador unário, {, etc ... então o modo de análise é Expressiondiferente Argument(até um terminador de comando).
Uber Kluger
8

Nota de documentação: Get-Help about_Quoting_Rulescobre a interpolação de strings, mas, a partir do PSv5, não em profundidade.

Para complementar a resposta útil de Joey com um resumo pragmático da expansão de string do PowerShell (interpolação de string em strings entre aspas duplas ( "..."), incluindo strings here entre aspas duplas ):

  • Apenas referências como $foo, $global:foo(ou $script:foo, ...) e$env:PATH (variáveis ​​de ambiente) são reconhecidas quando diretamente embutidas em uma "..."string - isto é, apenas a própria referência de variável é expandida, independentemente do que segue.

    • Para eliminar a ambigüidade de um nome de variável de caracteres subsequentes na string, coloque-o entre {e} ; por exemplo, ${foo}.
      Isto é especialmente importante se o nome da variável é seguido por um :, como PowerShell de outra forma, considerar tudo entre a $ea :um âmbito especificador, normalmente fazendo com que a interpolação para falhar ; por exemplo, "$HOME: where the heart is."quebra, mas "${HOME}: where the heart is."funciona como pretendido.
      (Alternativamente, `escapam a o :: "$HOME`: where the heart is.").

    • Para tratar a $ou a "como literal , prefixe-o com escape char. `(um backtick ); por exemplo:
      "`$HOME's value: `"$HOME`""

  • Para qualquer outra coisa, incluindo o uso de subscritos de array e acesso às propriedades de uma variável de objeto , você deve colocar a expressão em$(...) , o operador de subexpressão (por exemplo, "PS version: $($PSVersionTable.PSVersion)"ou "1st el.: $($someArray[0])")

    • Usar $(...)even permite que você insira a saída de linhas de comando inteiras em strings entre aspas (por exemplo, "Today is $((Get-Date).ToString('d')).").
  • Os resultados da interpolação não são necessariamente iguais ao formato de saída padrão (o que você veria se imprimisse a variável / subexpressão diretamente no console, por exemplo, que envolve o formatador padrão; consulte Get-Help about_format.ps1xml):

    • Coleções , incluindo matrizes, são convertidas em strings colocando um único espaço entre as representações de string dos elementos (por padrão; um separador diferente pode ser especificado configurando $OFS). Por exemplo, "array: $(@(1, 2, 3))"rendimentosarray: 1 2 3

    • Instâncias de qualquer outro tipo (incluindo os elementos de colecções que não são eles próprios colecções) são Stringified por qualquer ligando para o IFormattable.ToString()método com a cultura invariante , se o tipo do exemplo suporta a IFormattableinterface de [1] , ou ligando .psobject.ToString(), que na maioria dos casos, simplesmente invoca o .ToString()método do tipo .NET subjacente [2] , que pode ou não fornecer uma representação significativa: a menos que um tipo (não primitivo) tenha sobrescrito especificamente o .ToString()método, tudo o que você obterá é o nome completo do tipo (por exemplo, "hashtable: $(@{ key = 'value' })"rendimentos hashtable: System.Collections.Hashtable).

    • Para obter a mesma saída do console , use uma subexpressão e um pipe paraOut-String e aplique .Trim()para remover quaisquer linhas vazias iniciais e finais, se desejado; por exemplo,
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"rendimentos:

      hashtable:                                                                                                                                                                          
      Name                           Value                                                                                                                                               
      ----                           -----                                                                                                                                               
      key                            value      
      

[1] Este comportamento talvez surpreendente significa que, para tipos que suportam representações sensíveis à cultura, $obj.ToString()produz uma representação apropriada à cultura atual , enquanto "$obj"(interpolação de strings) sempre resulta em uma representação invariante à cultura - veja esta minha resposta .

[2] Substituições notáveis:
* A stringificação de coleções discutida anteriormente (lista de elementos separados por espaço em vez de algo semelhante System.Object[]).
* O hashtable- como representação de [pscustomobject]casos (explicado aqui ), em vez do que a cadeia vazia .

mklement0
fonte