¿Cómo se puede utilizar la propiedad de un objeto en una cadena entre comillas dobles?

96

Tengo el siguiente 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;

Cuando intento usar una de las propiedades en un comando de ejecución de cadena:

& "$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'"

Intenta usar simplemente el valor de en $DatabaseSettingslugar del valor de $DatabaseSettings[0].DatabaseName, que no es válido.
Mi solución es copiarlo en una nueva variable.

¿Cómo puedo acceder a la propiedad del objeto directamente en una cadena entre comillas dobles?

Caveman_dick
fuente

Respuestas:

163

Cuando incluya el nombre de una variable en una cadena entre comillas dobles, será reemplazado por el valor de esa variable:

$foo = 2
"$foo"

se convierte en

"2"

Si no lo desea, debe usar comillas simples:

$foo = 2
'$foo'

Sin embargo, si desea acceder a las propiedades o utilizar índices en variables en una cadena entre comillas dobles, debe incluir esa subexpresión en $():

$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"

PowerShell solo expande variables en esos casos, nada más. Para forzar la evaluación de expresiones más complejas, incluidos índices, propiedades o incluso cálculos completos, debe encerrarlos en el operador de subexpresión $( )que hace que la expresión interna se evalúe e incruste en la cadena.

Joey
fuente
Esto funciona, pero me pregunto si también puedo concatenar cadenas como estaba tratando de evitar en primer lugar
ozzy432836
2
@ ozzy432836 Puede, por supuesto. O use cadenas de formato. Por lo general, no importa y se reduce a preferencias personales.
Joey
15

@Joey tiene la respuesta correcta, pero solo para agregar un poco más sobre por qué necesita forzar la evaluación con $():

Su código de ejemplo contiene una ambigüedad que indica por qué los creadores de PowerShell pueden haber optado por limitar la expansión a meras referencias variables y no admitir el acceso a las propiedades también (como nota al margen: la expansión de cadenas se realiza llamando al ToString()método en el objeto, que puede explicar algunos resultados "extraños").

Su ejemplo contenido al final de la línea de comando:

...\$LogFileName.ldf

Si las propiedades de los objetos se expandieran de forma predeterminada, lo anterior se resolvería como

...\

ya que el objeto referenciado por $LogFileNameno tendría una propiedad llamada ldf, $null(o una cadena vacía) se sustituirá por la variable.

Steven Murawski
fuente
1
Buen hallazgo. De hecho, no estaba completamente seguro de cuál era su problema, pero intentar acceder a las propiedades desde una cadena olía mal :)
Joey
¡Gracias chicos! Johannes, ¿por qué cree que el acceso a propiedades en una cadena huele mal? ¿Cómo sugeriría que se haga?
caveman_dick
hombre de las cavernas: Simplemente fue algo que me llamó la atención como una posible causa de error. Ciertamente puede hacerlo y no es nada extraño, pero al omitir el operador $ () grita "Fallo" porque no puede funcionar. Por lo tanto, mi respuesta fue solo una conjetura sobre cuál podría ser su problema, utilizando la primera causa de falla posible que pude ver. Lo siento si lo parecía, pero ese comentario no se refería a ninguna de las mejores prácticas.
Joey
¡Qué bueno que me tenías preocupado allí! ;) Cuando comencé a usar powershell, pensé que la variable en una cadena era un poco extraña, pero es bastante útil. Simplemente no he logrado encontrar un lugar definitivo donde pueda buscar ayuda de sintaxis.
caveman_dick
9

@Joey tiene una buena respuesta. Hay otra forma con un aspecto más .NET con un String.Format equivalente, lo prefiero al acceder a las propiedades de los objetos:

Cosas sobre un automóvil:

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

Crea un objeto:

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

Interpolar una cadena:

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

Salidas:

# The red car is a nice sedan that is fully loaded
loonison101
fuente
Sí, es más legible, especialmente si estás acostumbrado al código .net. ¡Gracias! :)
caveman_dick
3
Hay un problema que debe tener en cuenta cuando usa cadenas de formato por primera vez, y es que a menudo deberá encerrar la expresión entre paréntesis. No puede, por ejemplo, simplemente escribir write-host "foo = {0}" -f $fooya que PowerShell lo tratará -fcomo el ForegroundColorparámetro para write-host. En este ejemplo, necesitaría write-host ( "foo = {0}" -f $foo ). Este es el comportamiento estándar de PowerShell, pero vale la pena señalarlo.
andyb
Para ser pedante, el ejemplo anterior no es "interpolación de cadenas", es "formato compuesto". Dado que Powershell está inextricablemente ligado a .NET para el cual C # y VB.NET son los lenguajes de programación principales, los términos "interpolación de cadenas" y "cadena interpolada" (y cualquier cosa similar) deben aplicarse solo a las nuevas cadenas $ antepuestas que se encuentran en esos idiomas (C # 6 y VB.NET 14). En estas cadenas, las expresiones de parámetros se incrustan directamente en la cadena en lugar de referencias numéricas de parámetros, y las expresiones reales son argumentos para String.Format.
Uber Kluger
@andyb, respecto a la cadena de formato "gotchas". Esta es en realidad la diferencia entre los modos Expresión y Argumento (consulte about_Parsing ). Los comandos se analizan en tokens (grupos de caracteres que forman una cadena significativa). El espacio separa los tokens que se fusionarían pero que de lo contrario se ignoran. Si el primer token del comando es un número, variable, palabra clave de declaración (si, while, etc.), operador unario, {, etc ... entonces el modo de análisis es de Expressionotro modo Argument(hasta un terminador de comando).
Uber Kluger
8

Nota de documentación: Get-Help about_Quoting_Rulescubre la interpolación de cadenas, pero, a partir de PSv5, no en profundidad.

Para complementar la útil respuesta de Joey con un resumen pragmático de la expansión de cadenas de PowerShell (interpolación de cadenas en cadenas entre comillas dobles ( "..."), incluso en cadenas aquí entre comillas dobles ):

  • Solamente las referencias tales como $foo, $global:foo(o $script:foo, ...) y$env:PATH (variables de entorno) se reconocen cuando directamente incrustado en una "..."cadena - que es, sólo la referencia variable en sí se expande, con independencia de lo que sigue.

    • Para eliminar la ambigüedad de un nombre de variable de los caracteres subsiguientes en la cadena, enciérrelo entre {y} ; ej ${foo}.
      Esto es especialmente importante si el nombre de la variable va seguido de una :, ya que PowerShell consideraría todo lo que se encuentra entre$ y el :de un alcance especificador, normalmente causando la interpolación a fallar ; por ejemplo, se "$HOME: where the heart is."rompe, pero "${HOME}: where the heart is."funciona según lo previsto.
      (Alternativamente, `-Escape la :: "$HOME`: where the heart is.").

    • Para tratar un $ o a "como un literal , antepóngalo con escape char. `(una tilde ); p.ej:
      "`$HOME's value: `"$HOME`""

  • Para cualquier otra cosa, incluido el uso subíndices de matriz y el acceso a las propiedades de una variable de objeto , debe incluir la expresión en$(...) el operador de subexpresión (p. Ej., "PS version: $($PSVersionTable.PSVersion)"O "1st el.: $($someArray[0])")

    • El uso de $(...)even le permite incrustar la salida de líneas de comando completas en cadenas entre comillas dobles (por ejemplo, "Today is $((Get-Date).ToString('d')).").
  • Los resultados de la interpolación no necesariamente tienen el mismo aspecto que el formato de salida predeterminado (lo que vería si imprimiera la variable / subexpresión directamente en la consola, por ejemplo, que involucra el formateador predeterminado; consulte Get-Help about_format.ps1xml):

    • Colecciones , incluyendo matrices, se convierten en cadenas por la colocación de un único espacio entre las representaciones de cadena de los elementos (por defecto; un separador diferente puede ser especificado por el ajuste $OFS) por ejemplo, "array: $(@(1, 2, 3))"los rendimientosarray: 1 2 3

    • Las instancias de cualquier otro tipo (incluyendo elementos de colecciones que no son en sí mismos colecciones) se stringified por ya sea llamando al IFormattable.ToString()método con la cultura invariante , si el tipo de la instancia soporta la IFormattableinterfaz de [1] , o llamando .psobject.ToString(), que en la mayoría de los casos simplemente invoca el .ToString()método del tipo .NET subyacente [2] , que puede o no dar una representación significativa: a menos que un tipo (no primitivo) haya anulado específicamente el .ToString()método, todo lo que obtendrá es el nombre completo del tipo (por ejemplo, "hashtable: $(@{ key = 'value' })"rendimientos hashtable: System.Collections.Hashtable).

    • Para obtener el mismo resultado que en la consola , use una subexpresión y una canalizaciónOut-String y aplique .Trim()para eliminar las líneas vacías iniciales y finales, si lo desea; por ejemplo,
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"rinde:

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

[1] Este comportamiento quizás sorprendente significa que, para los tipos que apoyan representaciones sensibles a la cultura, $obj.ToString()produce una representación apropiada a la cultura actual , mientras que "$obj"(interpolación de cadenas) siempre da como resultado una representación invariante a la cultura - vea esta respuesta mía.

[2]
Anulaciones notables: * La cadena de colecciones discutida anteriormente (lista de elementos separados por espacios en lugar de algo así System.Object[]).
* La representación de instancias similar a una tabla hash [pscustomobject](explicada aquí ) en lugar de la cadena vacía .

mklement0
fuente