Repository: xonsh/xonsh

Issue:
xonsh crashes with a TypeError when launched using `xonsh -c '$("")'`
## xonfig

<details>

```
+------------------+-----------------+
| xonsh            | 0.9.27          |
| Python           | 3.9.4           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | None            |
| shell type       | readline        |
| pygments         | None            |
| on posix         | True            |
| on linux         | True            |
| distro           | arch            |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| on msys2         | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
| on jupyter       | False           |
| jupyter kernel   | None            |
| xontrib          | []              |
+------------------+-----------------+
```

</details>

## Expected Behavior
xonsh should output:
```
xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
xonsh: subprocess mode: permission denied: 
''
```
## Current Behavior
xonsh crashes:
```
File "/usr/lib/python3.9/site-packages/xonsh/platform.py", line 231, in pathsplit
    pre = pre.rstrip(seps) or pre
TypeError: rstrip arg must be None or str
```

### Traceback (if applicable)

<details>

```
Traceback (most recent call last):
  File "/usr/bin/xonsh", line 33, in <module>
    sys.exit(load_entry_point('xonsh==0.9.27', 'console_scripts', 'xonsh')())
  File "/usr/lib/python3.9/site-packages/xonsh/main.py", line 423, in main
    _failback_to_other_shells(args, err)
  File "/usr/lib/python3.9/site-packages/xonsh/main.py", line 370, in _failback_to_other_shells
    raise err
  File "/usr/lib/python3.9/site-packages/xonsh/main.py", line 421, in main
    sys.exit(main_xonsh(args))
  File "/usr/lib/python3.9/site-packages/xonsh/main.py", line 464, in main_xonsh
    run_code_with_cache(args.command.lstrip(), shell.execer, mode="single")
  File "/usr/lib/python3.9/site-packages/xonsh/codecache.py", line 209, in run_code_with_cache
    run_compiled_code(ccode, glb, loc, mode)
  File "/usr/lib/python3.9/site-packages/xonsh/codecache.py", line 67, in run_compiled_code
    func(code, glb, loc)
  File "078c96a70d4bc3266c3f26248cc4991e", line 1, in <module>
  File "/usr/lib/python3.9/site-packages/xonsh/built_ins.py", line 169, in subproc_captured_stdout
    return xonsh.procs.specs.run_subproc(cmds, captured="stdout", envs=envs)
  File "/usr/lib/python3.9/site-packages/xonsh/procs/specs.py", line 843, in run_subproc
    specs = cmds_to_specs(cmds, captured=captured, envs=envs)
  File "/usr/lib/python3.9/site-packages/xonsh/procs/specs.py", line 813, in cmds_to_specs
    _update_last_spec(specs[-1])
  File "/usr/lib/python3.9/site-packages/xonsh/procs/specs.py", line 716, in _update_last_spec
    and cmds_cache.predict_threadable(last.args)
  File "/usr/lib/python3.9/site-packages/xonsh/commands_cache.py", line 239, in predict_threadable
    predictor = self.get_predictor_threadable(cmd[0])
  File "/usr/lib/python3.9/site-packages/xonsh/commands_cache.py", line 246, in get_predictor_threadable
    name = self.cached_name(cmd0)
  File "/usr/lib/python3.9/site-packages/xonsh/commands_cache.py", line 139, in cached_name
    cached = pathbasename(name)
  File "/usr/lib/python3.9/site-packages/xonsh/platform.py", line 240, in pathbasename
    return pathsplit(p)[-1]
  File "/usr/lib/python3.9/site-packages/xonsh/platform.py", line 231, in pathsplit
    pre = pre.rstrip(seps) or pre
TypeError: rstrip arg must be None or str
```

</details>

## Steps to Reproduce
From an interactive xonsh or bash session, launch xonsh with:
```
xonsh -c '$("")'
``` 
## Cause of the crash

I tried to identify the type of `seps` by adding:
```
print(type(seps))
```
before the `pre = pre.rstrip(seps) or pre` line in the `pathsplit` function.

Then I launched xonsh with `xonsh -c '$("")'`. I got this for the type of `seps`:
```
<class 'xonsh.lazyasd.LazyObject'>
```

This shows that `seps` is a `LazyObject` that does not get initialized before it is used, which causes a `TypeError` in the next line.

When I instead launched xonsh with `xonsh -c 'echo hello; $("")'`, I got the expected output, and the type of `seps` was `<class 'str'>`:
```
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
hello
<class 'str'>
<class 'str'>
xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
xonsh: subprocess mode: permission denied: 
''
```

Adding the `echo` command before the empty subprocess command when launching xonsh got rid of the `TypeError` because it initialized `seps` to a string.

## Solution

Ensure that `seps` (a `LazyObject`) has been initialized before using it in `pre.rstrip(seps)`. 

(I'm not sure what the best way to do this is)

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:

Resolving patch:
diff --git a/xonsh/platform.py b/xonsh/platform.py
--- a/xonsh/platform.py
+++ b/xonsh/platform.py
@@ -232,6 +232,9 @@ def pathsplit(p):
     without a drive.
     """
     n = len(p)
+    if n == 0:
+        # lazy object seps does not get initialized when n is zero
+        return "", ""
     while n and p[n - 1] not in seps:
         n -= 1
     pre = p[:n]
diff --git a/xonsh/procs/specs.py b/xonsh/procs/specs.py
--- a/xonsh/procs/specs.py
+++ b/xonsh/procs/specs.py
@@ -458,6 +458,8 @@ def run(self, *, pipeline_group=None):
         return p
 
     def _run_binary(self, kwargs):
+        if not self.cmd[0]:
+            raise xt.XonshError("xonsh: subprocess mode: command is empty")
         bufsize = 1
         try:
             p = self.cls(self.cmd, bufsize=bufsize, **kwargs)
@@ -553,6 +555,8 @@ def build(kls, cmd, *, cls=subprocess.Popen, **kwargs):
         modifications and adjustments based on the actual cmd that
         was received.
         """
+        if not cmd:
+            raise xt.XonshError("xonsh: subprocess mode: command is empty")
         # modifications that do not alter cmds may come before creating instance
         spec = kls(cmd, cls=cls, **kwargs)
         # modifications that alter cmds must come after creating instance


Test patch:
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
--- a/tests/test_integrations.py
+++ b/tests/test_integrations.py
@@ -603,6 +603,26 @@ def test_open_quote_syntax_error():
 )
 
 
+@skip_if_no_xonsh
+def test_atdollar_no_output():
+    # see issue 1521
+    script = """
+def _echo(args):
+    print(' '.join(args))
+    aliases['echo'] = _echo
+@$(echo)
+"""
+    out, err, rtn = run_xonsh(script, stderr=sp.PIPE)
+    assert "command is empty" in err
+
+
+@skip_if_no_xonsh
+def test_empty_command():
+    script = "$['']\n"
+    out, err, rtn = run_xonsh(script, stderr=sp.PIPE)
+    assert "command is empty" in err
+
+
 @skip_if_no_xonsh
 @_bad_case
 def test_printfile():
diff --git a/tests/test_platform.py b/tests/test_platform.py
--- a/tests/test_platform.py
+++ b/tests/test_platform.py
@@ -14,3 +14,7 @@ def mocked_open(*args):
     sha, date_ = xp.githash()
     assert date_ is None
     assert sha is None
+
+
+def test_pathsplit_empty_path():
+    assert xp.pathsplit("") == ("", "")