about_Pipeline_Chain_Operators

簡短描述

描述在 PowerShell 中使用 &&|| 運算子鏈結管線。

詳細描述

從 PowerShell 7 開始,PowerShell 會實作 &&|| 運算符,以有條件地鏈結管線。 這些運算符在 PowerShell 中稱為管線鏈結運算符,類似於 POSIX 殼層中的 AND-OR 清單 ,例如 bash、zsh 和 sh,以及 Windows 命令殼層中的條件式處理符號 (cmd.exe)。

如果左側管線成功,運算符 && 會執行右側管線。 相反地,如果左側管線失敗, || 運算符會執行右側管線。

這些運算子會使用 $?$LASTEXITCODE 變數來判斷管線是否失敗。 這可讓您搭配原生命令使用它們,而不只是搭配 Cmdlet 或函式使用。 例如:

# Create an SSH key pair - if successful copy the public key to clipboard
ssh-keygen -t rsa -b 2048 && Get-Content -Raw ~\.ssh\id_rsa.pub | clip

範例

兩個成功的命令

Write-Output 'First' && Write-Output 'Second'
First
Second

第一個命令失敗,導致第二個命令無法執行

Write-Error 'Bad' && Write-Output 'Second'
Write-Error: Bad

第一個命令成功,因此不會執行第二個命令

Write-Output 'First' || Write-Output 'Second'
First

第一個命令失敗,因此會執行第二個命令

Write-Error 'Bad' || Write-Output 'Second'
Write-Error: Bad
Second

管線成功是由變數的值 $? 所定義,PowerShell 會根據管線的執行狀態在執行管線之後自動設定。 這表示管線鏈結運算符具有下列等價:

Test-Command '1' && Test-Command '2'

的運作方式與相同

Test-Command '1'; if ($?) { Test-Command '2' }

Test-Command '1' || Test-Command '2'

的運作方式與相同

Test-Command '1'; if (-not $?) { Test-Command '2' }

從管線鏈結指派

從管線鏈結指派變數會採用鏈結中所有管線的串連:

$result = Write-Output '1' && Write-Output '2'
$result
1
2

如果從管線鏈結指派期間發生腳本終止錯誤,指派不會成功:

try
{
    $result = Write-Output 'Value' && $(throw 'Bad')
}
catch
{
    # Do nothing, just squash the error
}

"Result: $result"
Result:

運算符語法和優先順序

與其他運算符不同,&&而且||在管線上運作,而不是在 或 -and+表達式上運作。

&&|| 的優先順序低於管線 (|) 或重新導向 (>),但優先順序高於作業運算元 ()、指派 (&=) 或分號 (;)。 這表示管線鏈結內的管線可以個別重新導向,而且整個管線鏈結可以背景處理、指派給變數或分隔為語句。

若要在管線鏈結中使用優先順序較低的語法,請考慮使用括弧 (...)。 同樣地,若要在管線鏈結內嵌 語句,可以使用子表達式 $(...) 。 這在結合原生命令與控制流程時很有用:

foreach ($file in 'file1','file2','file3')
{
    # When find succeeds, the loop breaks
    find $file && Write-Output "Found $file" && $(break)
}
find: file1: No such file or directory
file2
Found file2

從 PowerShell 7 起,這些語法的行為已變更,因此 $? 當命令在括號或子表達式內成功或失敗時,就會如預期般設定。

就像 PowerShell 中大部分的其他運算子一樣, && 而且 || 也是 左關聯運算元,這表示它們會從左邊分組。 例如:

Get-ChildItem -Path ./file.txt ||
    Write-Error "file.txt doesn't exist" &&
    Get-Content -Raw ./file.txt

將會群組為:

(Get-ChildItem -Path ./file.txt || Write-Error "file.txt doesn't exist") &&
    Get-Content -Raw ./file.txt

相當於:

Get-ChildItem -Path ./file.txt

if (-not $?) { Write-Error "file.txt does not exist" }

if ($?) { Get-Content -Raw ./file.txt }

錯誤互動

管線鏈結運算子不會吸收錯誤。 當管線鏈結中的語句擲回腳本終止錯誤時,管線鏈結就會終止。

例如:

$(throw 'Bad') || Write-Output '2'
Exception: Bad

即使攔截到錯誤,管線鏈結仍會終止:

try
{
    $(throw 'Bad') || Write-Output '2'
}
catch
{
    Write-Output "Caught: $_"
}
Write-Output 'Done'
Caught: Bad
Done

如果錯誤為非終止,或只終止管線,管線鏈結會繼續,並遵循的值 $?

function Test-NonTerminatingError
{
    [CmdletBinding()]
    param()

    $exception = [System.Exception]::new('BAD')
    $errorId = 'BAD'
    $errorCategory = 'NotSpecified'

    $errorRecord = [System.Management.Automation.ErrorRecord]::new(
        $exception, $errorId, $errorCategory, $null
    )

    $PSCmdlet.WriteError($errorRecord)
}

Test-NonTerminatingError || Write-Output 'Second'
Test-NonTerminatingError: BAD
Second

鏈結管線而不是命令

管線鏈結運算子的名稱可用來鏈結管線,而不只是命令。 這符合其他殼層的行為,但可能會讓成功更難判斷:

function Test-NotTwo
{
    [CmdletBinding()]
    param(
      [Parameter(ValueFromPipeline)]
      $Input
    )

    process
    {
        if ($Input -ne 2)
        {
            return $Input
        }

        $exception = [System.Exception]::new('Input is 2')
        $errorId = 'InputTwo'
        $errorCategory = 'InvalidData'

        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception, $errorId, $errorCategory, $null
        )

        $PSCmdlet.WriteError($errorRecord)
    }
}

1,2,3 | Test-NotTwo && Write-Output 'All done!'
1
Test-NotTwo : Input is 2
3

請注意, Write-Output 'All done!' 不會執行,因為在 Test-NotTwo 產生非終止錯誤之後,會被視為失敗。

另請參閱