Comment utiliser la propriété d'un objet dans une chaîne entre guillemets?

96

J'ai le code suivant:

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

Lorsque j'essaie d'utiliser l'une des propriétés dans une commande d'exécution de chaîne:

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

Il essaie d'utiliser simplement la valeur de $DatabaseSettingsplutôt que la valeur de $DatabaseSettings[0].DatabaseName, ce qui n'est pas valide.
Ma solution de contournement consiste à le copier dans une nouvelle variable.

Comment puis-je accéder à la propriété de l'objet directement dans une chaîne entre guillemets?

caveman_dick
la source

Réponses:

163

Lorsque vous placez un nom de variable dans une chaîne entre guillemets, il sera remplacé par la valeur de cette variable:

$foo = 2
"$foo"

devient

"2"

Si vous ne voulez pas que vous deviez utiliser des guillemets simples:

$foo = 2
'$foo'

Cependant, si vous souhaitez accéder aux propriétés ou utiliser des index sur des variables dans une chaîne entre guillemets, vous devez placer cette sous-expression dans $():

$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 ne développe les variables que dans ces cas, rien de plus. Pour forcer l'évaluation d'expressions plus complexes, y compris des index, des propriétés ou même des calculs complets, vous devez les inclure dans l'opérateur de sous-expression $( )qui provoque l'évaluation et l'incorporation de l'expression à l'intérieur dans la chaîne.

Joey
la source
Cela fonctionne mais je me demande si je peux aussi bien concaténer des chaînes que j'essayais d'éviter en premier lieu
ozzy432836
2
@ ozzy432836 Vous pouvez, bien sûr. Ou utilisez des chaînes de format. Cela n'a généralement pas vraiment d'importance et dépend de vos préférences personnelles.
Joey
15

@Joey a la bonne réponse, mais juste pour ajouter un peu plus pourquoi vous devez forcer l'évaluation avec $():

Votre exemple de code contient une ambiguïté qui indique pourquoi les fabricants de PowerShell ont peut-être choisi de limiter l'expansion à de simples références de variables et de ne pas prendre en charge également l'accès aux propriétés (en passant: l'expansion de la chaîne se fait en appelant la ToString()méthode sur l'objet, ce qui peut expliquer certains résultats «étranges»).

Votre exemple contenu à la toute fin de la ligne de commande:

...\$LogFileName.ldf

Si les propriétés des objets étaient développées par défaut, ce qui précède se résoudrait à

...\

puisque l'objet référencé par $LogFileNamen'aurait pas de propriété appelée ldf, $null(ou une chaîne vide) serait substituée à la variable.

Steven Murawski
la source
1
Belle trouvaille. En fait, je n'étais pas tout à fait sûr de son problème, mais essayer d'accéder aux propriétés à partir d'une chaîne sentait mauvais :)
Joey
Merci les gars! Johannes pourquoi pensez-vous que l'accès aux propriétés dans une chaîne sent mauvais? Comment suggérerait-on que cela soit fait?
caveman_dick
homme des cavernes: C'était juste une chose qui a attiré mon attention comme étant une cause d'erreur potentielle. Vous pouvez certainement le faire et ce n'est rien de bizarre, mais en omettant l'opérateur $ (), il crie "Fail" car cela ne peut pas fonctionner. Ainsi ma réponse était juste une supposition éclairée sur ce que pourrait être votre poblème, en utilisant la première cause d'échec possible que je pouvais voir. Désolé si cela ressemblait à cela, mais ce commentaire ne faisait référence à aucune meilleure pratique.
Joey
Bon tu m'avais inquiété là-bas! ;) Quand j'ai commencé à utiliser PowerShell, je pensais que la variable dans une chaîne était un peu bizarre, mais c'est assez pratique. Je n'ai tout simplement pas réussi à trouver un endroit définitif où je peux chercher de l'aide sur la syntaxe.
caveman_dick
9

@Joey a une bonne réponse. Il existe un autre moyen avec un look plus .NET avec un équivalent String.Format, je le préfère lors de l'accès aux propriétés sur des objets:

Choses sur une voiture:

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

Créez un objet:

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

Interpoler une chaîne:

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

Les sorties:

# The red car is a nice sedan that is fully loaded
loonison101
la source
Ouais, c'est plus lisible, surtout si vous êtes habitué au code .net. Merci! :)
caveman_dick
3
Il y a un piège à rechercher lorsque vous utilisez pour la première fois des chaînes de format, c'est-à-dire que vous devrez souvent placer l'expression entre parenthèses. Vous ne pouvez pas, par exemple, simplement écrire write-host "foo = {0}" -f $foocar PowerShell le traitera -fcomme le ForegroundColorparamètre pour write-host. Dans cet exemple, vous devez write-host ( "foo = {0}" -f $foo ). Il s'agit d'un comportement PowerShell standard, mais il convient de le noter.
andyb
Pour être pédant, l'exemple ci-dessus n'est pas une "interpolation de chaîne", c'est un "formatage composite". Puisque Powershell est inextricablement lié à .NET pour lequel C # et VB.NET sont les principaux langages de programmation, les termes «interpolation de chaîne» et «chaîne interpolée» (et tout ce qui est similaire) ne doivent être appliqués qu'aux nouvelles chaînes $ préfixées trouvées dans ces langages (C # 6 et VB.NET 14). Dans ces chaînes, les expressions de paramètres sont directement incorporées dans la chaîne au lieu de références de paramètres numériques, les expressions réelles étant alors des arguments de String.Format.
Uber Kluger
@andyb, concernant la chaîne de format "gotchas". C'est en fait la différence entre les modes Expression et Argument (voir about_Parsing ). Les commandes sont analysées en jetons (groupes de caractères formant une chaîne significative). L'espace sépare les jetons qui fusionneraient mais qui sont sinon ignorés. Si le premier jeton de la commande est un nombre, une variable, un mot-clé d'instruction (if, while, etc), un opérateur unaire, {, etc ... alors le mode d'analyse est le Expressioncontraire Argument(jusqu'à un terminateur de commande).
Uber Kluger
8

Note de documentation: Get-Help about_Quoting_Rulescouvre l'interpolation de chaîne, mais, à partir de PSv5, pas en profondeur.

Pour compléter la réponse utile de Joey avec un résumé pragmatique de l' extension de chaîne de PowerShell (interpolation de chaîne dans des chaînes entre guillemets ( "...")y compris dans des chaînes ici entre guillemets ):

  • Seules les références telles que $foo, $global:foo(ou $script:foo, ...) et$env:PATH (variables d'environnement) sont reconnues lorsqu'elles sont directement incorporées dans une "..."chaîne - c'est-à-dire que seule la référence de variable elle - même est développée, indépendamment de ce qui suit.

    • Pour dissocier un nom de variable des caractères suivants de la chaîne, placez-le entre {et} ; par exemple ${foo}.
      Ceci est particulièrement important si l' on suit le nom de la variable par un :, comme PowerShell serait par ailleurs considérer tout entre $et :une portée spécificateur, ce qui provoque généralement l'interpolation à l' échec ; par exemple, des "$HOME: where the heart is."pauses, mais "${HOME}: where the heart is."fonctionne comme prévu.
      ( En variante, les `caractères d' échappement du :: "$HOME`: where the heart is.").

    • Pour traiter a $ou a "comme un littéral , préfixez-le avec un caractère d' échappement. `(un backtick ); par exemple:
      "`$HOME's value: `"$HOME`""

  • Pour toute autre chose, y compris l'utilisation d'indices de tableau et l'accès aux propriétés d' une variable d'objet , vous devez placer l'expression dans$(...) , l' opérateur de sous - expression (par exemple, "PS version: $($PSVersionTable.PSVersion)"ou "1st el.: $($someArray[0])")

    • L'utilisation $(...)même vous permet d'incorporer la sortie de lignes de commande entières dans des chaînes entre guillemets (par exemple, "Today is $((Get-Date).ToString('d')).").
  • Les résultats de l'interpolation ne ressemblent pas nécessairement au format de sortie par défaut (ce que vous verriez si vous imprimiez la variable / sous-expression directement dans la console, par exemple, ce qui implique le formateur par défaut; voir Get-Help about_format.ps1xml):

    • Les collections , y compris les tableaux, sont converties en chaînes en plaçant un seul espace entre les représentations sous forme de chaîne des éléments (par défaut; un séparateur différent peut être spécifié en définissant $OFS) Par exemple, les "array: $(@(1, 2, 3))"rendementsarray: 1 2 3

    • Les cas de tout autre type (y compris les éléments des collections qui ne sont pas des collections elles - mêmes) sont de chaîne de caractères par soit appeler la IFormattable.ToString()méthode avec la culture invariante , si le type de l'instance prend en charge l' IFormattableinterface de [1] , ou en appelant .psobject.ToString(), qui dans la plupart des cas simplement invoque la .ToString()méthode du type .NET sous-jacent [2] , qui peut ou non donner une représentation significative: à moins qu'un type (non primitif) n'ait spécifiquement remplacé la .ToString()méthode, tout ce que vous obtiendrez est le nom complet du type (par exemple, les "hashtable: $(@{ key = 'value' })"rendements hashtable: System.Collections.Hashtable).

    • Pour obtenir le même résultat que dans la console , utilisez une sous-expression et un tube versOut-String et appliquez .Trim()pour supprimer toutes les lignes vides de début et de fin, si vous le souhaitez; par exemple,
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"donne:

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

[1] Ce comportement peut-être surprenant signifie que, pour les types qui prennent en charge les représentations sensibles à la culture, $obj.ToString()donne une représentation courante appropriée "$obj"à la culture , alors que (interpolation de chaîne) aboutit toujours à une représentation invariante de la culture - voir ma réponse .

[2]
Remplacements notables: * La stringification des collections évoquée précédemment (liste d'éléments séparés par des espaces plutôt que quelque chose comme System.Object[]).
* La représentation des instances de type table de hachage [pscustomobject](expliquée ici ) plutôt que la chaîne vide .

mklement0
la source