Слияние нулей в PowerShell

115

Есть ли в PowerShell нулевой оператор объединения?

Я хотел бы иметь возможность выполнять эти команды C # в PowerShell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Михей
источник

Ответы:

133

Powershell 7+

Powershell 7 представляет встроенное объединение null, условное присвоение null и тернарные операторы в Powershell.

Нулевое слияние

$null ?? 100    # Result is 100

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

Нулевое условное присвоение

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

Тернарный оператор

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

Предыдущие версии:

Расширения Powershell Community Extensions не требуются, вы можете использовать стандартные операторы Powershell if в качестве выражения:

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

Итак, что касается замены вашего первого выражения C #:

var s = myval ?? "new value";

становится одним из следующих (в зависимости от предпочтений):

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

или в зависимости от того, что может содержать $ myval, вы можете использовать:

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

и второе выражение C # отображается аналогичным образом:

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

становится

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

Честно говоря, они не очень быстрые и далеко не так удобны в использовании, как формы C #.

Вы также можете обернуть его очень простой функцией, чтобы сделать ее более читаемой:

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

$s = Coalesce $myval "new value"

или, возможно, как IfNull:

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

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

Как видите, очень простая функция может дать вам некоторую свободу синтаксиса.

ОБНОВЛЕНИЕ: еще один вариант, который следует учитывать в сочетании, - это более общая функция IsTrue:

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

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

Затем объедините это с возможностью Powershell объявлять псевдонимы, которые немного похожи на операторы, в результате вы получите:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

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

Ясно, что это не всем придется по вкусу, но может быть именно то, что вы ищете.

Как отмечает Томас, еще одно тонкое различие между версией C # и приведенной выше заключается в том, что C # выполняет сокращение аргументов, но версии Powershell, включающие функции / псевдонимы, всегда будут оценивать все аргументы. Если это проблема, используйте ifформу выражения.

StephenD
источник
6
Единственный истинный эквивалент оператора объединения - использование оператора if; проблема в том, что при любом другом подходе вместо короткого замыкания оцениваются все операнды. «?? $ myval SomeReallyExpenisveFunction ()» вызовет функцию, даже если $ myval не равно нулю. Я полагаю, что можно отложить оценку, используя блоки сценариев, но имейте в виду, что блоки сценариев НЕ являются закрытием, и все начинает становиться неуклюжим.
Thomas S. Trias
Не работает в строгом режиме - выкидывает The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 Ошибка, которую вы видите в строгом режиме, не связана с представленными параметрами объединения null. Это просто стандартная проверка Powershell того, что переменная определена первой. Если установить $myval = $nullдо проведения теста, ошибка должна исчезнуть.
StephenD 04
1
Предупреждение, причуда PowerShell, null всегда следует сравнивать (неудобно), помещая сначала null, т.е. $null -ne $a не могу найти достойную ссылку сейчас, но это давняя практика.
dudeNumber4
90

PowerShell 7 и новее

PowerShell 7 представляет множество новых функций и выполняет миграцию с .NET Framework на .NET Core. По состоянию на середину 2020 года он не полностью заменил устаревшие версии PowerShell из-за зависимости от .NET Core, но Microsoft указала, что они намерены, чтобы семейство Core в конечном итоге заменило устаревшее семейство Framework. К тому времени, как вы это прочитаете, в вашей системе может быть предустановлена ​​совместимая версия PowerShell; если нет, см. https://github.com/powershell/powershell .

Согласно документации , в PowerShell 7.0 «из коробки» поддерживаются следующие операторы:

  1. Нулевое слияние :??
  2. Присваивание слияния нулей :??=
  3. Тернарный :... ? ... : ...

Они работают так, как и следовало ожидать от нулевого объединения:

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

Поскольку был введен тернарный оператор, теперь возможно следующее, хотя в этом нет необходимости, учитывая добавление нулевого оператора объединения:

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

Начиная с версии 7.0, если включенаPSNullConditionalOperators дополнительная функция, также доступны следующие функции , как описано в документации ( 1 , 2 ):

  1. Нулевой условный доступ к членам для членов: ?.
  2. Нулевой условный доступ к членам для массивов и др .: ?[]

У них есть несколько предостережений:

  1. Поскольку они экспериментальные, они могут быть изменены. К тому времени, как вы это прочитаете, они уже не будут считаться экспериментальными, и список предостережений может измениться.
  2. Переменные должны быть заключены в ${}if, за которым следует один из экспериментальных операторов, поскольку в именах переменных разрешены вопросительные знаки. Неясно, будет ли это так, если / когда функции выйдут из экспериментального статуса (см. Проблему № 11379 ). Например, ${x}?.Test()использует оператор new, но $x?.Test()работает Test()с переменной с именем $x?.
  3. ?(Оператора нет, как и следовало ожидать, если вы работаете с TypeScript. Следующее не сработает:$x.Test?()

PowerShell 6 и ранее

Версии PowerShell до 7 действительно имеют фактический нулевой оператор объединения или, по крайней мере, оператор, способный к такому поведению. Этот оператор -ne:

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

# Output:
alpha

Он немного более универсален, чем оператор объединения с нулевым значением, поскольку он создает массив всех ненулевых элементов:

$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 работает аналогично, что полезно для подсчета нулевых записей:

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

# Result:
2

Но в любом случае вот типичный случай зеркального отражения ??оператора C # :

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

объяснение

Это объяснение основано на предложении редактирования от анонимного пользователя. Спасибо, кто бы вы ни были!

В зависимости от порядка операций это работает в следующем порядке:

  1. ,Оператор создает массив значений для тестирования.
  2. В -neоператорском отфильтровывает любые элементы из массива , которые соответствуют указанному значению - в данном случае, нулевой. Результатом является массив ненулевых значений в том же порядке, что и массив, созданный на шаге 1.
  3. [0] используется для выбора первого элемента фильтруемого массива.

Упрощая это:

  1. Создайте массив возможных значений в предпочтительном порядке
  2. Исключить все нулевые значения из массива
  3. Возьмите первый элемент из получившегося массива

Предостережения

В отличие от оператора объединения NULL в C #, каждое возможное выражение будет оцениваться, поскольку первым шагом является создание массива.

Zenexer
источник
В итоге я использовал измененную версию вашего ответа в своем , потому что мне нужно было разрешить всем экземплярам в объединении быть нулевым, не вызывая исключения. Тем не менее, очень хороший способ сделать это, многому меня научил. :)
Johny Skovdal
Этот метод предотвращает короткое замыкание. Определите function DidItRun($a) { Write-Host "It ran with $a"; return $a }и бегите, ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]чтобы увидеть это.
jpmc26
@ jpmc26 Да, так задумано.
Zenexer 08
Любая попытка определить Coalesce функцию также может устранить короткое замыкание не так ли?
Крис Ф. Кэрролл
8
Ааааа меня убивает приоритет операторов PowerShell! Я всегда забываю, что у оператора запятой ,такой высокий приоритет. Для всех, кто не понимает, как это работает, ($null, 'alpha', 1 -ne $null)[0]то же самое, что и (($null, 'alpha', 1) -ne $null)[0]. Фактически, единственные два оператора «тире» с более высоким приоритетом - это -splitand -join(и унарное тире? Т.е. -4или -'56').
Плутон
15

Это только половина ответа на первую половину вопроса, поэтому четвертый ответ, если хотите, но есть гораздо более простая альтернатива оператору объединения null при условии, что значение по умолчанию, которое вы хотите использовать, на самом деле является значением по умолчанию для типа :

string s = myval ?? "";

Может быть записано в Powershell как:

([string]myval)

Или

int d = myval ?? 0;

переводится на Powershell:

([int]myval)

Я обнаружил, что первый из них полезен при обработке элемента xml, который может не существовать и который, если бы он существовал, мог иметь нежелательные пробелы вокруг него:

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

Приведение к строке защищает от нулевого значения элемента и предотвращает любой риск Trim()сбоя.

Дункан
источник
([string]$null)-> ""похоже на "полезный, но досадный побочный эффект" :(
user2864740 02
11

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

возвращается

3

Крис Ф. Кэрролл
источник
1
Но комментарий @ thomas-s-trias по поводу короткого замыкания все еще в силе.
Крис Ф. Кэрролл,
9

Если вы установите модуль расширений сообщества Powershell, вы можете использовать:

?? это псевдоним для Invoke-NullCoalescing.

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

?: это псевдоним Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
источник
На самом деле это не команды PowerShell. Вы получили их вместе с pscx:?: -> Invoke-
Ternary
... пропущен фактический код и результат ..;) Get-Command -Module pscx -CommandType alias | где {$ _. Name -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Name, $ _. Definition}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Упс ... вы совершенно правы. Я часто забываю, что у меня даже этот модуль загружается.
EBGreen
3
@($null,$null,"val1","val2",5) | select -First 1
статичность
источник
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]
 }
Шон
источник
2

Часто я обнаруживаю, что мне также нужно рассматривать пустую строку как нулевую при использовании coalesce. В итоге я написал функцию для этого, которая использует решение Zenexer для объединения для простого объединения NULL, а затем использовал Keith Hill для нулевой или пустой проверки и добавил это в качестве флага, чтобы моя функция могла выполнять и то, и другое.

Одним из преимуществ этой функции является то, что она также обрабатывает все элементы, равные нулю (или пустым), без создания исключения. Его также можно использовать для произвольного количества входных переменных, благодаря тому, как PowerShell обрабатывает входные данные массива.

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)
  }  
}

Это дает следующие результаты теста:

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

Код теста:

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)
Джонни Сковдал
источник
1

Ближайшее, что я могу найти, это: $Val = $MyVal |?? "Default Value"

Я реализовал нулевой оператор объединения для вышеуказанного следующим образом:

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

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

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

Условная тройная оператор может быть реализована в 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

И используется как: $Val = $MyVal |?: $MyVal "Default Value"

Илон Маллин
источник