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


214

Из того, что я знаю, PowerShell, похоже, не имеет встроенного выражения для так называемого троичного оператора .

Например, на языке C, который поддерживает троичный оператор, я мог бы написать что-то вроде:

<condition> ? <condition-is-true> : <condition-is-false>;

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


5
Взгляните на github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Если это то, что вы ищете, я могу сделать ответ.
Роман Кузьмин

2
Это условный оператор или троичный, если . Это не «троичный оператор», поскольку все, что это означает, - это оператор ( любой оператор), который принимает три аргумента.
Damien_The_Unbeliever

7
@Damien_The_Unbeliever Это технически верно, но его часто называют троичным оператором. «Поскольку этот оператор часто является единственным существующим троичным оператором в языке, его иногда просто называют« троичным оператором ». В некоторых языках этот оператор называют« условным оператором ».
Тернарная

Visual Basic не имеет истинного троичного оператора, но считает, что IF и IFF функционально эквивалентны.
Мэтт

@Matt Это неправильно. IIF Функция всегда будет оценивать оба операнда. IfЗаявление не будет - см stackoverflow.com/questions/1220411/... (новые версии VB.NET добавили трехкомпонентное выражение: If (x, y, z))
user2864740

Ответы:


320
$result = If ($condition) {"true"} Else {"false"}

Все остальное имеет второстепенную сложность и поэтому его следует избегать.

Для использования в или в качестве выражения, а не просто присваивания, оберните его $(), таким образом:

write-host  $(If ($condition) {"true"} Else {"false"}) 

3
Это работает с правой стороны от равенства, но не совсем так, как вы ожидаете, троичный оператор - они терпят неудачу: "a" + If ($ условие) {"true"} Else {"false"} и "a" + (If ($ condition) {"true"} Else {"false"}) Это работает (я пока не уверен, почему): "a" + $ (If ($ condition) {"true"} Else {"false "})
Ламарт

12
@Lamarth - это работает, потому что $()обертка заставляет вычислять оператор как выражение, таким образом возвращая либо истинное значение ложного значения, во многом как ожидалось бы от троичного оператора. Это самое близкое, что вы получите в PowerShell AFAIK.
KeithS

4
В отличие от некоторых других «не функциональных» ответов, это также обеспечит выполнение только одной ветви.
user2864740

63

Самая близкая конструкция PowerShell, которую я смог придумать для эмуляции:

@({'condition is false'},{'condition is true'})[$condition]

15
({true}, {false})[!$condition]немного лучше (возможно): а) традиционный порядок истинных и ложных частей; б) $conditionне обязательно должен быть просто 0 или 1 или $ false, $ true. Оператор !преобразует его по мере необходимости. Т.е. $conditionможет быть, скажем, 42:! 42 ~ $ false ~ 0 ~ первое выражение.
Роман Кузьмин

1
Не совсем идентично, @RomanKuzmin. Пример mjolinor возвращает строку. Но те же строковые значения из его примера, вставленные в ваше выражение, возвращают блок скрипта. :-(
Майкл Соренс,

5
К {true}и {false}я имею в виду <true expression>и <false expression>, не скриптовые блоки. Извините, что не точный.
Роман Кузьмин

1
Спасибо за разъяснения, @ RomanKuzmin - теперь я вижу ценность в вашем предложении.
Майкл Соренс

12
Это заставит нетерпеливо оценивать левый и правый варианты: это сильно отличается от правильной троичной операции.
user2864740


24

В этом сообщении блога PowerShell вы можете создать псевдоним для определения ?:оператора:

set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse) 
{
   if (&$decider) { 
      &$ifTrue
   } else { 
      &$ifFalse 
   }
}

Используйте это так:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})

По-видимому, нет причины, по которой «решающий» является блоком сценариев. Если ?:когда-либо оценивается, это потребует оценки аргументов. Без блока скрипта он будет иметь аналогичное использование, например. (?: ($quantity -le 10) {.9} {.75})
user2864740

Это классная функция, но она может сделать ваш скрипт нестандартным, например, ваш скрипт или его части могут не переноситься, если с ним не перенесено определение псевдонима. Вы в основном создаете свой собственный язык на этом этапе. Все это может привести к увеличению стоимости обслуживания из-за повышенной сложности и снижения читабельности.
Вэнс Маккоркл

1
@VanceMcCorkle Сплошная точка. Пользовательская краткость стоит своей стоимости, если она полезна часто. Но если бы ситуация возникала редко, я бы придерживался выражения «если».
Эдвард Брей

21

Я тоже искал лучший ответ, и хотя решение в сообщении Эдварда "хорошо", в этом сообщении в блоге я нашел гораздо более естественное решение

Коротко и сладко:

# ---------------------------------------------------------------------------
# Name:   Invoke-Assignment
# Alias:  =
# Author: Garrett Serack (@FearTheCowboy)
# Desc:   Enables expressions like the C# operators: 
#         Ternary: 
#             <condition> ? <trueresult> : <falseresult> 
#             e.g. 
#                status = (age > 50) ? "old" : "young";
#         Null-Coalescing 
#             <value> ?? <value-if-value-is-null>
#             e.g.
#                name = GetName() ?? "No Name";
#             
# Ternary Usage:  
#         $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
#         $name = (get-name) ? "No Name" 
# ---------------------------------------------------------------------------

# returns the evaluated value of the parameter passed in, 
# executing it, if it is a scriptblock   
function eval($item) {
    if( $item -ne $null ) {
        if( $item -is "ScriptBlock" ) {
            return & $item
        }
        return $item
    }
    return $null
}

# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
    if( $args ) {
        # ternary
        if ($p = [array]::IndexOf($args,'?' )+1) {
            if (eval($args[0])) {
                return eval($args[$p])
            } 
            return eval($args[([array]::IndexOf($args,':',$p))+1]) 
        }

        # null-coalescing
        if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
            if ($result = eval($args[0])) {
                return $result
            } 
            return eval($args[$p])
        } 

        # neither ternary or null-coalescing, just a value  
        return eval($args[0])
    }
    return $null
}

# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."

Что позволяет легко делать такие вещи, как (больше примеров в блоге):

$message == ($age > 50) ? "Old Man" :"Young Dude" 

1
это довольно круто Я просто не люблю использовать =в качестве псевдонима, потому что ==используется в C # и многих других языках для проверки на равенство. Наличие некоторого опыта в C # довольно распространено среди powershellers, и это делает полученный код немного запутанным. :возможно, будет лучшим псевдонимом. Использование его в сочетании с присваиванием переменной =:может напомнить кому-то об присваивании, используемом в Pascal, но не имеет какого-либо непосредственного эквивалента (который я знаю) ни в VB.NET, ни в C #.
nohwnd

Вы можете установить это на любой псевдоним, который вы хотите. : или ~что-то еще плавает на вашей лодке. : D set-alias : Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment." # ternary $message =: ($age > 50) ? "Old Man" :"Young Dude" # null coalescing $message =: $foo ?? "foo was empty"`
Гаррет Серак

Я знаю, и я сделал, я просто комментировал, почему я бы использовал другой псевдоним.
17

15

Попробуйте в качестве альтернативы оператор переключателя powershell , особенно для назначения переменных - несколько строк, но читаемые.

Пример,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"

1
Это может быть немного более многословно, но есть много преимуществ по сравнению с другими ответами. В частности, нет никаких зависимостей ни от внешнего кода, ни от функций, которые копируются / вставляются в скрипт или модуль.
bshacklett

9

Поскольку при назначении значения обычно используется троичный оператор, он должен возвращать значение. Это способ, которым может работать:

$var=@("value if false","value if true")[[byte](condition)]

Глупо, но работает. Также эту конструкцию можно использовать для быстрого превращения типа int в другое значение, просто добавьте элементы массива и укажите выражение, которое возвращает неотрицательные значения на основе 0.


6
Это заставит нетерпеливо оценивать левый и правый варианты: это сильно отличается от правильной троичной операции.
user2864740

6

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

$var = @{$true="this is true";$false="this is false"}[1 -eq 1]

самый уродливый из всех!

своего рода источник


4
Это заставит нетерпеливо оценивать левый и правый варианты: это сильно отличается от правильной троичной операции.
user2864740

@ user2864740 потому что в PS нет правильной троичной операции. Это синтетический вариант.
Вигго Лунден

2
@ ViggoLundén Отсутствие правильного троичного оператора не снижает правильность комментария. Этот «синтетический вариант» имеет другое поведение .
user2864740

Вы правы, и после перечитывания вашего комментария я понимаю, как это может изменить ситуацию. Спасибо.
sodawillow

5

Недавно я улучшил (откройте PullRequest) троичные условные операторы и операторы с нулевым слиянием в библиотеке Psx PoweShell для
Pls, ищите мое решение.


Моя ветка github: UtilityModule_Invoke-Operators

Функции:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Псевдонимы

Set-Alias :?:   Pscx\Invoke-Ternary                     -Description "PSCX alias"
Set-Alias ?:    Pscx\Invoke-TernaryAsPipe               -Description "PSCX alias"
Set-Alias :??   Pscx\Invoke-NullCoalescing              -Description "PSCX alias"
Set-Alias ??    Pscx\Invoke-NullCoalescingAsPipe        -Description "PSCX alias"

использование

<condition_expression> |?: <true_expression> <false_expression>

<variable_expression> |?? <alternate_expression>

В качестве выражения вы можете передать:
$ null, литерал, переменную, «внешнее» выражение ($ b -eq 4) или блок сценария {$ b -eq 4}

Если переменная в выражении переменной равна $ null или не существует , альтернативное выражение оценивается как вывод.


4

В настоящее время PowerShell не имеет встроенного встроенного If (или троичного If ), но вы можете использовать собственный командлет:

IIf <condition> <condition-is-true> <condition-is-false>
См .: PowerShell Inline If (IIf)


Связанные почтовые показывает , как создать в IIfфункцию. Однако нет стандартной IIfфункции / командлета PowerShell .
user2864740

1
@ user2864740, и что? исключает ли это ответ в качестве возможного пригодного ответа на вопрос: « Каков наилучший способ (то есть легко читаемый и поддерживаемый) для достижения того же результата?» Но это в стороне, ссылка относится к ИИФ вопросу (отправил 5 сентября '14) , который очень похож на это (дубликат?). Остальные ответы в связанном вопросе также можно рассматривать как добавленную стоимость (а если нет, то это в основном потому, что они являются дубликатами).
iRon

Небольшое объяснение имеет большое значение, и именно поэтому голые ссылки не рекомендуется, так как не закрывается для дубликатов, если нет дополнительной дополнительной информации. Учтите, что очень маленькое объяснение значительно повысит качество такого простого ответа: «Powershell не поддерживает прямой« троичный »(^, но смотрите другие ответы). Однако можно написать функцию, такую ​​как (^ используя этот метод, или посмотрите другие ответы) .. ", но это не моя работа, чтобы добавить. Ответы могут быть изменены / обновлены / и т.д. в зависимости от обстоятельств.
user2864740

@ user2864740, спасибо за ваш отзыв, я внес коррективы в ответ соответственно.
ЖЕЛЕЗНЫЙ

1

Вот альтернативный подход к пользовательской функции:

function Test-TernaryOperatorCondition {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [bool]$ConditionResult
        ,
        [Parameter(Mandatory = $true, Position = 0)]
        [PSObject]$ValueIfTrue
        ,
        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateSet(':')]
        [char]$Colon
        ,
        [Parameter(Mandatory = $true, Position = 2)]
        [PSObject]$ValueIfFalse
    )
    process {
        if ($ConditionResult) {
            $ValueIfTrue
        }
        else {
            $ValueIfFalse
        }
    }
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'

пример

1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'

Различия объяснены

  • Почему 3 знака вопроса вместо 1?
    • У ?персонажа уже есть псевдоним Where-Object.
    • ?? используется в других языках как оператор объединения нулей, и я хотел избежать путаницы.
  • Зачем нам труба перед командой?
    • Поскольку я использую конвейер для оценки этого, нам все еще нужен этот символ, чтобы передать условие в нашу функцию
  • Что произойдет, если я передам массив?
    • Мы получаем результат для каждого значения; т.е. -2..2 |??? 'match' : 'nomatch'дает: match, match, nomatch, match, match(т. е. поскольку любое ненулевое int оценивается как true; в то время как нулевое оценивается как false).
    • Если вы не хотите этого, преобразуйте массив в bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(или просто: [bool](-2..2) |??? 'match' : 'nomatch')

1

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

"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)

Если вас когда-нибудь интересует результат, когда что-то верно, вы можете просто полностью пропустить ложную часть (или наоборот), например, простую систему подсчета очков:

$isTall = $true
$isDark = $false
$isHandsome = $true

$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"

Обратите внимание, что логическое значение не должно быть ведущим в умножении, т. Е. $ Condition * "true" и т. Д. Не будет работать.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.