Repository: xonsh/xonsh

Issue:
Env variables wrong substitution
Hi! What the best shell? Xonsh!

I've found something like a buggy in environment variable substitution:
```python
$QWE=123
$ASD=456

echo $QWE $ASD
# 123 456

echo $zxc
# $zxc

echo $QWE $zxc:$ASD
# 123 456:$ASD
# but should be:
# 123 $zxc:456

echo $QWE $$$$zxc$ASD$ASD $ASD
# 123 $$456$ASD$ASD 456
# but should be:
# 123 $$$$zxc456456 456
```

xonfig

<details>

```
$ xonfig
+------------------+--------------------------------------------+
| xonsh            | 0.9.22.dev169                              |
| Git SHA          | 40c3125d                                   |
| Commit Date      | Oct 1 03:33:46 2020                        |
| Python           | 3.8.2                                      |
| PLY              | 3.11                                       |
```

</details>

Thanks!

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:

Resolving patch:
diff --git a/xonsh/tools.py b/xonsh/tools.py
--- a/xonsh/tools.py
+++ b/xonsh/tools.py
@@ -2190,6 +2190,7 @@ def expandvars(path):
         # get the path's string representation
         path = str(path)
     if "$" in path:
+        shift = 0
         for match in POSIX_ENVVAR_REGEX.finditer(path):
             name = match.group("envvar")
             if name in env:
@@ -2197,7 +2198,10 @@ def expandvars(path):
                 val = env[name]
                 value = str(val) if detyper is None else detyper(val)
                 value = str(val) if value is None else value
-                path = POSIX_ENVVAR_REGEX.sub(value, path, count=1)
+                start_pos, end_pos = match.span()
+                path_len_before_replace = len(path)
+                path = path[: start_pos + shift] + value + path[end_pos + shift :]
+                shift = shift + len(path) - path_len_before_replace
     return path
 
 


Test patch:
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -860,7 +860,7 @@ def test_str_to_env_path(inp, exp):
 def test_path_to_str(inp, exp):
     obs = path_to_str(inp)
     if ON_WINDOWS:
-        exp = exp.replace('/','\\')
+        exp = exp.replace("/", "\\")
     assert exp == obs
 
 
@@ -1510,22 +1510,32 @@ def test_expand_case_matching(inp, exp):
     [
         ("foo", "foo"),
         ("$foo $bar", "bar $bar"),
+        ("$unk $foo $bar", "$unk bar $bar"),
         ("$foobar", "$foobar"),
         ("$foo $spam", "bar eggs"),
+        ("$unk $foo $spam", "$unk bar eggs"),
+        ("$unk $foo $unk $spam $unk", "$unk bar $unk eggs $unk"),
         ("$an_int$spam$a_bool", "42eggsTrue"),
+        ("$unk$an_int$spam$a_bool", "$unk42eggsTrue"),
         ("bar$foo$spam$foo $an_int $none", "barbareggsbar 42 None"),
+        ("$unk bar$foo$spam$foo $an_int $none", "$unk barbareggsbar 42 None"),
         ("$foo/bar", "bar/bar"),
+        ("$unk/$foo/bar", "$unk/bar/bar"),
         ("${'foo'} $spam", "bar eggs"),
+        ("$unk ${'unk'} ${'foo'} $spam", "$unk ${'unk'} bar eggs"),
         ("${'foo'} ${'a_bool'}", "bar True"),
         ("${'foo'}bar", "barbar"),
         ("${'foo'}/bar", "bar/bar"),
+        ("${'unk'}/${'foo'}/bar", "${'unk'}/bar/bar"),
         ("${\"foo'}", "${\"foo'}"),
         ("$?bar", "$?bar"),
         ("$foo}bar", "bar}bar"),
         ("${'foo", "${'foo"),
         (b"foo", "foo"),
         (b"$foo bar", "bar bar"),
+        (b"$unk $foo bar", "$unk bar bar"),
         (b"${'foo'}bar", "barbar"),
+        (b"${'unk'}${'foo'}bar", "${'unk'}barbar"),
     ],
 )
 def test_expandvars(inp, exp, xonsh_builtins):