owned this note
owned this note
Published
Linked with GitHub
# Stdio Redirection Behavior
Expressions and blocks in nushell return a single `Value` or `PipelineData` (from the last element of the last pipeline). This document is where we can start to better specify how stdout and stderr from the other pipeline elements are handled.
Consider the following code `Block`:
```nushell
^cmd1 | ^cmd2
^cmd3 | ^cmd4
```
This `Block` has two `Pipeline`s, each with two `PipelineElement`s:
- `cmd4` is the last element of the last pipeline, and its stdout is the "return" stream/output for the block.
- `cmd3` is part of the last pipeline, but its not the last element. Its stdout is piped into `cmd4`.
- `cmd2` is the last element of a pipeline, but the pipeline is not the last pipeline of the block. If this block is not part of a subexpression, then the stdout of `cmd2` is forwarded to the terminal.
- `cmd1` is part of a pipeline, but it is not the last element and its pipeline is not the last either. Its stdout is piped into `cmd2`.
For testing, you can use something like:
```nushell
nu -c 'print -e e1; print o1' | nu -c 'print -e e2; print o2'
nu -c 'print -e e3; print o3' | nu -c 'print -e e4; print o4'
```
# Behavior
We can use tables to organize the intended redirection destination for each command's stdio streams. For example, top level code blocks output everything to the terminal:
| Command | Stdout | Stderr |
| ------- | -------- | -------- |
| cmd1 | Piped | Terminal |
| cmd2 | Terminal | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Terminal | Terminal |
## Subexpression (no redirection)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4)
```
Current behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *Ignored* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Terminal | Terminal |
Desired behavior:
| Command | Stdout | Stderr |
| ------- | -------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *Terminal* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Terminal | Terminal |
## Pipe (`|`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) | ^cmd5
```
Current behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *Ignored* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Piped | Terminal |
Desired behavior:
| Command | Stdout | Stderr |
| ------- | -------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *Terminal* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Piped | Terminal |
## Stderr Pipe (`e>|`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) e>| ^cmd5
```
Current behavior: Error
Desired behavior:
| Command | Stdout | Stderr |
| ------- | -------- | -------- |
| cmd1 | Piped | Terminal |
| cmd2 | Terminal | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Terminal | Piped |
## Combined Pipe (`o+e>|`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) o+e>| ^cmd5
```
Current behavior: Error
Desired behavior:
| Command | Stdout | Stderr |
| ------- | -------- | -------- |
| cmd1 | Piped | Terminal |
| cmd2 | Terminal | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | Piped | Piped |
## Stdout File Redirect (`o>`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) o> test.out
```
Current behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *Ignored* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | File | Terminal |
Desired behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | Terminal |
| cmd2 | *File* | Terminal |
| cmd3 | Piped | Terminal |
| cmd4 | File | Terminal |
## Stderr File Redirect (`e>`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) e> test.out
```
Current behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | *Terminal* |
| cmd2 | *Ignored* | *Terminal* |
| cmd3 | Piped | *Terminal* |
| cmd4 | *File* | *Terminal* |
Note that the current behavior redirects stdout instead of stderr
Desired behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | *File* |
| cmd2 | *Terminal* | *File* |
| cmd3 | Piped | *File* |
| cmd4 | *Terminal* | *File* |
## Combined File Redirect (`o+e>`)
```nushell
(^cmd1 | ^cmd2; ^cmd3 | ^cmd4) o+e> test.out
```
Current behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | *Terminal* |
| cmd2 | *Ignored* | *Terminal* |
| cmd3 | Piped | *Terminal* |
| cmd4 | File | *Terminal* |
Note that the current behavior only redirects stdout and ignores stderr
Desired behavior:
| Command | Stdout | Stderr |
| ------- | ---------- | ---------- |
| cmd1 | Piped | *File* |
| cmd2 | *File* | *File* |
| cmd3 | Piped | *File* |
| cmd4 | File | *File* |
# Other things to figure out
- Do closures invocations and custom command calls count as part of their current/parent block, or do they count as a new subblock/subexpression? I.e., should there be a difference between:
- ```nushell
1..2 | each {|i| ^echo $i; $i }
1..2 | (each {|i| ^echo $i; $i })
```
- ```nushell
def cmd [] { let x = $in; ^echo $x; $x }
"text" | cmd
"text" | (cmd)
```
- How is stdio hooked up for other closures:
- env conversions
- hooks
- style computers
- completions
- lazy records
- Relation to other commands:
- `print`
- `do`
- `complete`
- `run-external`
- `ignore`
<!--
# Proposal
1. File redirections apply to all subexpressions and pipelines:
```nushell
(^cmd1; ^cmd2 | ^cmd3 (^cmd4)) e> err.out
```
This redirects stderr for all the `cmd`s above.
2. Pipes and pipe redirections apply only to the last element of the last pipeline:
```nushell
(^cmd1; ^cmd2 | ^cmd3 (^cmd4)) e>| ^cmd5
```
This redirects only `cmd3`'s stderr to `cmd5`.
## Rationale
Without threading. I'm not sure there's a way to redirect multiple command outputs into another command. For example, take:
```nushell
(^cmd1; ^cmd2) | ^cmd3
```
In order redirect `cmd1` and `cmd2` output to `cmd3`, the shell would first have to spawn `cmd1` and then use its stdout as stdin for `cmd3`. It would then have to wait for `cmd1` to finish, checking that `cmd1` did not have a non-zero exit code. Then, while consuming output of `cmd3`, the shell would have to stop and backtrack to spawn `cmd2` and hook it up as input to `cmd3`.
If we say that pipes `|`, `e>|`, and `o+e>|` only apply to last command of the last pipeline of a block, then this avoids this problem. In fact, this is how the regular pipes (`|`) currently work, since in most cases we only want to pipe the last command output anyways. The problem that then arises is what should be done with the other pipelines in a block (i.e., `cmd1`)? We could ignore their stdout like we currently do, but this is inconsistent with how we handle stderr which is not ignored and instead printed.
With file redirections, users may want to capture multiple command outputs and redirect them to a log. If we say that this redirects all output to the file:
```nushell
(^cmd1; ^cmd2) o> log.out
```
Then, this should not redirect any output and rather should print everything to the terminal:
```nushell
(^cmd1; ^cmd2)
```
-->