|
| 1 | +--- |
| 2 | +ms.date: 09/30/2019 |
| 3 | +schema: 2.0.0 |
| 4 | +locale: en-us |
| 5 | +keywords: powershell,cmdlet |
| 6 | +title: about_Pipeline_Chain_Operators |
| 7 | +--- |
| 8 | + |
| 9 | +# About Pipeline Chain Operators |
| 10 | + |
| 11 | +## Short description |
| 12 | + |
| 13 | +Describes chaining pipelines with the `&&` and `||` operators in PowerShell. |
| 14 | + |
| 15 | +## Long description |
| 16 | + |
| 17 | +From PowerShell 7, PowerShell implements the `&&` and `||` operators |
| 18 | +to conditionally chain pipelines. |
| 19 | +These operators are known in PowerShell as *pipeline chain operators*, |
| 20 | +and are similar to [AND-OR lists](https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_03) |
| 21 | +in POSIX shells like bash, zsh and sh, |
| 22 | +as well as [conditional processing symbols](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10)#using-multiple-commands-and-conditional-processing-symbols) |
| 23 | +in Windows' command shell (cmd.exe). |
| 24 | + |
| 25 | +`&&` and `||` allow conditional execution of PowerShell commands and pipelines |
| 26 | +based on the success of the left-hand pipeline. |
| 27 | +In particular, they allow conditional execution of native executables |
| 28 | +based on execution success: |
| 29 | + |
| 30 | +```powershell |
| 31 | +npm run build && npm run deploy |
| 32 | +``` |
| 33 | + |
| 34 | +The `&&` operator will execute the right-hand pipeline, |
| 35 | +if the left-hand pipeline succeeded, |
| 36 | +whereas the `||` operator will execute the right-hand pipeline |
| 37 | +if the left-hand pipeline failed: |
| 38 | + |
| 39 | +```powershell |
| 40 | +Write-Output 'First' && Write-Output 'Second' |
| 41 | +``` |
| 42 | + |
| 43 | +```Output |
| 44 | +First |
| 45 | +Second |
| 46 | +``` |
| 47 | + |
| 48 | +```powershell |
| 49 | +Write-Error 'Bad' && Write-Output 'Second' |
| 50 | +``` |
| 51 | + |
| 52 | +```Output |
| 53 | +Write-Error 'Bad' && Write-Output 'Second' : Bad |
| 54 | ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException |
| 55 | ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException |
| 56 | +``` |
| 57 | + |
| 58 | +```powershell |
| 59 | +Write-Output 'First' || Write-Output 'Second' |
| 60 | +``` |
| 61 | + |
| 62 | +```Output |
| 63 | +First |
| 64 | +``` |
| 65 | + |
| 66 | +```powershell |
| 67 | +Write-Error 'Bad' || Write-Output 'Second' |
| 68 | +``` |
| 69 | + |
| 70 | +```Output |
| 71 | +Write-Error 'Bad' && Write-Output 'Second' : Bad |
| 72 | ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException |
| 73 | ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException |
| 74 | +
|
| 75 | +Second |
| 76 | +``` |
| 77 | + |
| 78 | +Pipeline success is defined by the value of the `$?` variable, |
| 79 | +which PowerShell automatically sets after executing a pipeline based on its execution status. |
| 80 | +This means that pipeline chain operators have the following equivalence: |
| 81 | + |
| 82 | +```powershell |
| 83 | +Test-Command '1' && Test-Command '2' |
| 84 | +``` |
| 85 | + |
| 86 | +works the same as |
| 87 | + |
| 88 | +```powershell |
| 89 | +Test-Command '1'; if ($?) { Test-Command '2' } |
| 90 | +``` |
| 91 | + |
| 92 | +and |
| 93 | + |
| 94 | +```powershell |
| 95 | +Test-Command '1' || Test-Command '2' |
| 96 | +``` |
| 97 | + |
| 98 | +works the same as |
| 99 | + |
| 100 | +```powershell |
| 101 | +Test-Command '1'; if (-not $?) { Test-Command '2' } |
| 102 | +``` |
| 103 | + |
| 104 | +### Assignment from pipeline chains |
| 105 | + |
| 106 | +Assigning a variable from a pipeline chain takes the concatenation |
| 107 | +of all the pipelines in the chain: |
| 108 | + |
| 109 | +```powershell |
| 110 | +$result = Write-Output '1' && Write-Output '2' |
| 111 | +$result |
| 112 | +``` |
| 113 | + |
| 114 | +```Output |
| 115 | +1 |
| 116 | +2 |
| 117 | +``` |
| 118 | + |
| 119 | +If a script-terminating error is thrown during assignment from a pipeline chain, |
| 120 | +the assignment does not succeed: |
| 121 | + |
| 122 | +```powershell |
| 123 | +try |
| 124 | +{ |
| 125 | + $result = Write-Output 'Value' && $(throw 'Bad') |
| 126 | +} |
| 127 | +catch |
| 128 | +{ |
| 129 | + # Do nothing, just squash the error |
| 130 | +} |
| 131 | +
|
| 132 | +"Result: $result" |
| 133 | +``` |
| 134 | + |
| 135 | +```Output |
| 136 | +Result: |
| 137 | +``` |
| 138 | + |
| 139 | +### Operator syntax and precedence |
| 140 | + |
| 141 | +Unlike other operators, `&&` and `||` operate on pipelines, |
| 142 | +rather than on expressions (like, for example, `+` or `-and`). |
| 143 | + |
| 144 | +`&&` and `||` have a lower precedence than piping (`|`) or redirection (`>`), |
| 145 | +but a higher precdence than job operators (`&`), assignment (`=`) or semicolons (`;`). |
| 146 | +This means that pipelines within a pipeline chain can be individually redirected, |
| 147 | +and that entire pipeline chains can be backgrounded, assigned from |
| 148 | +or separated as statements. |
| 149 | + |
| 150 | +To use lower precedence syntax within a pipeline chain, |
| 151 | +consider the use of parentheses `(...)` or a subexpression `$(...)`. |
| 152 | + |
| 153 | +### Error interaction |
| 154 | + |
| 155 | +Pipeline chain operators, particularly the `||` operator, |
| 156 | +do not absorb errors. |
| 157 | +If a statement in a pipeline chain throws a script-terminating error, |
| 158 | +that will abort the pipeline chain: |
| 159 | + |
| 160 | +```powershell |
| 161 | +$(throw 'Bad') || Write-Output '2' |
| 162 | +``` |
| 163 | + |
| 164 | +```Output |
| 165 | +Bad |
| 166 | +At line:1 char:3 |
| 167 | ++ $(throw 'Bad') || Write-Output '2' |
| 168 | ++ ~~~~~~~~~~~ |
| 169 | ++ CategoryInfo : OperationStopped: (Bad:String) [], RuntimeException |
| 170 | ++ FullyQualifiedErrorId : Bad |
| 171 | +``` |
| 172 | + |
| 173 | +If the error is caught, the pipeline chain will still be cut short: |
| 174 | + |
| 175 | +```powershell |
| 176 | +try |
| 177 | +{ |
| 178 | + $(throw 'Bad') || Write-Output '2' |
| 179 | +} |
| 180 | +catch |
| 181 | +{ |
| 182 | + Write-Output "Caught: $_" |
| 183 | +} |
| 184 | +Write-Output 'Done' |
| 185 | +``` |
| 186 | + |
| 187 | +```Output |
| 188 | +Caught: Bad |
| 189 | +Done |
| 190 | +``` |
| 191 | + |
| 192 | +If an error is non-terminating, |
| 193 | +or only terminates a pipeline, |
| 194 | +the pipeline chain will continue, respecting on `$?`: |
| 195 | + |
| 196 | +```powershell |
| 197 | +function Test-NonTerminatingError |
| 198 | +{ |
| 199 | + [CmdletBinding()] |
| 200 | + param() |
| 201 | +
|
| 202 | + $exception = [System.Exception]::new('BAD') |
| 203 | + $errorId = 'BAD' |
| 204 | + $errorCategory = 'NotSpecified' |
| 205 | +
|
| 206 | + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) |
| 207 | +
|
| 208 | + $PSCmdlet.WriteError($errorRecord) |
| 209 | +} |
| 210 | +
|
| 211 | +Test-NonTerminatingError || Write-Output 'Second' |
| 212 | +``` |
| 213 | + |
| 214 | +```Output |
| 215 | +Test-NonTerminatingError : BAD |
| 216 | +At line:1 char:1 |
| 217 | ++ Test-NonTerminatingError || Write-Output 'Second' |
| 218 | ++ ~~~~~~~~~~~~~~~~~~~~~~~~ |
| 219 | ++ CategoryInfo : NotSpecified: (:) [Test-NonTerminatingError], Exception |
| 220 | ++ FullyQualifiedErrorId : BAD,Test-NonTerminatingError |
| 221 | +
|
| 222 | +Second |
| 223 | +``` |
| 224 | + |
| 225 | +### Chaining pipelines rather than commands |
| 226 | + |
| 227 | +Pipeline chain operators, by their name, can be used to chain pipelines, |
| 228 | +rather than just commands. |
| 229 | +This matches the behaviour of other shells, |
| 230 | +but can make "success" harder to reason about: |
| 231 | + |
| 232 | +```powershell |
| 233 | +function Test-NotTwo |
| 234 | +{ |
| 235 | + [CmdletBinding()] |
| 236 | + param( |
| 237 | + [Parameter(ValueFromPipeline)] |
| 238 | + $Input |
| 239 | + ) |
| 240 | +
|
| 241 | + process |
| 242 | + { |
| 243 | + if ($Input -ne 2) |
| 244 | + { |
| 245 | + return $Input |
| 246 | + } |
| 247 | +
|
| 248 | + $exception = [System.Exception]::new('Input is 2') |
| 249 | + $errorId = 'InputTwo' |
| 250 | + $errorCategory = 'InvalidData' |
| 251 | +
|
| 252 | + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) |
| 253 | +
|
| 254 | + $PSCmdlet.WriteError($errorRecord) |
| 255 | + } |
| 256 | +} |
| 257 | +
|
| 258 | +1,2,3 | Test-NotTwo && Write-Output 'All done!' |
| 259 | +``` |
| 260 | + |
| 261 | +```Output |
| 262 | +1 |
| 263 | +Test-NotTwo : Input is 2 |
| 264 | +At line:1 char:9 |
| 265 | ++ 1,2,3 | Test-NotTwo && Write-Output 'All done!' |
| 266 | ++ ~~~~~~~~~~~ |
| 267 | ++ CategoryInfo : InvalidData: (:) [Test-NotTwo], Exception |
| 268 | ++ FullyQualifiedErrorId : InputTwo,Test-NotTwo |
| 269 | +
|
| 270 | +3 |
| 271 | +``` |
| 272 | + |
| 273 | +Note that `Write-Output 'All done!'` is not executed, |
| 274 | +since `Test-NotTwo` is deemed to have failed |
| 275 | +after throwing the non-terminating error. |
| 276 | + |
| 277 | +## See also |
| 278 | + |
| 279 | +- [about_Operators](about_Operators.md) |
| 280 | +- [about_Automatic_Variables](about_Automatic_Variables.md) |
| 281 | +- [about_pipelines](about_pipelines.md) |
0 commit comments