Repository: xonsh/xonsh

Issue:
$ARG1 doesn't work in subprocess mode
It seems like $ARGS works in both subprocess and python mode, but naming a specific argument such as $ARG1 does not. For example:

```
$ cat a.xsh
#!/usr/bin/env xonsh

echo $ARGS
echo $ARG1

print($ARGS)
print($ARG1)

$ ./a.xsh 1 2 3 4
['./a.xsh', '1', '2', '3', '4']
$ARG1
['./a.xsh', '1', '2', '3', '4']
1
```

xonfig output:
```
$ xonfig
+------------------+----------------------+
| xonsh            | 0.6.8                |
| Git SHA          | a88a485f             |
| Commit Date      | Jun 28 20:10:30 2018 |
| Python           | 3.5.2                |
| PLY              | 3.9                  |
| have readline    | True                 |
| prompt toolkit   | 1.0.15               |
| shell type       | prompt_toolkit       |
| pygments         | None                 |
| on posix         | True                 |
| on linux         | True                 |
| distro           | neon                 |
| 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      |
+------------------+----------------------+
```


Discussion:
Happens as well upon upgrading to 0.7.0:

```
+------------------+----------------------+
| xonsh            | 0.7.0                |
| Git SHA          | 3779e8b5             |
| Commit Date      | Jul 14 14:40:04 2018 |
| Python           | 3.5.2                |
| PLY              | 3.9                  |
| have readline    | True                 |
| prompt toolkit   | 1.0.15               |
| shell type       | prompt_toolkit1      |
| pygments         | None                 |
| on posix         | True                 |
| on linux         | True                 |
| distro           | neon                 |
| 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      |
+------------------+----------------------+
```
Thanks for reporting @battaglia01! This is because `$ARG<N>` were implemeted as psuedo-environment variables that don't get passed down to subprocesses.  This is clearly wrong.  I am working on a fix now.

Resolving patch:
diff --git a/xonsh/aliases.py b/xonsh/aliases.py
--- a/xonsh/aliases.py
+++ b/xonsh/aliases.py
@@ -10,7 +10,7 @@
 
 from xonsh.lazyasd import lazyobject
 from xonsh.dirstack import cd, pushd, popd, dirs, _get_cwd
-from xonsh.environ import locate_binary
+from xonsh.environ import locate_binary, make_args_env
 from xonsh.foreign_shells import foreign_shell_data
 from xonsh.jobs import jobs, fg, bg, clean_jobs
 from xonsh.platform import (ON_ANACONDA, ON_DARWIN, ON_WINDOWS, ON_FREEBSD,
@@ -303,7 +303,7 @@ def source_alias(args, stdin=None):
             src += '\n'
         ctx = builtins.__xonsh_ctx__
         updates = {'__file__': fpath, '__name__': os.path.abspath(fpath)}
-        with env.swap(ARGS=args[i + 1:]), swap_values(ctx, updates):
+        with env.swap(**make_args_env(args[i + 1:])), swap_values(ctx, updates):
             try:
                 builtins.execx(src, 'exec', ctx, filename=fpath)
             except Exception:
diff --git a/xonsh/environ.py b/xonsh/environ.py
--- a/xonsh/environ.py
+++ b/xonsh/environ.py
@@ -813,12 +813,6 @@ def __init__(self, *args, **kwargs):
             self._d['PATH'] = list(PATH_DEFAULT)
         self._detyped = None
 
-    @property
-    def arg_regex(self):
-        if self._arg_regex is None:
-            self._arg_regex = re.compile(r'ARG(\d+)')
-        return self._arg_regex
-
     @staticmethod
     def detypeable(val):
         return not (callable(val) or isinstance(val, cabc.MutableMapping))
@@ -937,14 +931,6 @@ def __getitem__(self, key):
             return self['PROMPT_FIELDS']
         if key is Ellipsis:
             return self
-        m = self.arg_regex.match(key)
-        if (m is not None) and (key not in self._d) and ('ARGS' in self._d):
-            args = self._d['ARGS']
-            ix = int(m.group(1))
-            if ix >= len(args):
-                e = "Not enough arguments given to access ARG{0}."
-                raise KeyError(e.format(ix))
-            val = self._d['ARGS'][ix]
         elif key in self._d:
             val = self._d[key]
         elif key in self._defaults:
@@ -1128,3 +1114,14 @@ def default_env(env=None):
     if env is not None:
         ctx.update(env)
     return ctx
+
+
+def make_args_env(args=None):
+    """Makes a dictionary containing the $ARGS and $ARG<N> environment
+    variables. If the supplied ARGS is None, then sys.argv is used.
+    """
+    if args is None:
+        args = sys.argv
+    env = {'ARG' + str(i): arg for i, arg in enumerate(args)}
+    env['ARGS'] = list(args)  # make a copy so we don't interfere with original variable
+    return env
diff --git a/xonsh/main.py b/xonsh/main.py
--- a/xonsh/main.py
+++ b/xonsh/main.py
@@ -24,7 +24,7 @@
 from xonsh.lazyimps import pygments, pyghooks
 from xonsh.imphooks import install_import_hooks
 from xonsh.events import events
-from xonsh.environ import xonshrc_context
+from xonsh.environ import xonshrc_context, make_args_env
 
 
 events.transmogrify('on_post_init', 'LoadEvent')
@@ -376,7 +376,7 @@ def func_sig_ttin_ttou(n, f):
             path = os.path.abspath(os.path.expanduser(args.file))
             if os.path.isfile(path):
                 sys.argv = [args.file] + args.args
-                env['ARGS'] = sys.argv[:]  # $ARGS is not sys.argv
+                env.update(make_args_env())  # $ARGS is not sys.argv
                 env['XONSH_SOURCE'] = path
                 shell.ctx.update({'__file__': args.file, '__name__': '__main__'})
                 run_script_with_cache(args.file, shell.execer, glb=shell.ctx,


Test patch:
diff --git a/tests/test_environ.py b/tests/test_environ.py
--- a/tests/test_environ.py
+++ b/tests/test_environ.py
@@ -11,7 +11,8 @@
 import pytest
 
 from xonsh.commands_cache import CommandsCache
-from xonsh.environ import Env, locate_binary, DEFAULT_ENSURERS, DEFAULT_VALUES, default_env
+from xonsh.environ import (Env, locate_binary, DEFAULT_ENSURERS, DEFAULT_VALUES,
+                           default_env, make_args_env)
 
 from tools import skip_if_on_unix
 
@@ -230,3 +231,15 @@ def test_no_lines_columns():
     finally:
         del os.environ['LINES']
         del os.environ['COLUMNS']
+
+
+def test_make_args_env():
+    obs = make_args_env(['script', '1', '2', '3'])
+    exp = {
+        'ARGS': ['script', '1', '2', '3'],
+        'ARG0': 'script',
+        'ARG1': '1',
+        'ARG2': '2',
+        'ARG3': '3',
+    }
+    assert exp == obs