Как можно использовать свойство объекта в строке с двойными кавычками?

97

У меня такой код:

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

Когда я пытаюсь использовать одно из свойств в строке, выполните команду:

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

Он пытается просто использовать значение, $DatabaseSettingsа не значение $DatabaseSettings[0].DatabaseName, которое недопустимо.
Мой обходной путь - скопировать его в новую переменную.

Как я могу получить доступ к свойству объекта непосредственно в строке с двойными кавычками?

caveman_dick
источник

Ответы:

166

Когда вы заключаете имя переменной в строку с двойными кавычками, оно будет заменено значением этой переменной:

$foo = 2
"$foo"

становится

"2"

Если вы не хотите использовать одинарные кавычки:

$foo = 2
'$foo'

Однако, если вы хотите получить доступ к свойствам или использовать индексы для переменных в строке с двойными кавычками, вы должны заключить это подвыражение в $():

$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 расширяет только переменные в этих случаях, не более того. Чтобы принудительно вычислить более сложные выражения, включая индексы, свойства или даже полные вычисления, вы должны заключить их в оператор подвыражения, $( )который заставляет внутреннее выражение быть оцененным и встроенным в строку.

Джоуи
источник
Это работает, но мне интересно, могу ли я просто объединить строки, как я пытался избежать в первую очередь
ozzy432836
2
@ ozzy432836 Можно, конечно. Или используйте строки формата. Обычно это не имеет значения и сводится к личным предпочтениям.
Joey
15

@Joey имеет правильный ответ, но просто чтобы добавить немного больше о том, почему вам нужно принудительно выполнить оценку $():

Ваш примерный код содержит двусмысленность, которая указывает на то, почему создатели PowerShell, возможно, решили ограничить расширение простыми ссылками на переменные и не поддерживать доступ к свойствам (в стороне: расширение строки выполняется путем вызова ToString()метода объекта, который может объяснить некоторые "странные" результаты).

Ваш пример содержится в самом конце командной строки:

...\$LogFileName.ldf

Если бы свойства объектов были расширены по умолчанию, вышеуказанное разрешило бы

...\

так как объект ссылается $LogFileNameне будет иметь свойство ldf, $null(или пустая строка) будет заменена на переменную.

Стивен Муравски
источник
1
Хорошая находка. На самом деле я не был полностью уверен, в чем была его проблема, но попытка получить доступ к свойствам из строки плохо пахла :)
Джоуи
Спасибо, парни! Йоханнес, почему вы думаете, что доступ к свойствам в строке плохо пахнет? Как бы предположить, что это делается?
caveman_dick
пещерный человек: Это просто было то, что привлекло мое внимание как потенциальная причина ошибки. Вы, конечно, можете это сделать, и в этом нет ничего странного, но при пропуске оператора $ () он кричит «Неудачно», потому что он не может работать. Таким образом, мой ответ был просто обоснованным предположением о том, в чем может быть ваша проблема, с использованием первой возможной причины отказа, которую я мог видеть. Извините, если это выглядело так, но этот комментарий не относился к какой-либо передовой практике или около того.
Joey
Хорошо, что ты меня там волновал! ;) Когда я впервые начал использовать PowerShell, мне показалось, что переменная в строке немного странная, но очень удобная. Мне просто не удалось найти конкретное место, где я мог бы искать помощь по синтаксису.
caveman_dick
9

@Joey имеет хороший ответ. Есть еще один способ с более .NET-видом с эквивалентом String.Format, я предпочитаю его при доступе к свойствам объектов:

Вещи о машине:

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

Создайте объект:

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

Интерполировать строку:

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

Выходы:

# The red car is a nice sedan that is fully loaded
дурачок101
источник
Да, это более читабельно, особенно если вы привыкли к .net-коду. Спасибо! :)
caveman_dick
3
При первом использовании форматных строк следует обратить внимание на одну хитрость, заключающуюся в том, что вам часто нужно заключать выражение в скобки. Вы не можете, например, просто написать, write-host "foo = {0}" -f $fooпоскольку PowerShell будет рассматривать -fкак ForegroundColorпараметр для write-host. В этом примере вам потребуется write-host ( "foo = {0}" -f $foo ). Это стандартное поведение PowerShell, но его стоит отметить.
Andyb
Чтобы быть педантичным, приведенный выше пример - это не «интерполяция строк», а «составное форматирование». Поскольку Powershell неразрывно связан с .NET, для которого C # и VB.NET являются основными языками программирования, термины «строковая интерполяция» и «интерполированная строка» (и что-либо подобное) должны применяться только к новым строкам с добавлением $, найденным в этих языки (C # 6 и VB.NET 14). В этих строках выражения параметров непосредственно встраиваются в строку вместо числовых ссылок на параметры, а фактические выражения затем являются аргументами для String.Format.
Uber Kluger,
@andyb, относительно "ошибок" в строке формата. На самом деле в этом разница между режимом выражения и режимом аргумента (см. About_Parsing ). Команды разбиваются на токены (группы символов, образующие значимую строку). Пробел разделяет токены, которые могут объединиться, но в противном случае игнорируются. Если 1-й токен команды - это число, переменная, ключевое слово оператора (if, while и т. Д.), Унарный оператор, {и т. Д., То режим синтаксического анализа - Expressionдругой Argument(до конца команды).
Uber Kluger,
8

Примечание к документации: Get-Help about_Quoting_Rulesохватывает интерполяцию строк, но, начиная с PSv5, не углубленно.

Для того, чтобы дополнить полезный ответ Джой с прагматическим резюме в PowerShell в разложении строки (строка интерполяция в двойных кавычках строки ( в "...")том числе в двойных кавычках здесь-строк ):

  • Только ссылки , такие как $foo, $global:foo(или $script:foo, ...) и$env:PATH (переменные среды) признаются , когда непосредственно встраивается в "..."строке - то есть, только ссылочная переменная сама расширяется, независимо от того, что следует.

    • Чтобы отличить имя переменной от последующих символов в строке, заключите его в {и} ; например, ${foo}.
      Это особенно важно , если имя переменной сопровождается :, в PowerShell иначе бы рассмотреть все между $и в области видимости спецификатор, как правило , вызывает интерполяцию неудачу ; например, ломается, но работает по назначению. ( В качестве альтернативы, экранирующего : ).:"$HOME: where the heart is.""${HOME}: where the heart is."
      `:"$HOME`: where the heart is."

    • Чтобы рассматривать a $или a "как литерал , добавьте к нему escape-символ. `( обратная кавычка ); например:
      "`$HOME's value: `"$HOME`""

  • Для чего -то еще, в том числе с использованием индексов массива и доступ к переменной объекта в свойства , необходимо заключить выражение$(...) , то оператор подвыражения (например, "PS version: $($PSVersionTable.PSVersion)"или "1st el.: $($someArray[0])")

    • Использование $(...)even позволяет вставлять вывод целых командных строк в строки с двойными кавычками (например, "Today is $((Get-Date).ToString('d')).").
  • Результаты интерполяции не обязательно выглядят так же, как формат вывода по умолчанию (то, что вы бы увидели, если бы вы напечатали переменную / подвыражение непосредственно в консоли, например, что включает средство форматирования по умолчанию; см. Get-Help about_format.ps1xml):

    • Коллекции , включая массивы, преобразуются в строки путем помещения одного пробела между строковыми представлениями элементов (по умолчанию; можно указать другой разделитель путем установки $OFS) Например, "array: $(@(1, 2, 3))"даетarray: 1 2 3

    • Экземпляры любого другого типа (включая элементы коллекций, которые сами не являются коллекциями) преобразовываются в строку либо путем вызова IFormattable.ToString()метода с инвариантной культурой , если тип экземпляра поддерживает IFormattableинтерфейс [1] , либо путем вызова .psobject.ToString(), который в большинстве случаев просто вызывает базовый типа .NET в .ToString()методе [2] , которые могут или не могут дать содержательное представления: если а (непримитивный) тип специально не переопределил .ToString()метод, все , что вы получите это полное имя типа (например, "hashtable: $(@{ key = 'value' })"выходы hashtable: System.Collections.Hashtable).

    • Для того, чтобы получить тот же результат , как и в консоли , используйте подвыражения и трубуOut-String и применить , .Trim()чтобы удалить любые начальные и конечные пустые строки, если это необходимо; например,
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"дает:

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

[1] Это, возможно, удивительное поведение означает, что для типов, которые поддерживают представления, $obj.ToString()зависящие от языка и региональных параметров , дает представление, соответствующее текущей культуре , тогда как "$obj"(строковая интерполяция) всегда приводит к представлению, инвариантному для культуры - см. Этот мой ответ .

[2] Важные переопределения:
* Ранее обсуждавшаяся строковая форма коллекций (разделенный пробелами список элементов, а не что-то подобное System.Object[]).
* The hashtable- как представление [pscustomobject]экземпляров (объяснено здесь ) , а не пустая строка .

mklement0
источник