Repository: xonsh/xonsh

Issue:
Capturing subprocess stdout without stderr
Suppose I try to `ls` in a directory without proper permissions in Bash:

```bash
> result=$(ls)
ls: cannot open directory '.': Permission denied
```

The command fails, and stderr is printed directly to the console. However, in xonsh, the same code silently fails:

```
> result=$(ls)
> result
''
```

Is there a way to capture only stdout, so that errors show up immediately? I did see `!()`, but using that means we lose out on colored output in the error messages from a lot of programs.

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Workaround is to add pipe: `result = $(ls | cat)`
I don't clearly understand how it works but in this case you will get the stdout in `result` and colored errors in stderr. I think xonsh captures only the last stdin/stderr and the stderr in the middle of pipe stay uncaptured and shown. May be this is a bugster :)

<details>

![Screenshot_20200921_192923](https://user-images.githubusercontent.com/1708680/93794446-c4ea0900-fc40-11ea-92ff-8bfba6d17db3.png)

</details>

The title of issue is misleading. It could be like "Add colors to captured stderr"
@anki-code piping to cat does solve the problem, but it messes with exit code, so I can't really use it. Is there any other way of doing this?

> The title of issue is misleading. It could be like "Add colors to captured stderr"

xonsh is not removing colors from stderr, the program is.

Most programs, only print ANSI sequences (colors) if they are printing to a terminal (i.e. `isatty`). Since xonsh is capturing stderr and not printing it to the terminal, the program chooses not to use colors. IMO, the solution here is not to trick the program into thinking the captured output is going to a terminal (that will cause other issues); it's to not redirect `stderr` at all (which most shells do), so it goes directly to the terminal.
Ahh yeah, this is probably a bug/oversight.  Though I would argue that this is sort of undefined behaviour.  @anki-code is right that this issue is that it is because when capturing, the last-command-in-the-pipeline is being used and does not go through the normal iteration of line printing. Happy to review PRs / fixes for this.

Resolving patch:
diff --git a/xonsh/procs/specs.py b/xonsh/procs/specs.py
--- a/xonsh/procs/specs.py
+++ b/xonsh/procs/specs.py
@@ -776,6 +776,8 @@ def _update_last_spec(last):
     # set standard error
     if last.stderr is not None:
         pass
+    elif captured == "stdout":
+        pass
     elif captured == "object":
         r, w = os.pipe()
         last.stderr = safe_open(w, "w")


Test patch:
diff --git a/tests/procs/test_specs.py b/tests/procs/test_specs.py
--- a/tests/procs/test_specs.py
+++ b/tests/procs/test_specs.py
@@ -47,6 +47,18 @@ def test_cmds_to_specs_thread_subproc(xession):
     assert specs[0].cls is ProcProxy
 
 
+@pytest.mark.parametrize("thread_subprocs", [True, False])
+def test_cmds_to_specs_capture_stdout_not_stderr(thread_subprocs):
+    env = XSH.env
+    cmds = (["ls", "/root"],)
+
+    env["THREAD_SUBPROCS"] = thread_subprocs
+
+    specs = cmds_to_specs(cmds, captured="stdout")
+    assert specs[0].stdout is not None
+    assert specs[0].stderr is None
+
+
 @skip_if_on_windows
 @pytest.mark.parametrize(
     "thread_subprocs, capture_always", list(itertools.product((True, False), repeat=2))