Penggabungan nol di PowerShell

115

Apakah ada operator penggabungan nol di PowerShell?

Saya ingin bisa melakukan perintah c # ini di PowerShell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Mikha
sumber

Jawaban:

133

Powershell 7+

Powershell 7 memperkenalkan penggabungan nol asli, penetapan bersyarat null, dan operator terner di Powershell.

Penggabungan Nihil

$null ?? 100    # Result is 100

"Evaluated" ?? (Expensive-Operation "Not Evaluated")    # Right side here is not evaluated

Penugasan Bersyarat Nihil

$x = $null
$x ??= 100    # $x is now 100
$x ??= 200    # $x remains 100

Operator Ternary

$true  ? "this value returned" : "this expression not evaluated"
$false ? "this expression not evaluated" : "this value returned"

Versi sebelumnya:

Tidak perlu Ekstensi Komunitas Powershell, Anda dapat menggunakan pernyataan Powershell if standar sebagai ungkapan:

variable = if (condition) { expr1 } else { expr2 }

Jadi untuk penggantian ekspresi C # pertama Anda dari:

var s = myval ?? "new value";

menjadi salah satu dari berikut ini (tergantung pada preferensi):

$s = if ($myval -eq $null) { "new value" } else { $myval }
$s = if ($myval -ne $null) { $myval } else { "new value" }

atau tergantung pada apa yang mungkin berisi $ myval, Anda dapat menggunakan:

$s = if ($myval) { $myval } else { "new value" }

dan ekspresi C # kedua dipetakan dengan cara yang sama:

var x = myval == null ? "" : otherval;

menjadi

$x = if ($myval -eq $null) { "" } else { $otherval }

Sekarang untuk bersikap adil, ini tidak terlalu tajam, dan tidak nyaman digunakan sebagai bentuk C #.

Anda juga dapat mempertimbangkan untuk membungkusnya dalam fungsi yang sangat sederhana untuk membuatnya lebih mudah dibaca:

function Coalesce($a, $b) { if ($a -ne $null) { $a } else { $b } }

$s = Coalesce $myval "new value"

atau mungkin sebagai, IfNull:

function IfNull($a, $b, $c) { if ($a -eq $null) { $b } else { $c } }

$s = IfNull $myval "new value" $myval
$x = IfNull $myval "" $otherval

Seperti yang Anda lihat, fungsi yang sangat sederhana dapat memberi Anda sedikit kebebasan sintaks.

PEMBARUAN: Satu opsi tambahan untuk dipertimbangkan dalam campuran adalah fungsi IsTrue yang lebih umum:

function IfTrue($a, $b, $c) { if ($a) { $b } else { $c } }

$x = IfTrue ($myval -eq $null) "" $otherval

Kemudian gabungkan yaitu kemampuan Powershell untuk mendeklarasikan alias yang terlihat seperti operator, Anda akan mendapatkan:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

$ans = ?: ($q -eq "meaning of life") 42 $otherval

Jelas ini tidak akan menjadi selera semua orang, tetapi mungkin yang Anda cari.

Seperti yang dicatat Thomas, satu perbedaan halus lainnya antara versi C # dan di atas adalah bahwa C # melakukan hubungan arus pendek pada argumen, tetapi versi Powershell yang melibatkan fungsi / alias akan selalu mengevaluasi semua argumen. Jika ini menjadi masalah, gunakan ifbentuk ekspresi.

StephenD
sumber
6
Satu-satunya padanan yang benar untuk operator penggabungan adalah menggunakan pernyataan if; Masalahnya adalah bahwa pendekatan lain mengevaluasi semua operan alih-alih hubungan arus pendek. "?? $ myval SomeReallyExpenisveFunction ()" akan memanggil fungsi meskipun $ myval bukan null. Saya kira seseorang dapat menunda evaluasi menggunakan scriptblocks, tetapi ketahuilah bahwa scriptblock BUKAN penutupan, dan hal-hal mulai menjadi kikuk.
Thomas S. Trias
Tidak bekerja dalam mode ketat - itu melempar The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 Kesalahan yang Anda lihat dalam mode ketat tidak terkait dengan opsi penggabungan nol yang disajikan. Ini hanya standar, Powershell memeriksa bahwa variabel ditentukan terlebih dahulu. Jika Anda menyetel $myval = $nullsebelum melakukan pengujian, kesalahan tersebut akan hilang.
StephenD
1
Peringatan, powershell quirk, null harus selalu dibandingkan (dengan canggung) dengan menempatkan null terlebih dahulu, yaitu, $null -ne $a Tidak dapat menemukan referensi yang layak sekarang, tetapi merupakan praktik yang sudah berlangsung lama.
dudeNumber4
90

PowerShell 7 dan yang lebih baru

PowerShell 7 memperkenalkan banyak fitur baru dan bermigrasi dari .NET Framework ke .NET Core. Pada pertengahan 2020, itu belum sepenuhnya menggantikan versi lama PowerShell karena ketergantungan pada .NET Core, tetapi Microsoft telah mengindikasikan bahwa mereka bermaksud agar keluarga Core pada akhirnya menggantikan keluarga Framework lama. Pada saat Anda membaca ini, versi PowerShell yang kompatibel mungkin sudah diinstal sebelumnya di sistem Anda; jika tidak, lihat https://github.com/powershell/powershell .

Sesuai dokumentasi , operator berikut ini didukung langsung di PowerShell 7.0:

  1. Penggabungan nol :??
  2. Penugasan penggabungan nol :??=
  3. Ternary :... ? ... : ...

Ini berfungsi seperti yang Anda harapkan untuk penggabungan nol:

$x = $a ?? $b ?? $c ?? 'default value'
$y ??= 'default value'

Karena operator terner telah diperkenalkan, berikut ini sekarang mungkin, meskipun itu tidak perlu mengingat penambahan operator penggabungan null:

$x = $a -eq $null ? $b : $a

Mulai 7.0, berikut ini juga tersedia jika PSNullConditionalOperatorsfitur opsional diaktifkan , seperti yang dijelaskan di dokumen ( 1 , 2 ):

  1. Akses anggota tanpa syarat untuk anggota: ?.
  2. Akses anggota bersyarat nol untuk array dkk: ?[]

Ini memiliki beberapa peringatan:

  1. Karena ini bersifat eksperimental, mereka dapat berubah. Mereka mungkin tidak lagi dianggap eksperimental pada saat Anda membaca ini, dan daftar peringatan mungkin telah berubah.
  2. Variabel harus diapit ${}jika diikuti oleh salah satu operator eksperimental karena tanda tanya diizinkan dalam nama variabel. Tidak jelas apakah hal ini akan terjadi jika / saat fitur berpindah dari status eksperimental (lihat masalah # 11379 ). Misalnya, ${x}?.Test()menggunakan operator baru, tetapi $x?.Test()menjalankan Test()variabel bernama $x?.
  3. Tidak ada ?(operator seperti yang Anda harapkan jika Anda berasal dari TypeScript. Berikut ini tidak akan berhasil:$x.Test?()

PowerShell 6 dan yang lebih lama

Versi PowerShell sebelum 7 memang memiliki operator penggabungan nol aktual, atau setidaknya operator yang mampu melakukan perilaku tersebut. Operator itu adalah -ne:

# Format:
# ($a, $b, $c -ne $null)[0]
($null, 'alpha', 1 -ne $null)[0]

# Output:
alpha

Ini sedikit lebih fleksibel daripada operator penggabungan nol, karena ini membuat larik semua item bukan nol:

$items = $null, 'alpha', 5, 0, '', @(), $null, $true, $false
$instances = $items -ne $null
[string]::Join(', ', ($instances | ForEach-Object -Process { $_.GetType() }))

# Result:
System.String, System.Int32, System.Int32, System.String, System.Object[],
System.Boolean, System.Boolean

-eq berfungsi serupa, yang berguna untuk menghitung entri nol:

($null, 'a', $null -eq $null).Length

# Result:
2

Tapi bagaimanapun, inilah kasus khas untuk mencerminkan ??operator C # :

'Filename: {0}' -f ($filename, 'Unknown' -ne $null)[0] | Write-Output

Penjelasan

Penjelasan ini didasarkan pada saran edit dari pengguna anonim. Terima kasih, siapapun Anda!

Berdasarkan urutan operasi, ini bekerja dengan urutan sebagai berikut:

  1. The ,Operator menciptakan sebuah array nilai yang akan diuji.
  2. The -nefilter Operator keluar semua item dari array yang sesuai dengan nilai tertentu - dalam hal ini, null. Hasilnya adalah larik nilai bukan nol dalam urutan yang sama seperti larik yang dibuat pada Langkah 1.
  3. [0] digunakan untuk memilih elemen pertama dari larik yang difilter.

Menyederhanakan itu:

  1. Buat larik nilai yang mungkin, dalam urutan yang disukai
  2. Kecualikan semua nilai null dari larik
  3. Ambil item pertama dari larik yang dihasilkan

Peringatan

Tidak seperti operator penggabungan null C #, setiap ekspresi yang mungkin akan dievaluasi, karena langkah pertama adalah membuat array.

Zenexer
sumber
Saya akhirnya menggunakan versi modifikasi dari jawaban Anda di milik saya , karena saya perlu mengizinkan semua contoh dalam penggabungan menjadi nol, tanpa membuat pengecualian. Cara yang sangat bagus untuk melakukannya, banyak mempelajariku. :)
Johny Skovdal
Metode ini mencegah korsleting. Tentukan function DidItRun($a) { Write-Host "It ran with $a"; return $a }dan jalankan ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]untuk melihat ini.
jpmc26
@ jpmc26 Ya, itu memang desainnya.
Zenexer
Setiap upaya untuk mendefinisikan fungsi penggabungan juga cenderung menghilangkan korsleting bukan?
Chris F Carroll
8
Prioritas operator Ahhhh PowerShell membunuh saya! Saya selalu lupa bahwa operator koma ,diutamakan begitu tinggi. Bagi siapa pun yang bingung tentang cara kerjanya, ($null, 'alpha', 1 -ne $null)[0]sama saja dengan (($null, 'alpha', 1) -ne $null)[0]. Nyatanya, hanya dua operator "tanda hubung" dengan prioritas lebih tinggi adalah -splitdan -join(dan tanda hubung unary? Yaitu -4atau -'56').
Pluto
15

Ini hanya setengah jawaban untuk paruh pertama pertanyaan, jadi jawaban seperempat jika Anda mau, tetapi ada alternatif yang jauh lebih sederhana untuk operator penggabungan nol asalkan nilai default yang ingin Anda gunakan sebenarnya adalah nilai default untuk tipe tersebut :

string s = myval ?? "";

Dapat ditulis di Powershell sebagai:

([string]myval)

Atau

int d = myval ?? 0;

diterjemahkan menjadi Powershell:

([int]myval)

Saya menemukan yang pertama berguna saat memproses elemen xml yang mungkin tidak ada dan yang jika memang ada mungkin memiliki spasi kosong yang tidak diinginkan di sekelilingnya:

$name = ([string]$row.td[0]).Trim()

Cast to string melindungi elemen yang menjadi null dan mencegah risiko Trim()kegagalan.

Duncan
sumber
([string]$null)-> ""sepertinya "efek samping yang berguna, tetapi tidak menguntungkan" :(
user2864740
11

$null, $null, 3 | Select -First 1

kembali

3

Chris F Carroll
sumber
1
Namun komentar @ thomas-s-trias tentang korsleting masih berlaku.
Chris F Carroll
9

Jika Anda menginstal Modul Ekstensi Komunitas Powershell, maka Anda dapat menggunakan:

?? adalah alias untuk Invoke-NullCoalescing.

$s = ?? {$myval}  {"New Value"}

?: adalah alias untuk Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
sumber
Sebenarnya, itu bukan perintah PowerShell. Anda mendapatkannya bersama dengan pscx:?: -> Invoke-
Ternary
... melewatkan kode dan hasil aktual ..;) Get-Command -Module pscx -CommandType alias | di mana {$ _. Name -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Nama, $ _. Definisi}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Ups ... Anda sepenuhnya benar. Saya sering lupa bahwa saya bahkan memuat modul itu.
EBGreen
5

Terakhir, PowerShell 7 mendapatkan operator penugasan Null Coalescing !

PS > $null ?? "a"
a
PS > "x" ?? "y"
x
PS > $x = $null
PS > $x ??= "abc"
PS > $x
abc
PS > $x ??= "xyz"
PS > $x
abc
hiroki
sumber
3
@($null,$null,"val1","val2",5) | select -First 1
staticrysis.dll
sumber
2
function coalesce {
   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = ($list -ne $null)
   $coalesced[0]
 }
 function coalesce_empty { #COALESCE for empty_strings

   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = (($list -ne $null) -ne '')[0]
   $coalesced[0]
 }
Shawn
sumber
2

Seringkali saya menemukan bahwa saya juga perlu memperlakukan string kosong sebagai null saat menggunakan penggabungan. Saya akhirnya menulis fungsi untuk ini, yang menggunakan solusi Zenexer untuk penggabungan untuk penggabungan nol sederhana, dan kemudian menggunakan Keith Hill untuk pemeriksaan nol atau kosong, dan menambahkannya sebagai bendera sehingga fungsi saya dapat melakukan keduanya.

Salah satu keuntungan dari fungsi ini adalah, fungsi ini juga menangani memiliki semua elemen null (atau kosong), tanpa mengeluarkan pengecualian. Ini juga dapat digunakan untuk banyak variabel input sewenang-wenang, berkat cara PowerShell menangani input array.

function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
  if ($EmptyStringAsNull.IsPresent) {
    return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
  } else {
    return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
  }  
}

Ini menghasilkan hasil tes berikut:

Null coallesce tests:
1 (w/o flag)  - empty/null/'end'                 : 
1 (with flag) - empty/null/'end'                 : end
2 (w/o flag)  - empty/null                       : 
2 (with flag) - empty/null                       : 
3 (w/o flag)  - empty/null/$false/'end'          : 
3 (with flag) - empty/null/$false/'end'          : False
4 (w/o flag)  - empty/null/"$false"/'end'        : 
4 (with flag) - empty/null/"$false"/'end'        : False
5 (w/o flag)  - empty/'false'/null/"$false"/'end': 
5 (with flag) - empty/'false'/null/"$false"/'end': false

Kode tes:

Write-Host "Null coalesce tests:"
Write-Host "1 (w/o flag)  - empty/null/'end'                 :" (Coalesce '', $null, 'end')
Write-Host "1 (with flag) - empty/null/'end'                 :" (Coalesce '', $null, 'end' -EmptyStringAsNull)
Write-Host "2 (w/o flag)  - empty/null                       :" (Coalesce('', $null))
Write-Host "2 (with flag) - empty/null                       :" (Coalesce('', $null) -EmptyStringAsNull)
Write-Host "3 (w/o flag)  - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end')
Write-Host "3 (with flag) - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end' -EmptyStringAsNull)
Write-Host "4 (w/o flag)  - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end')
Write-Host "4 (with flag) - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end' -EmptyStringAsNull)
Write-Host "5 (w/o flag)  - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end')
Write-Host "5 (with flag) - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end' -EmptyStringAsNull)
Johny Skovdal
sumber
1

Yang paling dekat yang bisa saya dapatkan adalah: $Val = $MyVal |?? "Default Value"

Saya menerapkan operator penggabungan nol untuk hal di atas seperti ini:

function NullCoalesc {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$Default
    )

    if ($Value) { $Value } else { $Default }
}

Set-Alias -Name "??" -Value NullCoalesc

The operator ternary kondisional dapat diterapkan dengan cara similary.

function ConditionalTernary {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$First,
        [Parameter(Position=1)]$Second
    )

    if ($Value) { $First } else { $Second }
}

Set-Alias -Name "?:" -Value ConditionalTernary

Dan digunakan seperti: $Val = $MyVal |?: $MyVal "Default Value"

Elon Mallin
sumber