Hashtable'lar hakkında bilmek istediğiniz her şey


Bir adım geriye çekilip hashtable'lardan bahsetmek istiyorum. Artık onları her zaman kullanıyorum. Dün geceki kullanıcı grubu toplantımızdan sonra birisine onlar hakkında bilgi veriyordum ve onlar hakkında onun yaşadığı kafa karışıklığının aynısını yaşadığımı fark ettim. Hashtable'lar PowerShell'de gerçekten önemlidir, bu nedenle bunları sağlam bir şekilde anlamak iyidir.

Not

Bu makalenin orijinal versiyonu @KevinMarquette tarafından yazılan blogda yayınlandı. PowerShell ekibi bu içeriği bizimle paylaştığı için Kevin'e teşekkür ediyor. Lütfen PowerShellExplained.com adresindeki bloguna göz atın.

Bir şeyler koleksiyonu olarak Hashtable

Öncelikle Hashtable'ı, karma tablosunun geleneksel tanımındaki bir koleksiyon olarak görmenizi istiyorum. Bu tanım, daha sonra daha gelişmiş şeylere alıştıklarında nasıl çalıştıklarına dair temel bir anlayış sağlar. Bu anlayışın atlanması çoğu zaman kafa karışıklığının kaynağıdır.

Dizi nedir?

Hashtable'ın ne olduğuna geçmeden önce dizilerden bahsetmem gerekiyor. Bu tartışmanın amacına göre bir dizi, değerlerin veya nesnelerin bir listesi veya koleksiyonudur.

$array = @(1,2,3,5,7,11)

Öğelerinizi bir diziye yerleştirdikten sonra, liste üzerinde yineleme yapmak için foreach komutunu kullanabilir veya dizideki tek tek öğelere erişmek için bir dizin kullanabilirsiniz.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

Aynı şekilde bir indeks kullanarak değerleri de güncelleyebilirsiniz.

$array[2] = 13

Dizilerin sadece yüzeyini çizdim ama hashtable'lara geçerken bu onları doğru bağlama yerleştirmeli.

Hashtable nedir?

PowerShell'in bunları kullandığı diğer yöntemlere geçmeden önce genel anlamda hashtable'ların ne olduğuna dair temel bir teknik tanımla başlayacağım.

Hashtable, her değeri (nesneyi) bir anahtar kullanarak saklamanız dışında, diziye çok benzeyen bir veri yapısıdır. Temel bir anahtar/değer deposudur. Öncelikle boş bir hashtable oluşturuyoruz.

$ageList = @{}

Hashtable'ı tanımlamak için parantez yerine parantezlerin kullanıldığına dikkat edin. Daha sonra şunun gibi bir anahtar kullanarak bir öğe ekliyoruz:

$key = 'Kevin'
$value = 36
$ageList.add( $key, $value )

$ageList.add( 'Alex', 9 )

Kişinin adı anahtardır ve yaşı da kaydetmek istediğim değerdir.

Erişim için parantezlerin kullanılması

Değerlerinizi karma tablosuna ekledikten sonra, aynı anahtarı kullanarak (bir dizi için kullandığınız gibi sayısal bir dizin kullanmak yerine) onları geri çekebilirsiniz.

$ageList['Kevin']
$ageList['Alex']

Kevin'in yaşını istediğimde ona erişmek için onun adını kullanıyorum. Bu yaklaşımı hash tablosuna değer eklemek veya güncellemek için de kullanabiliriz. Bu tıpkı yukarıdaki add() fonksiyonunun kullanılmasına benzer.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

Daha sonraki bir bölümde ele alacağım değerlere erişmek ve bunları güncellemek için kullanabileceğiniz başka bir sözdizimi daha var. PowerShell'e başka bir dilden geliyorsanız bu örnekler, daha önce karma tabloları nasıl kullandığınıza uygun olmalıdır.

Değerlerle karma tablo oluşturma

Şu ana kadar bu örnekler için boş bir karma tablo oluşturdum. Anahtarları ve değerleri oluşturduğunuzda önceden doldurabilirsiniz.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

Arama tablosu olarak

Bu tür karma tablonun gerçek değeri, bunları bir arama tablosu olarak kullanabilmenizdir. İşte basit bir örnek.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

Bu örnekte, $env değişkeni için bir ortam belirtirsiniz ve değişken doğru sunucuyu seçer. Bunun gibi bir seçim için switch($env){... kullanabilirsiniz, ancak karma tablosu güzel bir seçenektir.

Daha sonra kullanmak üzere arama tablosunu dinamik olarak oluşturduğunuzda bu daha da iyi hale gelir. Bu nedenle, bir şeye çapraz referans vermeniz gerektiğinde bu yaklaşımı kullanmayı düşünün. PowerShell Where-Object ile boruyu filtrelemede bu kadar iyi olmasaydı bunu daha da çok göreceğimizi düşünüyorum. Performansın önemli olduğu bir durumdaysanız bu yaklaşımın dikkate alınması gerekir.

Daha hızlı olduğunu söylemeyeceğim ancak Performans önemliyse test edin kuralına uyuyor.

Çoklu seçim

Genel olarak karma tabloyu, bir anahtar sağladığınız ve bir değer aldığınız bir anahtar/değer çifti olarak düşünürsünüz. PowerShell, birden fazla değer elde etmek için bir anahtar dizisi sağlamanıza olanak tanır.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

Bu örnekte, yukarıdan aynı arama karma tablosunu kullanıyorum ve eşleşmeleri elde etmek için üç farklı dizi stili sunuyorum. Bu, çoğu insanın bilmediği PowerShell'deki gizli bir mücevherdir.

Yinelenen hashtable'lar

Hashtable, anahtar/değer çiftlerinin bir koleksiyonu olduğundan, bir dizi veya normal bir öğe listesi için yaptığınızdan farklı şekilde yinelersiniz.

Dikkat etmeniz gereken ilk şey, hashtable'ınızı pipetlerseniz pipenin ona tek bir nesne gibi davranmasıdır.

PS> $ageList | Measure-Object
count : 1

.count özelliği size kaç değer içerdiğini söylese de.

PS> $ageList.count
2

İhtiyacınız olan tek şey yalnızca değerlerse, .values özelliğini kullanarak bu sorunu aşabilirsiniz.

PS> $ageList.values | Measure-Object -Average
Count   : 2
Average : 22.5

Anahtarları numaralandırmak ve değerlere erişmek için bunları kullanmak genellikle daha kullanışlıdır.

PS> $ageList.keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

Burada foreach(){... döngüsüyle aynı örneği görüyoruz.

foreach($key in $ageList.keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

Hashtable'daki her bir anahtarı yürütüyoruz ve ardından onu değere erişmek için kullanıyoruz. Bu, hashtable'larla koleksiyon olarak çalışırken yaygın bir kalıptır.

GetEnumerator()

Bu bizi karma tablomuz üzerinde yinelemeler yapmak için GetEnumerator()'a getiriyor.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.key, $_.value
    Write-Output $message
}

Numaralandırıcı size her anahtar/değer çiftini birbiri ardına verir. Bu kullanım durumu için özel olarak tasarlanmıştır. Bunu bana hatırlattığı için Mark Kraus'a teşekkür ederim.

BadEnumeration

Önemli bir ayrıntı, bir hash tablosunu numaralandırılırken değiştiremeyeceğinizdir. Temel $environments örneğimizle başlarsak:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

Ve her anahtarı aynı sunucu değerine ayarlamaya çalışmak başarısız olur.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

Bu da iyi olması gerektiği gibi görünse de başarısız olacaktır:

foreach($key in $environments.keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

Bu durumun püf noktası, numaralandırmayı yapmadan önce anahtarları klonlamaktır.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Özelliklerin bir koleksiyonu olarak Hashtable

Şu ana kadar karma tablomuza yerleştirdiğimiz nesnelerin tümü aynı türde nesnelerdi. Tüm bu örneklerde yaşları kullandım ve anahtar kişinin adıydı. Bu, nesne koleksiyonunuzun her birinin bir adı olduğunda ona bakmanın harika bir yoludur. PowerShell'de karma tabloları kullanmanın başka bir yaygın yolu da, anahtarın özelliğin adı olduğu bir özellikler koleksiyonunu tutmaktır. Bir sonraki örnekte bu fikre adım atacağım.

Mülk tabanlı erişim

Özellik tabanlı erişimin kullanılması karma tabloların dinamiklerini ve bunları PowerShell'de nasıl kullanabileceğinizi değiştirir. Anahtarları özellik olarak ele alan yukarıdaki olağan örneğimiz aşağıdadır.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

Tıpkı yukarıdaki örneklerde olduğu gibi, bu örnek de, hash tablosunda mevcut değilse bu anahtarları ekler. Anahtarlarınızı nasıl tanımladığınıza ve değerlerinizin ne olduğuna bağlı olarak bu ya biraz garip ya da mükemmel bir uyumdur. Yaş listesi örneği bu noktaya kadar harika çalıştı. İleriye doğru ilerlemek için yeni bir örneğe ihtiyacımız var.

$person = @{
    name = 'Kevin'
    age  = 36
}

Ve $person'a bunun gibi nitelikler ekleyebilir ve bunlara erişebiliriz.

$person.city = 'Austin'
$person.state = 'TX'

Birdenbire bu hashtable bir nesne gibi hissetmeye ve hareket etmeye başlıyor. Bu hala bir şeyler koleksiyonu olduğundan yukarıdaki örneklerin tümü hala geçerlidir. Biz sadece farklı bir bakış açısıyla yaklaşıyoruz.

Anahtarları ve değerleri kontrol etme

Çoğu durumda, değeri şunun gibi bir şeyle test edebilirsiniz:

if( $person.age ){...}

Basit ama benim için birçok hatanın kaynağı oldu çünkü mantığımda önemli bir ayrıntıyı gözden kaçırıyordum. Bir anahtarın mevcut olup olmadığını test etmek için kullanmaya başladım. Değer $false veya sıfır olduğunda, bu ifade beklenmedik bir şekilde $false sonucunu döndürüyordu.

if( $person.age -ne $null ){...}

Bu, sıfır değerler için bu sorunu çözer ancak $null ve var olmayan anahtarlar için geçerli değildir. Çoğu zaman bu ayrımı yapmanıza gerek yoktur, ancak bunu yaptığınızda kullanabileceğiniz işlevler vardır.

if( $person.ContainsKey('age') ){...}

Ayrıca, anahtarı bilmeden veya koleksiyonun tamamını yinelemeden bir değeri test etmeniz gereken durumlar için bir ContainsValue()'ya da sahibiz.

Anahtarları çıkarma ve temizleme

.Remove() işleviyle anahtarları kaldırabilirsiniz.

$person.remove('age')

Onlara bir $null değeri atamak, size yalnızca $null değeri olan bir anahtar bırakır.

Bir karma tablosunu temizlemenin yaygın bir yolu, onu boş bir karma tablosuna başlatmaktır.

$person = @{}

Bu işe yarasa da bunun yerine clear() işlevini kullanmayı deneyin.

$person.clear()

Bu, işlevi kullanmanın kendi kendini belgeleyen kod oluşturduğu ve kodun amacını çok net hale getirdiği örneklerden biridir.

Tüm eğlenceli şeyler

Sıralı hashtable'lar

Varsayılan olarak hashtable'lar sıralanmaz (veya sıralanmaz). Geleneksel bağlamda, değerlere erişmek için her zaman bir anahtar kullandığınızda sıranın bir önemi yoktur. Özelliklerin tanımladığınız sırayla kalmasını istediğinizi fark edebilirsiniz. Neyse ki bunu ordered anahtar kelimesiyle yapmanın bir yolu var.

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

Artık anahtarları ve değerleri numaralandırdığınızda bu sırayla kalırlar.

Satır içi karma tablolar

Bir satırda karma tablosu tanımlarken anahtar/değer çiftlerini noktalı virgülle ayırabilirsiniz.

$person = @{ name = 'kevin'; age = 36; }

Bunları boru üzerinde oluşturuyorsanız bu kullanışlı olacaktır.

Yaygın ardışık düzen komutlarındaki özel ifadeler

Özel veya hesaplanmış özellikler oluşturmak için karma tabloların kullanımını destekleyen birkaç cmdlet vardır. Bunu genellikle Select-Object ve Format-Table ile görürsünüz. Hashtable'ların tamamen genişletildiğinde buna benzeyen özel bir söz dizimi vardır.

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

ad, cmdlet'in bu sütunu etiketleyeceği şeydir. ifade, $_'un kanal üzerindeki nesnenin değeri olduğu yerde yürütülen bir komut dosyası bloğudur. İşte bu komut dosyası eylem halinde:

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

Bunu bir değişkene yerleştirdim ancak satır içi kolayca tanımlanabilir ve ad'ı n olarak ve ifade'yi e olarak kısaltabilirsiniz. kod> siz bu sırada.

$drives | Select-Object -property name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}

Kişisel olarak bunun komutları bu kadar uzun süre vermesinden hoşlanmıyorum ve çoğu zaman içine girmeyeceğim bazı kötü davranışları teşvik ediyor. Komut dosyalarında bu yaklaşımı kullanmak yerine, istediğim tüm alan ve özelliklere sahip yeni bir karma tablosu veya pscustomobject oluşturma olasılığım daha yüksek. Ancak bunu yapan çok sayıda kod var, bu yüzden bunun farkında olmanızı istedim. Daha sonra bir pscustomobject oluşturmaktan bahsedeceğim.

Özel sıralama ifadesi

Nesneler sıralamak istediğiniz verilere sahipse, bir koleksiyonu sıralamak kolaydır. Nesneyi sıralamadan önce verileri nesneye ekleyebilir veya Sort-Object için özel bir ifade oluşturabilirsiniz.

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

Bu örnekte, kullanıcıların bir listesini alıyorum ve yalnızca sıralamaya yönelik ek bilgiler almak için bazı özel cmdlet'leri kullanıyorum.

Hashtable'ların listesini sıralama

Sıralamak istediğiniz karma tabloların bir listesi varsa Sort-Object'in anahtarlarınızı özellik olarak değerlendirmediğini göreceksiniz. Özel bir sıralama ifadesi kullanarak bunu bir tur elde edebiliriz.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

Hashtable'ları cmdlet'lere sıçratma

Bu, hashtable'larla ilgili en sevdiğim şeylerden biri ve çoğu insanın erkenden keşfetmediği bir şey. Buradaki fikir, tüm özellikleri tek satırda bir cmdlet'e sağlamak yerine, önce bunları bir karma tablosuna paketleyebilmenizdir. Daha sonra hash tablosunu fonksiyona özel bir şekilde verebilirsiniz. Burada normal yolla DHCP kapsamı oluşturmanın bir örneği verilmiştir.

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

Splatting kullanmadan tüm bunların tek bir satırda tanımlanması gerekir. Ya ekrandan kayar ya da istediği yere sarılır. Şimdi bunu sıçramayı kullanan bir komutla karşılaştırın.

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

Uyarı işlemini başlatan şey $ yerine @ işaretinin kullanılmasıdır.

Bu örneğin okunmasının ne kadar kolay olduğunu anlamak için bir dakikanızı ayırın. Hepsi aynı değerlere sahip, tamamen aynı komutlardır. İkinciyi anlamak ve ileriye taşımak daha kolaydır.

Komut çok uzadığında sıçramayı kullanırım. Çok uzun süreyi penceremin sağa kaymasına neden olacak şekilde tanımlıyorum. Bir fonksiyon için üç özelliğe ulaşırsam, muhtemelen onu dağınık bir hash tablosu kullanarak yeniden yazacağım.

İsteğe bağlı parametreler için sıçrama

Splatting'i kullanmamın en yaygın yollarından biri, betiğimin başka bir yerinden gelen isteğe bağlı parametrelerle ilgilenmektir. Diyelim ki, isteğe bağlı bir $Credential argümanına sahip bir Get-CIMInstance çağrısını saran bir fonksiyonum var.

$CIMParams = @{
    ClassName = 'Win32_Bios'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CIMInstance @CIMParams

Ortak parametrelerle karma tablomu oluşturarak başlıyorum. Daha sonra varsa $Credential'ı eklerim. Burada splatting kullandığım için kodumda Get-CIMInstance çağrısını yalnızca bir kez yapmam gerekiyor. Bu tasarım modeli çok temizdir ve birçok isteğe bağlı parametreyi kolaylıkla işleyebilir.

Adil olmak gerekirse, komutlarınızı parametreler için $null değerlerine izin verecek şekilde yazabilirsiniz. Çağırdığınız diğer komutlar üzerinde her zaman kontrol sahibi olamazsınız.

Çoklu uyarılar

Birden fazla hashtable'ı aynı cmdlet'e yerleştirebilirsiniz. Orijinal sıçrama örneğimize tekrar dönersek:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

Birçok komuta aktardığım ortak bir parametre kümesine sahip olduğumda bu yöntemi kullanacağım.

Temiz kod için sıçrama

Kodlamanızı daha temiz hale getirecekse, tek bir parametreyi eklemenin yanlış bir yanı yoktur.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

Yürütülebilir dosyalar

Splatting ayrıca /param:value sözdizimini kullanan bazı yürütülebilir dosyalar üzerinde de çalışır. Örneğin Robocopy.exe bunun gibi bazı parametrelere sahiptir.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

Bunun o kadar yararlı olduğunu bilmiyorum ama ilginç buldum.

Hashtable ekleme

Hashtable'lar, iki hashtable'ı birleştirmek için toplama operatörünü destekler.

$person += @{Zip = '78701'}

Bu yalnızca iki karma tablonun bir anahtarı paylaşmaması durumunda işe yarar.

İç içe hashtable'lar

Hashtable'ları bir hashtable içindeki değerler olarak kullanabiliriz.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

İki anahtar içeren temel bir karma tabloyla başladım. Boş karma tablosuna sahip location adında bir anahtar ekledim. Daha sonra son iki öğeyi bu konum karma tablosuna ekledim. Bunların hepsini inline olarak da yapabiliriz.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

Bu, yukarıda gördüğümüz hash tablosunun aynısını oluşturur ve özelliklere aynı şekilde erişebilir.

$person.location.city
Austin

Nesnelerinizin yapısına yaklaşmanın birçok yolu vardır. İşte iç içe geçmiş bir karma tabloya bakmanın ikinci bir yolu.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

Bu, hashtable'ları bir nesne koleksiyonu ve bir özellikler koleksiyonu olarak kullanma kavramını karıştırır. Tercih ettiğiniz yaklaşım kullanılarak iç içe yerleştirildiğinde bile değerlere erişim yine de kolaydır.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

Bir özellik gibi davranırken nokta özelliğini kullanma eğilimindeyim. Bunlar genellikle kodumda statik olarak tanımladığım şeylerdir ve bunları kafamın üstünden biliyorum. Listede gezinmem veya anahtarlara programlı olarak erişmem gerekirse, anahtar adını sağlamak için parantezleri kullanırım.

foreach($name in $people.keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

Hashtable'ları iç içe yerleştirme yeteneğine sahip olmak size çok fazla esneklik ve seçenek sunar.

İç içe hashtable'lara bakmak

Hashtable'ları iç içe yerleştirmeye başlar başlamaz, onlara konsoldan bakmanın kolay bir yoluna ihtiyacınız olacak. Eğer son karma tablosunu alırsam, şunun gibi görünen bir çıktı elde ederim ve bu çıktı yalnızca şu kadar derine iner:

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

Bunlara bakmak için başvuracağım komut ConvertTo-JSON'dur çünkü çok temizdir ve JSON'u başka şeylerde sıklıkla kullanırım.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

JSON'u bilmeseniz bile aradığınızı görebilmelisiniz. Bunun gibi yapılandırılmış veriler için bir Format-Custom komutu var ancak yine de JSON görünümünü daha çok seviyorum.

Nesne oluşturma

Bazen sadece bir nesneye sahip olmanız gerekir ve özellikleri tutmak için bir hash tablosu kullanmak işin yapılmasını sağlamaz. Çoğunlukla anahtarları sütun adları olarak görmek istersiniz. Bir pscustomobject bunu kolaylaştırır.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

Başlangıçta onu bir pscustomobject olarak oluşturmasanız bile, daha sonra ihtiyaç duyduğunuzda istediğiniz zaman yayınlayabilirsiniz.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

Zaten pscustomobject için bundan sonra okumanız gereken ayrıntılı bir yazım var. Burada öğrenilen birçok şeyin üzerine kuruludur.

Dosyaya karma tabloları okuma ve yazma

CSV'ye kaydediliyor

CSV'ye kaydedecek hash tablosunu almakta zorluk çekmek yukarıda bahsettiğim zorluklardan biri. Hashtable'ınızı bir pscustomobject dosyasına dönüştürün; CSV'ye doğru şekilde kaydedilecektir. Sütun sırasının korunması için bir pscustomobject ile başlamanız yardımcı olur. Ancak gerekirse bunu satır içi bir pscustomobject'e aktarabilirsiniz.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-CSV -Path $path

Bir kez daha pscustomobject kullanımıyla ilgili yazıma göz atın.

İç içe geçmiş bir hash tablosunu dosyaya kaydetme

İç içe geçmiş bir karma tablosunu bir dosyaya kaydetmem ve ardından tekrar okumam gerekirse, bunu yapmak için JSON cmdlet'lerini kullanırım.

$people | ConvertTo-JSON | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-JSON

Bu yöntemle ilgili iki önemli nokta vardır. Birincisi, JSON'un çok satırlı olarak yazılmış olması, dolayısıyla onu tek bir dizeye geri okumak için -Raw seçeneğini kullanmam gerekiyor. İkincisi, içe aktarılan nesnenin artık bir [hashtable] olmamasıdır. Artık bir [pscustomobject] oldu ve eğer beklemezseniz bu sorunlara neden olabilir.

Derinlemesine yuvalanmış karma tabloları izleyin. JSON'a dönüştürdüğünüzde beklediğiniz sonuçları alamayabilirsiniz.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

İç içe geçmiş tüm karma tabloları genişlettiğinizden emin olmak için Derinlik parametresini kullanın.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

İçe aktarma sırasında bunun bir [hashtable] olmasını istiyorsanız Export-CliXml ve Import-CliXml komutlarını kullanmanız gerekir.

JSON'u Hashtable'a dönüştürme

JSON'u bir [hashtable]'a dönüştürmeniz gerekiyorsa, bunu .NET'teki JavaScriptSerializer ile yapmanın bildiğim bir yolu var.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

PowerShell v6'dan itibaren JSON desteği, NewtonSoft JSON.NET'i kullanır ve hash tablosu desteği ekler.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2, Derinlik parametresini ConvertFrom-Json'a ekledi. Varsayılan Derinlik 1024'tür.

Doğrudan dosyadan okuma

PowerShell söz dizimini kullanan hash tablosu içeren bir dosyanız varsa onu doğrudan içe aktarmanın bir yolu vardır.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

Dosyanın içeriğini bir scriptblock'a aktarır ve ardından onu çalıştırmadan önce içinde başka PowerShell komutunun bulunmadığından emin olmak için kontrol eder.

Bu bağlamda, bir modül bildiriminin (psd1 dosyası) yalnızca bir karma tablosu olduğunu biliyor muydunuz?

Anahtarlar herhangi bir nesne olabilir

Çoğu zaman tuşlar yalnızca dizelerdir. Böylece herhangi bir şeyin etrafına tırnak işareti koyabilir ve onu anahtar yapabiliriz.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

Yapabileceğinizi fark etmediğiniz bazı tuhaf şeyleri yapabilirsiniz.

$person.'full name'

$key = 'full name'
$person.$key

Bir şeyi yapabiliyor olmanız, yapmanız gerektiği anlamına gelmez. Sonuncusu, gerçekleşmeyi bekleyen bir hata gibi görünüyor ve kodunuzu okuyan herkes tarafından kolayca yanlış anlaşılabilir.

Teknik olarak anahtarınızın bir dize olması gerekmez, ancak yalnızca dizeleri kullanırsanız bunları düşünmek daha kolaydır. Ancak indeksleme karmaşık anahtarlarla iyi çalışmaz.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

Hash tablosundaki bir değere anahtarıyla erişmek her zaman işe yaramaz. Örneğin:

$key = $ht.keys[0]
$ht.$($key)
a
$ht[$key]
a

Anahtar bir dizi olduğunda, üye erişimi (.) gösterimiyle kullanılabilmesi için $key değişkenini bir alt ifadeye sarmanız gerekir. Veya dizi dizini ([]) gösterimini kullanabilirsiniz.

Otomatik değişkenlerde kullanın

$PSBoundParameters

$PSBoundParameters, yalnızca bir işlevin bağlamında var olan otomatik bir değişkendir. Fonksiyonun çağrıldığı tüm parametreleri içerir. Bu tam olarak bir hash tablosu değil ama öyleymiş gibi davranabileceğiniz kadar yakın.

Buna anahtarların çıkarılması ve diğer işlevlere aktarılması da dahildir. Kendinizi proxy işlevleri yazarken bulursanız, buna daha yakından bakın.

Daha fazla ayrıntı için about_Automatic_Variables'a bakın.

PSBoundParameters anladım

Hatırlanması gereken önemli bir nokta, bunun yalnızca parametre olarak iletilen değerleri içermesidir. Ayrıca varsayılan değerleri olan ancak arayan tarafından iletilmeyen parametreleriniz varsa $PSBoundParameters bu değerleri içermez. Bu genellikle gözden kaçar.

$PSDefaultParameterValues

Bu otomatik değişken, cmdlet'i değiştirmeden herhangi bir cmdlet'e varsayılan değerler atamanıza olanak tanır. Bu örneğe bir göz atın.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

Bu, $PSDefaultParameterValues karma tablosuna, Out-File -Encoding parametresi için varsayılan değer olarak UTF8'i ayarlayan bir giriş ekler. Bu oturuma özeldir, dolayısıyla onu $profile'ınıza yerleştirmelisiniz.

Bunu sıklıkla yazdığım değerleri önceden atamak için kullanıyorum.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

Bu aynı zamanda joker karakterleri de kabul eder, böylece değerleri toplu olarak ayarlayabilirsiniz. İşte bunu kullanmanın bazı yolları:

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

Daha ayrıntılı bir döküm için Michael Sorens'in Otomatik Varsayılanlar hakkındaki bu harika makalesine bakın.

Regex $Eşleşmeleri

-match operatörünü kullandığınızda, eşleşme sonuçlarıyla $matches adlı otomatik bir değişken oluşturulur. Normal ifadenizde herhangi bir alt ifadeniz varsa bu alt eşleşmeler de listelenir.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

Adlandırılmış eşleşmeler

Bu, çoğu insanın bilmediği en sevdiğim özelliklerden biridir. Adlandırılmış bir normal ifade eşleşmesi kullanırsanız bu eşleşmeye, eşleşmelerdeki ada göre erişebilirsiniz.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

Yukarıdaki örnekte (?.*), adlandırılmış bir alt ifadedir. Bu değer daha sonra $Matches.Name özelliğine yerleştirilir.

Grup-Nesne -AsHashtable

Group-Object'in az bilinen bir özelliği, bazı veri kümelerini sizin için karma tablosuna dönüştürebilmesidir.

Import-CSV $Path | Group-Object -AsHashtable -Property email

Bu, her satırı bir hash tablosuna ekleyecek ve belirtilen özelliği ona erişmek için anahtar olarak kullanacaktır.

Hashtable'ları Kopyalama

Bilmeniz gereken önemli bir şey, karma tabloların nesneler olduğudur. Ve her değişken yalnızca bir nesneye referanstır. Bu, karma tablosunun geçerli bir kopyasını oluşturmak için daha fazla çalışma yapılması gerektiği anlamına gelir.

Referans türlerini atama

Bir karma tablonuz olduğunda ve onu ikinci bir değişkene atadığınızda, her iki değişken de aynı karma tablosunu işaret eder.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

Bu, bunların aynı olduğunu vurgular çünkü birindeki değerleri değiştirmek diğerindeki değerleri de değiştirecektir. Bu aynı zamanda karma tabloları diğer işlevlere aktarırken de geçerlidir. Bu işlevler o hash tablosunda değişiklik yaparsa orijinaliniz de değişir.

Sığ kopyalar, tek düzey

Yukarıdaki örneğimizdeki gibi basit bir karma tablomuz varsa sığ bir kopya oluşturmak için .Clone() kullanabiliriz.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

Bu, birinde diğerini etkilemeyen bazı temel değişiklikler yapmamıza olanak tanıyacak.

Sığ kopyalar, iç içe

Sığ kopya olarak adlandırılmasının nedeni yalnızca temel düzeydeki özellikleri kopyalamasıdır. Bu özelliklerden biri bir referans türüyse (başka bir karma tablosu gibi), o zaman iç içe geçmiş nesneler yine de birbirini işaret edecektir.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

Yani karma tablosunu klonlamama rağmen kişi referansının kopyalanmadığını görebilirsiniz. İlkine bağlı olmayan ikinci bir hash tablosuna gerçekten sahip olmak için derin bir kopya yapmamız gerekiyor.

Derin kopyalar

Hashtable'ın derin bir kopyasını oluşturmanın (ve bunu hashtable olarak saklamanın) birkaç yolu vardır. Yinelemeli olarak derin bir kopya oluşturmak için PowerShell'i kullanan bir işlev:

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

Başka herhangi bir referans türünü veya dizisini işlemez, ancak iyi bir başlangıç noktasıdır.

Başka bir yol da, bu işlevdeki gibi CliXml kullanarak seri durumdan çıkarmak için .Net'i kullanmaktır:

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

Son derece büyük hashtable'lar için seri durumdan çıkarma işlevi ölçeklendikçe daha hızlıdır. Ancak bu yöntemi kullanırken dikkat edilmesi gereken bazı hususlar vardır. CliXml kullandığı için bellek yoğundur ve büyük karma tabloları klonluyorsanız bu bir sorun olabilir. CliXml'in diğer bir sınırlaması da 48'lik bir derinlik sınırlamasıdır. Yani, 48 iç içe geçmiş karma tablosu içeren bir karma tablonuz varsa, klonlama başarısız olur ve hiçbir karma tablosu çıktısı alınmaz.

Başka bir şey?

Çok fazla mesafeyi hızla kat ettim. Umudum, bunu her okuduğunuzda yeni bir şeye eğilerek veya onu daha iyi anlayarak ayrılmanızdır. Bu özelliğin tüm kapsamını ele aldığım için şu anda sizin için geçerli olmayabilecek yönler var. Bu tamamen normaldir ve PowerShell ile ne kadar çalıştığınıza bağlı olarak beklenen bir durumdur.

İlgili makaleler