Як можна використовувати властивість об’єкта у рядку з подвійними лапками?


96

У мене є такий код:

$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, яке не є дійсним.
Моє обхідне рішення - скопіювати його в нову змінну.

Як я можу отримати доступ до властивості об’єкта безпосередньо у рядку з подвійними лапками?

Відповіді:


163

Коли ви вкладаєте ім'я змінної у рядок із подвійними лапками, воно буде замінено значенням цієї змінної:

$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
Приємна знахідка. Я насправді не був повністю впевнений, у чому його проблема, але спроба отримати доступ до властивостей всередині рядка неприємно пахла :)
Joey

Спасибі, хлопці! Йоганнесе, чому, на вашу думку, доступ до властивостей у рядку неприємно пахне? Як би припустити, що це зроблено?
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

Так, це читабельніше, особливо якщо ви звикли до .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, щодо рядка формату "gotchas". Це насправді різниця між режимом " Вираз" та " Аргумент" (див. Розділ about_Paring ). Команди розбиваються на лексеми (групи символів, що утворюють значущий рядок). Пробіл відокремлює маркери, які зливаються, але інакше ігноруються. Якщо 1-й маркер команди - це число, змінна, ключове слово оператора (if, while і т.д.), одинарний оператор, {, etc ..., тоді режим синтаксичного аналізу - 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])")

    • Використання $(...)навіть дозволяє вбудовувати вихідні дані з цілих командних рядків у рядки з подвійними лапками (наприклад, "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(), який у більшості випадків просто викликає основний .ToString()метод .NET-типу [2] , який може надавати значуще подання або не надавати його: якщо (не примітивний) тип спеціально перевизначив .ToString()метод, ви отримаєте лише повне ім'я типу (наприклад, "hashtable: $(@{ key = 'value' })"yields 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[]).
* Представлення екземплярів, подібних до хеш-таблиці [pscustomobject](пояснено тут ), а не порожній рядок .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.