Toán tử ternary trong PowerShell


214

Từ những gì tôi biết, PowerShell dường như không có biểu thức tích hợp cho cái gọi là toán tử ternary .

Ví dụ, trong ngôn ngữ C, hỗ trợ toán tử ternary, tôi có thể viết một cái gì đó như:

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

Nếu điều đó không thực sự tồn tại trong PowerShell, điều gì sẽ là cách tốt nhất (nghĩa là dễ đọc và dễ duy trì) để đạt được kết quả tương tự?


5
Hãy xem github.com/nightroman/PowerShellTraps/tree/master/Basic/ mẹo . Nếu đây là những gì bạn đang tìm kiếm tôi có thể làm cho nó một câu trả lời.
Roman Kuzmin

2
Đó là một toán tử có điều kiện hoặc ternary nếu . Đó không phải là "toán tử ternary" vì tất cả điều đó có nghĩa là một toán tử ( bất kỳ toán tử nào ) có ba đối số.
Damien_The_Unbeliever

7
@Damien_The_Unbeliever Điều đó đúng về mặt kỹ thuật, nhưng nó thường được gọi là toán tử ternary. "Vì toán tử này thường là toán tử ternary duy nhất hiện có trong ngôn ngữ, nên đôi khi nó được gọi đơn giản là" toán tử ternary ". Trong một số ngôn ngữ, toán tử này được gọi là" toán tử có điều kiện. " Hoạt động
ternary

Visual basic không có toán tử ternary thực sự nhưng coi IF và IFF là tương đương về chức năng.
Matt

@Matt Điều đó không chính xác. Các IIF chức năng sẽ luôn đánh giá cả hai toán hạng. Câu Iflệnh sẽ không - xem stackoverflow.com/questions/1220411/ ((phiên bản VB.NET mới hơn đã thêm biểu thức ternary If (x, y, z):)
user2864740

Câu trả lời:


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

Mọi thứ khác là sự phức tạp ngẫu nhiên và do đó phải tránh.

Để sử dụng trong hoặc như một biểu thức, không chỉ là một bài tập, hãy gói nó vào $(), do đó:

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

3
Nó hoạt động ở phía bên phải của một đẳng thức, nhưng không hoàn toàn như bạn mong đợi một toán tử ternary - những lỗi này: "a" + If ($ condition) {"true"} Khác {"false"}"a" + (If ($ condition) {"true"} Else {"false"}) Điều này hoạt động (Tôi chưa chắc tại sao): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth

12
@Lamarth - Điều đó hoạt động vì $()trình bao bọc buộc đánh giá câu lệnh dưới dạng một biểu thức, do đó trả về giá trị thực của giá trị sai, giống như một toán tử ternary sẽ được mong đợi. Đây là lần gần nhất bạn sẽ nhận được trong PowerShell AFAIK.
KeithS

4
Không giống như một số câu trả lời "không chức năng" khác, điều này cũng sẽ đảm bảo rằng chỉ có một nhánh được thực thi.
dùng2864740

63

Cấu trúc PowerShell gần nhất mà tôi có thể đưa ra để mô phỏng đó là:

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

15
({true}, {false})[!$condition]là tốt hơn một chút (có lẽ): a) thứ tự truyền thống của các phần đúng và sai; b) $conditionkhông phải chỉ là 0 hoặc 1 hoặc $ false, $ true. Toán tử !chuyển đổi nó khi cần thiết. Tức là $conditioncó thể, giả sử, 42 :! 42 ~ $ false ~ 0 ~ biểu thức đầu tiên.
Roman Kuzmin

1
Không hoàn toàn giống nhau, @RomanKuzmin. ví dụ của mjolinor trả về một chuỗi. Nhưng các giá trị chuỗi tương tự từ ví dụ của anh ấy cắm vào biểu thức của bạn sẽ trả về một khối tập lệnh. :-(
Michael Sorens

5
Bằng {true}{false}tôi muốn nói <true expression><false expression>, không khối kịch bản. Xin lỗi vì không chính xác.
La Mã Kuzmin

1
Cảm ơn bạn đã làm rõ, @ RomanKuzmin - bây giờ tôi thấy giá trị trong đề xuất của bạn.
Michael Sorens

12
Điều này sẽ buộc đánh giá háo hức các lựa chọn bên trái và bên phải: điều này khác nhiều so với một hoạt động ternary thích hợp.
dùng2864740

24

Trên mỗi bài đăng trên blog PowerShell này , bạn có thể tạo bí danh để xác định ?:toán tử:

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

Sử dụng nó như thế này:

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

Dường như không có lý do nào mà 'người quyết định' là một người viết kịch bản. Nếu ?:được đánh giá, nó sẽ yêu cầu các đối số được đánh giá. Nếu không có khối script, nó sẽ có cách sử dụng tương tự, vd. (?: ($quantity -le 10) {.9} {.75})
dùng2864740

Đó là một tính năng thú vị nhưng nó có thể khiến tập lệnh của bạn không chuẩn, ví dụ tập lệnh hoặc các phần của tập lệnh có thể không chuyển trừ khi định nghĩa bí danh cũng được chuyển với nó. Về cơ bản, bạn đang tạo ngôn ngữ phụ của riêng bạn tại thời điểm này. Tất cả điều này có thể dẫn đến tăng chi phí bảo trì do độ phức tạp tăng và khả năng đọc giảm.
Vance McCorkle

1
@VanceMcCorkle Điểm rắn. Độ căng tùy chỉnh đáng giá của nó nếu thường xuyên hữu ích. Nhưng nếu tình huống chỉ xảy ra hiếm khi xảy ra, tôi sẽ sử dụng biểu thức "nếu".
Edward Brey


21

Tôi cũng vậy, tìm kiếm một câu trả lời tốt hơn, và trong khi giải pháp trong bài viết của Edward là "ok", tôi đã đưa ra một giải pháp tự nhiên hơn nhiều trong bài đăng trên blog này

Ngắn và ngọt:

# ---------------------------------------------------------------------------
# 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."

Điều này giúp bạn dễ dàng thực hiện những thứ như (nhiều ví dụ trong bài đăng trên blog):

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

1
Điều này là khá tuyệt vời. Tôi chỉ không thích sử dụng =làm bí danh, vì ==được sử dụng trong C # và rất nhiều ngôn ngữ khác để kiểm tra sự bình đẳng. Có một số kinh nghiệm về C # là khá phổ biến giữa các nhà cung cấp năng lượng và điều đó làm cho mã kết quả hơi khó hiểu. :có thể sẽ là một bí danh tốt hơn. Sử dụng kết hợp với phép gán biến =:có thể nhắc nhở ai đó về phép gán được sử dụng trong Pascal, nhưng không có bất kỳ tương đương ngay lập tức (mà tôi biết) trong VB.NET cũng như C #.
không có

Bạn có thể đặt cái này thành bất kỳ bí danh nào bạn muốn. : hoặc ~bất cứ điều gì nổi thuyền của bạn. : D set-alias : Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment." # ternary $message =: ($age > 50) ? "Old Man" :"Young Dude" # null hợp nhất $message =: $foo ?? "foo was empty"`
Garrett Serack

Tôi biết, và tôi đã làm, tôi chỉ bình luận tại sao tôi sẽ sử dụng một bí danh khác.
không có

15

Hãy thử câu lệnh chuyển đổi của powershell như một cách thay thế, đặc biệt là cho phép gán biến - nhiều dòng, nhưng có thể đọc được.

Thí dụ,

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

1
Điều này có thể dài dòng hơn một chút, nhưng có những lợi ích đáng kể so với nhiều câu trả lời khác. Cụ thể, không có sự phụ thuộc vào mã bên ngoài, cũng như các chức năng được sao chép / dán vào tập lệnh hoặc mô-đun.
bshacklett

9

Vì một toán tử ternary thường được sử dụng khi gán giá trị, nên nó sẽ trả về một giá trị. Đây là cách có thể làm việc:

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

Ngốc, nhưng làm việc. Ngoài ra, cấu trúc này có thể được sử dụng để nhanh chóng biến một int thành một giá trị khác, chỉ cần thêm các phần tử mảng và chỉ định một biểu thức trả về các giá trị không âm dựa trên 0.


6
Điều này sẽ buộc đánh giá háo hức các lựa chọn bên trái và bên phải: điều này khác nhiều so với một hoạt động ternary thích hợp.
dùng2864740

6

Vì tôi đã sử dụng nó nhiều lần rồi và không thấy nó được liệt kê ở đây, tôi sẽ thêm tác phẩm của mình:

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

xấu nhất trong tất cả!

nguồn kinda


4
Điều này sẽ buộc đánh giá háo hức các lựa chọn bên trái và bên phải: điều này khác nhiều so với một hoạt động ternary thích hợp.
dùng2864740

@ user2864740 đó là vì không có hoạt động ternary thích hợp trong PS. Đây là một biến thể tổng hợp.
Viggo Lundén

2
@ ViggoLundén Thiếu một nhà điều hành ternary thích hợp không làm giảm tính chính xác của nhận xét. "Biến thể tổng hợp" này có hành vi khác nhau .
dùng2864740

Bạn nói đúng, và sau khi đọc lại bình luận của bạn, tôi hiểu làm thế nào nó có thể tạo ra sự khác biệt thực sự. Cảm ơn.
sodawvel

5

Gần đây tôi đã cải thiện (mở PullRequest) các toán tử kết hợp có điều kiện và không kết hợp ba phần trong PoweShell lib 'Pscx'
Xin hãy xem giải pháp của tôi.


Chi nhánh chủ đề github của tôi: UtilityModule_Invoke-Toán tử

Chức năng:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Bí danh

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"

Sử dụng

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

<variable_expression> |?? <alternate_expression>

Là biểu thức bạn có thể vượt qua:
$ null, nghĩa đen, biến, biểu thức 'bên ngoài' ($ b -eq 4) hoặc scriptblock {$ b -eq 4}

Nếu một biến trong biểu thức biến là $ null hoặc không tồn tại , biểu thức thay thế được đánh giá là đầu ra.


4

PowerShell hiện không có Inline If gốc (hoặc ternary If ) nhưng bạn có thể cân nhắc sử dụng lệnh ghép ngắn tùy chỉnh:

IIf <condition> <condition-is-true> <condition-is-false>
Xem: Nội tuyến PowerShell If (IIf)


Các bài đăng được liên kết cho thấy làm thế nào để tạo ra một IIfchức năng. Tuy nhiên, không có IIfchức năng / cmdlet PowerShell tiêu chuẩn .
dùng2864740

1
@ user2864740, vậy thì sao? điều đó có loại trừ câu trả lời như một câu trả lời có thể sử dụng được cho câu hỏi: " điều gì sẽ là cách tốt nhất (nghĩa là dễ đọc và dễ duy trì) để đạt được kết quả tương tự?" Nhưng bên cạnh đó, liên kết đề cập đến câu hỏi IIf (được đăng ngày 5 tháng 9 năm 14) rất giống với câu hỏi này (trùng lặp?). Phần còn lại của các câu trả lời trong câu hỏi được liên kết cũng có thể được coi là giá trị gia tăng (và ở đâu không, chủ yếu là vì chúng là các bản sao).
iRon

Một chút giải thích đi một chặng đường dài và đây là lý do tại sao các liên kết trần không được khuyến khích, vì sẽ không đóng cửa cho các bản sao nếu không có thêm thông tin bổ sung. Hãy xem xét rằng một lời giải thích rất nhỏ sẽ làm tăng đáng kể chất lượng của một câu trả lời liên kết trần như vậy : "Powershell không hỗ trợ một 'ternary' trực tiếp (^ nhưng xem các câu trả lời khác). Tuy nhiên, có thể viết một hàm như (^ bằng cách sử dụng phương pháp này hoặc xem các câu trả lời khác) .. ", nhưng đó không phải công việc của tôi để thêm. Câu trả lời có thể được sửa đổi / cập nhật / vv nếu có.
dùng2864740

@ user2864740, cảm ơn phản hồi của bạn, tôi đã điều chỉnh câu trả lời tương ứng.
iRon

1

Đây là một cách tiếp cận chức năng tùy chỉnh thay thế:

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'

Thí dụ

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

Giải thích về sự khác biệt

  • Tại sao lại là 3 dấu hỏi thay vì 1?
    • Các ?nhân vật đã là một bí danh cho Where-Object.
    • ?? được sử dụng trong các ngôn ngữ khác như là một toán tử hợp nhất null và tôi muốn tránh nhầm lẫn.
  • Tại sao chúng ta cần đường ống trước lệnh?
    • Vì tôi đang sử dụng đường ống để đánh giá điều này, chúng tôi vẫn cần nhân vật này đưa điều kiện vào chức năng của chúng tôi
  • Điều gì xảy ra nếu tôi vượt qua trong một mảng?
    • Chúng tôi nhận được một kết quả cho mỗi giá trị; tức là -2..2 |??? 'match' : 'nomatch'đưa ra: match, match, nomatch, match, match(tức là vì bất kỳ int nào khác không ước tính true; trong khi zero đánh giá false).
    • Nếu bạn không muốn điều đó, hãy chuyển đổi mảng thành bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(hoặc đơn giản là [bool](-2..2) |??? 'match' : 'nomatch':)

1

Nếu bạn chỉ tìm kiếm một cách đơn giản về mặt cú pháp để gán / trả về một chuỗi hoặc số dựa trên điều kiện boolean, bạn có thể sử dụng toán tử nhân như sau:

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

Nếu bạn chỉ quan tâm đến kết quả khi có gì đó đúng, bạn chỉ có thể bỏ qua phần sai hoàn toàn (hoặc ngược lại), ví dụ: một hệ thống tính điểm đơn giản:

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

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

Lưu ý rằng giá trị boolean không phải là thuật ngữ hàng đầu trong phép nhân, tức là $ condition * "true", v.v. sẽ không hoạt động.


1

Kể từ phiên bản PowerShell 7, toán tử ternary được tích hợp vào PowerShell.

1 -gt 2 ? "Yes" : "No"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.