about_Pipeline_Chain_Operators
简短说明
介绍如何在 PowerShell 中使用 &&
和 ||
运算符来链接管道。
长说明
从 PowerShell 7 开始,PowerShell 7 实现了 &&
和 ||
运算符,用于有条件地链接管道。 这些运算符在 PowerShell 中称为“管道链运算符”,与 POSIX shell(如 bash、zsh 和 sh)中的 AND-OR 列表以及 Windows 命令 Shell (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' }
and
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
链接管道而非命令
管道链运算符,顾名思义,可用于链接管道,而不是仅仅用于链接命令。 这与其他 shell 的行为匹配,但可能会使成功更难以确定:
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
在生成非终止性错误后被视为已失败。