Bagaimana Anda bisa menggunakan properti objek dalam string kutip ganda?

97

Saya memiliki kode berikut:

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

Ketika saya mencoba menggunakan salah satu properti dalam perintah eksekusi string:

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

Ia mencoba untuk hanya menggunakan nilai $DatabaseSettingsdaripada nilai $DatabaseSettings[0].DatabaseName, yang tidak valid.
Solusi saya adalah menyalinnya ke variabel baru.

Bagaimana cara mengakses properti objek secara langsung dalam string kutip ganda?

caveman_dick
sumber

Jawaban:

166

Saat Anda memasukkan nama variabel dalam string kutip ganda, itu akan diganti dengan nilai variabel itu:

$foo = 2
"$foo"

menjadi

"2"

Jika Anda tidak ingin menggunakan tanda kutip tunggal:

$foo = 2
'$foo'

Namun, jika Anda ingin mengakses properti, atau menggunakan indeks pada variabel dalam string bertanda kutip ganda, Anda harus menyertakan subekspresi tersebut dalam $():

$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 hanya memperluas variabel dalam kasus tersebut, tidak lebih. Untuk memaksa evaluasi ekspresi yang lebih kompleks, termasuk indeks, properti, atau bahkan penghitungan lengkap, Anda harus mengapitnya di operator subekspresi $( )yang menyebabkan ekspresi di dalamnya dievaluasi dan disematkan dalam string.

Joey
sumber
Ini berfungsi tetapi saya bertanya-tanya apakah saya mungkin juga hanya menggabungkan string seperti yang saya coba hindari sejak awal
ozzy432836
2
@ ozzy432836 Anda bisa, tentu saja. Atau gunakan string format. Biasanya itu tidak terlalu penting dan tergantung pada preferensi pribadi.
Joey
15

@Joey memiliki jawaban yang benar, tetapi hanya untuk menambahkan sedikit lagi mengapa Anda perlu memaksakan evaluasi dengan $():

Kode contoh Anda berisi ambiguitas yang menunjukkan mengapa pembuat PowerShell mungkin telah memilih untuk membatasi perluasan hanya untuk referensi variabel dan tidak mendukung akses ke properti juga (sebagai tambahan: ekspansi string dilakukan dengan memanggil ToString()metode pada objek, yang mana dapat menjelaskan beberapa hasil yang "aneh").

Contoh Anda terdapat di bagian paling akhir dari baris perintah:

...\$LogFileName.ldf

Jika properti objek diperluas secara default, di atas akan diselesaikan menjadi

...\

karena objek yang direferensikan $LogFileNametidak akan memiliki properti yang dipanggil ldf, $null(atau string kosong) akan menggantikan variabel.

Steven Murawski
sumber
1
Temuan bagus. Saya sebenarnya tidak sepenuhnya yakin apa masalahnya tetapi mencoba mengakses properti dari dalam string berbau tidak enak :)
Joey
Terima kasih teman-teman! Johannes, mengapa menurut Anda mengakses properti dalam string berbau tidak enak? Bagaimana menyarankan itu dilakukan?
caveman_dick
manusia gua: Itu hanya hal yang menarik perhatian saya sebagai penyebab kesalahan potensial. Anda pasti bisa melakukannya dan itu tidak aneh, tetapi ketika menghilangkan operator $ () itu berteriak "Gagal" karena tidak bisa bekerja. Jadi jawaban saya hanyalah tebakan terpelajar tentang apa poblem Anda, menggunakan kemungkinan penyebab kegagalan pertama yang bisa saya lihat. Maaf jika terlihat seperti itu, tapi komentar itu tidak mengacu pada praktik terbaik atau lebih.
Joey
Baik Anda membuat saya khawatir di sana! ;) Ketika saya pertama kali mulai menggunakan PowerShell, saya pikir variabel dalam string agak aneh, tetapi cukup berguna. Saya baru saja tidak berhasil menemukan tempat yang pasti sehingga saya dapat mencari bantuan sintaks.
caveman_dick
9

@ Joey punya jawaban yang bagus. Ada cara lain dengan tampilan .NET lebih dengan setara String.Format, saya lebih suka saat mengakses properti pada objek:

Hal-hal tentang mobil:

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

Buat sebuah objek:

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

Interpolasi string:

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

Keluaran:

# The red car is a nice sedan that is fully loaded
loonison101
sumber
Ya itu lebih mudah dibaca terutama jika Anda terbiasa dengan kode .net. Terima kasih! :)
caveman_dick
3
Ada hal yang harus diperhatikan saat Anda pertama kali menggunakan string format, yaitu Anda akan sering kali perlu menyertakan ekspresi dalam tanda kurung. Anda tidak bisa, misalnya hanya menulis write-host "foo = {0}" -f $fookarena PowerShell akan memperlakukannya -fsebagai ForegroundColorparameter untuk write-host. Dalam contoh ini, Anda perlu write-host ( "foo = {0}" -f $foo ). Ini adalah perilaku PowerShell standar, tetapi perlu diperhatikan.
andyb
Sebagai contoh, contoh di atas bukanlah "interpolasi string", melainkan "pemformatan gabungan". Karena Powershell terikat erat ke .NET di mana C # dan VB.NET adalah bahasa pemrograman utama, istilah "interpolasi string" dan "string interpolasi" (dan yang serupa) harus diterapkan hanya ke string $ prepended baru yang ditemukan di bahasa (C # 6 dan VB.NET 14). Dalam string ini, ekspresi parameter secara langsung disematkan dalam string alih-alih referensi parameter numerik, dengan ekspresi aktual kemudian menjadi argumen String.Format.
Uber Kluger
@andyb, tentang format string "gotchas". Ini sebenarnya adalah perbedaan antara mode Ekspresi dan Argumen (lihat about_Parsing ). Perintah diuraikan menjadi token (kelompok karakter yang membentuk string bermakna). Spasi memisahkan token yang akan bergabung tetapi diabaikan. Jika token pertama perintah adalah angka, variabel, kata kunci pernyataan (if, while, dll), operator unary, {, dll ... maka mode parsing adalah Expressionsebaliknya Argument(hingga terminator perintah).
Uber Kluger
8

Catatan dokumentasi: Get-Help about_Quoting_Rulesmencakup interpolasi string, tetapi, pada PSv5, tidak mendalam.

Untuk melengkapi jawaban Joey yang membantu dengan ringkasan pragmatis dari ekspansi string PowerShell (interpolasi string dalam string yang dikutip ganda ( "..."), termasuk dalam string yang dikutip ganda di sini ):

  • Hanya referensi seperti $foo, $global:foo(atau $script:foo, ...) dan$env:PATH (variabel lingkungan) yang dikenali saat disematkan secara langsung dalam "..."string - artinya, hanya referensi variabel itu sendiri yang diperluas, terlepas dari apa yang mengikutinya.

    • Untuk membedakan nama variabel dari karakter berikutnya dalam string, apit dengan {dan} ; mis ${foo}. , .
      Hal ini terutama penting jika nama variabel diikuti dengan :, seperti PowerShell jika tidak akan mempertimbangkan segala sesuatu antara $dan :sebuah ruang lingkup specifier, biasanya menyebabkan interpolasi untuk gagal ; misalnya, "$HOME: where the heart is."istirahat, tetapi "${HOME}: where the heart is."berfungsi sebagaimana mestinya.
      (Atau, `-escape yang :: "$HOME`: where the heart is.").

    • Untuk memperlakukan a $atau a "sebagai literal , awali dengan escape char. `(a backtick ); misalnya:
      "`$HOME's value: `"$HOME`""

  • Untuk hal lain, termasuk menggunakan subskrip array dan mengakses properti variabel objek , Anda harus menyertakan ekspresi dalam$(...) , operator subekspresi (mis., "PS version: $($PSVersionTable.PSVersion)"Atau "1st el.: $($someArray[0])")

    • Menggunakan $(...)even memungkinkan Anda untuk menyematkan keluaran dari seluruh baris perintah dalam string kutip ganda (misalnya, "Today is $((Get-Date).ToString('d')).").
  • Hasil interpolasi tidak selalu terlihat sama dengan format keluaran default (yang akan Anda lihat jika Anda mencetak variabel / subekspresi langsung ke konsol, misalnya, yang melibatkan pemformat default; lihat Get-Help about_format.ps1xml):

    • Koleksi , termasuk larik, diubah menjadi string dengan menempatkan satu spasi di antara representasi string dari elemen (secara default; pemisah yang berbeda dapat ditentukan dengan pengaturan $OFS) Misalnya, "array: $(@(1, 2, 3))"hasilarray: 1 2 3

    • Contoh jenis lainnya (termasuk unsur koleksi yang tidak sendiri koleksi) yang stringified oleh salah memanggil IFormattable.ToString()metode dengan budaya invarian , jika jenis contoh ini mendukung IFormattableantarmuka [1] , atau dengan menelepon .psobject.ToString(), yang dalam banyak kasus hanya memanggil .ToString()metode tipe NET yang mendasari [2] , yang mungkin atau mungkin tidak memberikan representasi yang berarti: kecuali tipe (non-primitif) telah secara khusus menimpa .ToString()metode tersebut, yang akan Anda dapatkan hanyalah nama tipe lengkap (misalnya, "hashtable: $(@{ key = 'value' })"hasil hashtable: System.Collections.Hashtable).

    • Untuk mendapatkan hasil yang sama seperti di konsol , gunakan subekspresi dan pipa keOut-String dan terapkan .Trim()untuk menghapus garis kosong di depan dan di belakang, jika diinginkan; misalnya,
      "hashtable:`n$((@{ key = 'value' } | Out-String).Trim())"hasil:

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

[1] Perilaku yang mungkin mengejutkan ini berarti bahwa, untuk tipe yang mendukung representasi yang peka budaya, $obj.ToString()menghasilkan representasi yang sesuai dengan budaya saat ini , sedangkan "$obj"(interpolasi string) selalu menghasilkan representasi budaya-invarian - lihat jawaban saya ini.

[2] Penimpaan penting:
* Stringifikasi koleksi yang telah dibahas sebelumnya (daftar elemen yang dipisahkan spasi daripada sesuatu seperti itu System.Object[]).
* Representasi instance seperti hashtable [pscustomobject](dijelaskan di sini ) daripada string kosong .

mklement0
sumber