Repository: xonsh/xonsh

Issue:
Non exisitng command wrapped in parentheses crashes xonsh
If I try to execute a non-existing command xonsh reports the error nicely:

```
snail@sea ~
$ not_a_command
xonsh: subprocess mode: command not found: not_a_command
```

If I do the same with parentheses xonsh comes with a weird traceback.
```
snail@sea ~\Documents\Projects\LLJ
$ (not_a_command)
Traceback (most recent call last):
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\built_ins.py", line 512, in _run_binary
    p = self.cls(self.cmd, bufsize=bufsize, **kwargs)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\proc.py", line 559, in __init__
    **kwargs)
  File "C:\Users\mel\Anaconda3\lib\subprocess.py", line 707, in __init__
    restore_signals, start_new_session)
  File "C:\Users\mel\Anaconda3\lib\subprocess.py", line 990, in _execute_child
    startupinfo)
FileNotFoundError: [WinError 2] The system cannot find the file specified

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\mel\Anaconda3\Scripts\xonsh-script.py", line 5, in <module>
    sys.exit(xonsh.main.main())
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\main.py", line 341, in main
    _failback_to_other_shells(args, err)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\main.py", line 305, in _failback_to_other_shells
    raise err
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\main.py", line 339, in main
    return main_xonsh(args)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\main.py", line 371, in main_xonsh
    run_code_with_cache(args.command.lstrip(), shell.execer, mode='single')
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\codecache.py", line 209, in run_code_with_cache
    run_compiled_code(ccode, glb, loc, mode)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\codecache.py", line 68, in run_compiled_code
    func(code, glb, loc)
  File "8104809f2f92cd6602961aead5bbcdfe", line 1, in <module>
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\built_ins.py", line 877, in subproc_captured_hiddenobject
    return run_subproc(cmds, captured='hiddenobject')
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\built_ins.py", line 813, in run_subproc
    command = HiddenCommandPipeline(specs)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\proc.py", line 1767, in __init__
    proc = spec.run(pipeline_group=pipeline_group)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\built_ins.py", line 502, in run
    p = self._run_binary(kwargs)
  File "C:\Users\mel\Anaconda3\lib\site-packages\xonsh\built_ins.py", line 523, in _run_binary
    raise XonshError(e)
xonsh.tools.XonshError: xonsh: subprocess mode: command not found: not_a_command
```


```
$ xonfig
+------------------+-------------------------------------------------------+
| xonsh            | 0.5.12.dev64                                          |
| Git SHA          | ab477051                                              |
| Commit Date      | Jul 22 23:08:26 2017                                  |
| Python           | 3.6.1                                                 |
| PLY              | 3.9                                                   |
| have readline    | False                                                 |
| prompt toolkit   | 1.0.14                                                |
| shell type       | prompt_toolkit                                        |
| pygments         | 2.2.0                                                 |
| on posix         | False                                                 |
| on linux         | False                                                 |
| on darwin        | False                                                 |
| on windows       | True                                                  |
| on cygwin        | <xonsh.lazyasd.LazyBool object at 0x0000027E4BEC4EB8> |
| is superuser     | False                                                 |
| default encoding | utf-8                                                 |
| xonsh encoding   | utf-8                                                 |
| encoding errors  | surrogateescape                                       |
+------------------+-------------------------------------------------------+
```


Discussion:
Hey @melund -- is this still an issue on windows with 0.6.2?
I completely forgot about this one. But yes. This is still a problem. 
Maybe the real question here is why `$XONSH_SHOW_TRACEBACK` has no effect on the case with parentheses... 
It might be because `FileNotFoundError` is not a XonshError or SyntaxError or something silly like that.
This is weird.  It is also happening with running files that don't have execute permission
The inverse problem is also True.  If you are showing the traceback and don't use parens, the traceback will *not* be displayed:

```console
scopatz@artemis ~ $ $XONSH_SHOW_TRACEBACK = True
scopatz@artemis ~ $ not_command
xonsh: subprocess mode: command not found: not_command
not_command: command not found

```

Resolving patch:
diff --git a/xonsh/jobs.py b/xonsh/jobs.py
--- a/xonsh/jobs.py
+++ b/xonsh/jobs.py
@@ -221,7 +221,7 @@ def _clear_dead_jobs():
     to_remove = set()
     for tid in tasks:
         obj = get_task(tid)['obj']
-        if obj.poll() is not None:
+        if obj is None or obj.poll() is not None:
             to_remove.add(tid)
     for job in to_remove:
         tasks.remove(job)
diff --git a/xonsh/proc.py b/xonsh/proc.py
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -1765,9 +1765,11 @@ def __init__(self, specs):
                 self.starttime = time.time()
             try:
                 proc = spec.run(pipeline_group=pipeline_group)
-            except XonshError:
+            except Exception:
+                print_exception()
                 self._return_terminal()
-                raise
+                self.proc = None
+                return
             if proc.pid and pipeline_group is None and not spec.is_proxy and \
                     self.captured != 'object':
                 pipeline_group = proc.pid
@@ -1804,6 +1806,8 @@ def iterraw(self):
         # get appropriate handles
         spec = self.spec
         proc = self.proc
+        if proc is None:
+            return
         timeout = builtins.__xonsh_env__.get('XONSH_PROC_FREQUENCY')
         # get the correct stdout
         stdout = proc.stdout
@@ -2067,22 +2071,26 @@ def _prev_procs_done(self):
             if p.poll() is None:
                 any_running = True
                 continue
-            self._safe_close(p.stdin)
             self._safe_close(s.stdin)
-            self._safe_close(p.stdout)
             self._safe_close(s.stdout)
-            self._safe_close(p.stderr)
             self._safe_close(s.stderr)
+            if p is None:
+                continue
+            self._safe_close(p.stdin)
+            self._safe_close(p.stdout)
+            self._safe_close(p.stderr)
         return False if any_running else (len(self) > 1)
 
     def _close_prev_procs(self):
         """Closes all but the last proc's stdout."""
         for s, p in zip(self.specs[:-1], self.procs[:-1]):
             self._safe_close(s.stdin)
-            self._safe_close(p.stdin)
             self._safe_close(s.stdout)
-            self._safe_close(p.stdout)
             self._safe_close(s.stderr)
+            if p is None:
+                continue
+            self._safe_close(p.stdin)
+            self._safe_close(p.stdout)
             self._safe_close(p.stderr)
 
     def _close_proc(self):
@@ -2090,16 +2098,20 @@ def _close_proc(self):
         s = self.spec
         p = self.proc
         self._safe_close(s.stdin)
-        self._safe_close(p.stdin)
         self._safe_close(s.stdout)
-        self._safe_close(p.stdout)
         self._safe_close(s.stderr)
-        self._safe_close(p.stderr)
         self._safe_close(s.captured_stdout)
         self._safe_close(s.captured_stderr)
+        if p is None:
+            return
+        self._safe_close(p.stdin)
+        self._safe_close(p.stdout)
+        self._safe_close(p.stderr)
 
     def _set_input(self):
         """Sets the input variable."""
+        if self.proc is None:
+            return
         stdin = self.proc.stdin
         if stdin is None or isinstance(stdin, int) or stdin.closed or \
            not stdin.seekable() or not safe_readable(stdin):
@@ -2127,7 +2139,7 @@ def _apply_to_history(self):
         """Applies the results to the current history object."""
         hist = builtins.__xonsh_history__
         if hist is not None:
-            hist.last_cmd_rtn = self.proc.returncode
+            hist.last_cmd_rtn = 1 if self.proc is None else self.proc.returncode
 
     def _raise_subproc_error(self):
         """Raises a subprocess error, if we are supposed to."""
@@ -2191,6 +2203,8 @@ def pid(self):
     def returncode(self):
         """Process return code, waits until command is completed."""
         self.end()
+        if self.proc is None:
+            return 1
         return self.proc.returncode
 
     rtn = returncode


Test patch: