Jak można użyć właściwości obiektu w ciągu znaków umieszczonych w podwójnych cudzysłowach?

100

Mam następujący kod:

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

Kiedy próbuję użyć jednej z właściwości w poleceniu wykonania ciągu:

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

Próbuje po prostu użyć wartości $DatabaseSettingszamiast wartości $DatabaseSettings[0].DatabaseName, która jest nieprawidłowa.
Moje obejście polega na skopiowaniu go do nowej zmiennej.

Jak mogę uzyskać dostęp do właściwości obiektu bezpośrednio w ciągu znaków umieszczonych w podwójnych cudzysłowach?

caveman_dick
źródło

Odpowiedzi:

169

Kiedy umieścisz nazwę zmiennej w łańcuchu umieszczonym w podwójnych cudzysłowach, zostanie ona zastąpiona wartością tej zmiennej:

$foo = 2
"$foo"

staje się

"2"

Jeśli nie chcesz, abyś używał pojedynczych cudzysłowów:

$foo = 2
'$foo'

Jeśli jednak chcesz uzyskać dostęp do właściwości lub użyć indeksów zmiennych w ciągu znaków umieszczonych w podwójnych cudzysłowach, musisz ująć to podwyrażenie w $():

$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 tylko rozwija zmienne w takich przypadkach, nic więcej. Aby wymusić ocenę bardziej złożonych wyrażeń, w tym indeksów, właściwości, a nawet pełnych obliczeń, należy ująć je w operatorze podwyrażenia, $( )który powoduje, że wyrażenie wewnątrz jest oceniane i osadzane w ciągu.

Joey
źródło
To działa, ale zastanawiam się, czy mogę równie dobrze łączyć ciągi, jak starałem się uniknąć w pierwszej kolejności
ozzy432836
2
@ ozzy432836 Oczywiście możesz. Lub użyj ciągów formatu. Zwykle nie ma to większego znaczenia i sprowadza się do osobistych preferencji.
Joey
15

@Joey ma poprawną odpowiedź, ale tylko trochę więcej wyjaśniając, dlaczego musisz wymusić ocenę za pomocą $():

Twój przykładowy kod zawiera niejednoznaczność, która wskazuje na to, dlaczego twórcy programu PowerShell mogli zdecydować się na ograniczenie interpretacji do zwykłych odwołań do zmiennych i nie obsługują również dostępu do właściwości (na marginesie: interpretacja ciągów jest wykonywana przez wywołanie ToString()metody na obiekcie, która może wyjaśnić niektóre „dziwne” wyniki).

Twój przykład zawarty na samym końcu linii poleceń:

...\$LogFileName.ldf

Jeśli właściwości obiektów byłyby rozszerzone domyślnie, powyższe rozwiązałoby się do

...\

Ponieważ obiekt odwołuje $LogFileNamenie miałby właściwość o nazwie ldf, $null(lub pusty ciąg) zostanie zastąpione zmiennej.

Steven Murawski
źródło
1
Niezłe znalezisko. I faktycznie nie był całkowicie pewien, co było jego problemem, ale stara się właściwościami dostępu od wewnątrz ciągu śmierdzi :)
Joey
Dzięki chłopaki! Johannes, jak myślisz, dlaczego dostęp do właściwości w sznurku brzydko pachnie? Jak sugerowałbyś, że to się robi?
caveman_dick
jaskiniowiec: To była rzecz, która zwróciła moją uwagę jako potencjalna przyczyna błędu. Z pewnością możesz to zrobić i nie ma w tym nic dziwnego, ale pomijając operator $ () krzyczy „Fail”, ponieważ nie działa. Tak więc moja odpowiedź była tylko wyedukowanym przypuszczeniem, jaki może być twój problem, wykorzystując pierwszą możliwą przyczynę niepowodzenia, którą widziałem. Przepraszam, jeśli na to wyglądało, ale ten komentarz nie dotyczył żadnej najlepszej praktyki.
Joey,
Dobrze, że się tam martwiłem! ;) Kiedy po raz pierwszy zacząłem używać PowerShell, pomyślałem, że zmienna w ciągu znaków jest trochę dziwna, ale jest całkiem przydatna. Po prostu nie udało mi się znaleźć ostatecznego miejsca, w którym mógłbym szukać pomocy dotyczącej składni.
caveman_dick
9

@Joey ma dobrą odpowiedź. Jest inny sposób z wyglądem bardziej .NET z odpowiednikiem String.Format, wolę go podczas uzyskiwania dostępu do właściwości obiektów:

Rzeczy o samochodzie:

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

Utwórz obiekt:

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

Interpolacja ciągu:

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

Wyjścia:

# The red car is a nice sedan that is fully loaded
loonison101
źródło
Tak, to jest bardziej czytelne, zwłaszcza jeśli jesteś przyzwyczajony do kodu .net. Dzięki! :)
caveman_dick
3
Jest pewna pułapka, na którą należy zwrócić uwagę, kiedy po raz pierwszy używasz ciągów formatujących, a mianowicie, że często będziesz musiał ująć wyrażenie w parens. Nie możesz na przykład po prostu pisać, write-host "foo = {0}" -f $fooponieważ PowerShell będzie traktował -fjako ForegroundColorparametr for write-host. W tym przykładzie musisz write-host ( "foo = {0}" -f $foo ). Jest to standardowe zachowanie programu PowerShell, ale warte odnotowania.
andyb
Aby być pedantycznym, powyższy przykład nie jest „interpolacją ciągów”, jest to „formatowanie złożone”. Ponieważ program PowerShell jest nierozerwalnie powiązany z platformą .NET, dla której C # i VB.NET są podstawowymi językami programowania, terminy „interpolacja ciągów” i „ciąg interpolowany” (i wszystko podobne) powinny być stosowane tylko do nowych ciągów znaków dodanych z prefiksem $ znajdujących się w tych języki (C # 6 i VB.NET 14). W tych ciągach wyrażenia parametrów są bezpośrednio osadzane w ciągu zamiast numerycznych odwołań do parametrów, a rzeczywiste wyrażenia są wówczas argumentami String.Format.
Uber Kluger
@andyb, w odniesieniu do ciągu formatu „gotchas”. W rzeczywistości jest to różnica między trybami wyrażeń i argumentów (zobacz about_Parsing ). Polecenia są przetwarzane na tokeny (grupy znaków tworzące znaczący ciąg). Spacja oddziela tokeny, które uległyby scaleniu, ale w przeciwnym razie są ignorowane. Jeśli pierwszym tokenem polecenia jest liczba, zmienna, słowo kluczowe instrukcji (if, while, itp.), Operator jednoargumentowy {, etc ..., wtedy tryb analizowania jest Expressioninny Argument(aż do terminatora polecenia).
Uber Kluger
9

Uwaga dokumentacyjna: Get-Help about_Quoting_Rulesobejmuje interpolację ciągów, ale od PSv5 nie jest dogłębna.

W celu uzupełnienia użytecznej odpowiedzi Joey'a z pragmatycznego Podsumowując PowerShell w ekspansji smyczkową (interpolacja ciągu w podwójnej notowane ciągi ( "..."vel spieniania strun ), w tym podwójnie cytowanych tu-strings ):

  • Tylko odniesienia, takie jak $foo, $global:foo(lub $script:foo, ...) i$env:PATH (zmienne środowiskowe) są rozpoznawane, gdy są bezpośrednio osadzone w "..."ciągu - to znaczy, tylko samo odwołanie do zmiennej jest rozwijane, niezależnie od tego, co następuje.

    • Aby oddzielić nazwę zmiennej od kolejnych znaków w ciągu, umieść ją w {i} ; np ${foo}.
      Jest to szczególnie ważne, jeśli po nazwie zmiennej występuje a :, ponieważ w przeciwnym razie program PowerShell uwzględniłby wszystko między specyfikatorem zakresu$ a :a , co zwykle powoduje niepowodzenie interpolacji ; np. przerwy, ale działa zgodnie z przeznaczeniem. (Alternatywnie, -escape : )."$HOME: where the heart is.""${HOME}: where the heart is."
      `:"$HOME`: where the heart is."

    • Aby traktować a $lub a "jako literał , należy poprzedzić go znakiem ucieczki. `( backtick ); na przykład:
      "`$HOME's value: `"$HOME`""

  • W przypadku czegokolwiek innego, w tym używania indeksów tablicowych i uzyskiwania dostępu do właściwości zmiennej obiektu , należy umieścić wyrażenie w$(...) , operator podwyrażenia (np. "PS version: $($PSVersionTable.PSVersion)"Lub "1st el.: $($someArray[0])")

    • Użycie $(...)even pozwala na osadzenie danych wyjściowych z całych wierszy poleceń w podwójnych cudzysłowach (np "Today is $((Get-Date).ToString('d')).".).
  • Wyniki interpolacji niekoniecznie wyglądają tak samo, jak domyślny format wyjściowy (co można zobaczyć, gdybyś na przykład wydrukował zmienną / podwyrażenie bezpośrednio na konsolę, która obejmuje domyślny program formatujący; zobacz Get-Help about_format.ps1xml):

    • Kolekcje , w tym tablice, są konwertowane na ciągi poprzez umieszczenie pojedynczej spacji między reprezentacjami łańcuchowymi elementów (domyślnie; inny separator można określić przez ustawienie $OFS) Np. "array: $(@(1, 2, 3))"Dajearray: 1 2 3

    • Instancje innego rodzaju (w tym elementów kolekcji, które same nie są kolekcje) są stringified przez albo wywołanie IFormattable.ToString()metody z kultury niezmienny , jeśli typ instancji obsługuje ten IFormattableinterfejs [1] , lub przez wywołanie .psobject.ToString(), co jest w wielu przypadkach po prostu wywołuje .ToString()metoda [2] bazowego typu .NET , która może, ale nie musi, dawać sensowną reprezentację: chyba że typ (inny niż pierwotny) wyraźnie przesłonił.ToString()metodę, wszystko, co otrzymasz, to pełna nazwa typu (np."hashtable: $(@{ key = 'value' })"plonyhashtable: System.Collections.Hashtable).

    • Aby uzyskać te same dane wyjściowe, co w konsoli , użyj wyrażenia podrzędnego i potoku doOut-String i zastosuj, .Trim()aby usunąć wszelkie początkowe i końcowe puste wiersze, jeśli to konieczne; np.
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"plony:

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

[1] To być może zaskakujące zachowanie oznacza, że ​​dla typów, które obsługują reprezentacje zależne od kultury, $obj.ToString()daje bieżącą reprezentację odpowiednią dla kultury , podczas gdy "$obj"(interpolacja ciągów) zawsze powoduje reprezentację niezmienną kulturowo - zobacz tę odpowiedź .

[2] Istotne zmiany:

  • Wcześniej omówione stringifikacja kolekcji (rozdzielona spacjami lista elementów, a nie coś podobnego System.Object[]).
  • Reprezentacja instancji podobna do hashy [pscustomobject](wyjaśniona tutaj ) zamiast pustego ciągu .
mklement0
źródło