scopatz: Thanks @laloch - can you pleas add some tests for these. Thanks!
diff --git a/news/execx-evalx-newline.rst b/news/execx-evalx-newline.rst
new file mode 100644
index 00000000..6f74a5bc
--- /dev/null
+++ b/news/execx-evalx-newline.rst
@@ -0,0 +1,24 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* ``execx`` does not require the input string to be newline-terminated.
+* ``evalx`` accepts newline-terminated input string.
+
+**Security:**
+
+* <news item>
diff --git a/xonsh/execer.py b/xonsh/execer.py
index 2d113ee5..b4daef78 100644
--- a/xonsh/execer.py
+++ b/xonsh/execer.py
@@ -145,6 +145,7 @@ class Execer(object):
         if isinstance(input, types.CodeType):
             code = input
         else:
+            input = input.rstrip("\n")
             if filename is None:
                 filename = self.filename
             code = self.compile(
@@ -174,6 +175,8 @@ class Execer(object):
         if isinstance(input, types.CodeType):
             code = input
         else:
+            if not input.endswith("\n"):
+                input += "\n"
             if filename is None:
                 filename = self.filename
             code = self.compile(

execx requires a line separator character at the end of the input
Hello! Thank you for xonsh!

I have a string variable with xonsh code. For example I need to change environment:
```
e = "print(123)"
evalx(e)
123
```
But:
```
e = "$TEST=1"
evalx(e)
```
Result:
```
  File "1.xsh", line 3, in <module>
    evalx(e)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23092, in __call__
    return self.obj.__call__(*args, **kwargs)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23246, in eval
    transform=transform,
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23224, in compile
    tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23184, in parse
    tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23324, in _parse_ctx_free
    raise original_error from None
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/__amalgam__.py", line 23309, in _parse_ctx_free
    debug_level=(self.debug_level > 2),
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/parsers/base.py", line 543, in parse
    tree = self.parser.parse(input=s, lexer=self.lexer, debug=debug_level)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 335, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 1203, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 194, in call_errorfunc
    r = errorfunc(token)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/parsers/base.py", line 3289, in p_error
    self._parse_error("no further code", None)
  File "/home/pc/miniconda3/lib/python3.7/site-packages/xonsh/parsers/base.py", line 676, in _parse_error
    raise err
SyntaxError: None: no further code
```
The same result for environ and execx:
```
evalx('os.environ["test"]=1')
...
SyntaxError: None: no further code
```
```
$ xonfig                                                                                                          
+------------------+-----------------+
| xonsh            | 0.9.14          |
| Python           | 3.7.6           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 3.0.3           |
| shell type       | prompt_toolkit2 |
| pygments         | 2.5.2           |
| on posix         | True            |
| on linux         | True            |
| distro           | unknown         |
| 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 |
+------------------+-----------------+
```
Is there a way to solve this?

-----------

🥈 Workaround - https://github.com/xonsh/xonsh/issues/3481#issuecomment-599864134
diff --git a/tests/test_execer.py b/tests/test_execer.py
index 22e612a6..d74ccf9a 100644
--- a/tests/test_execer.py
+++ b/tests/test_execer.py
@@ -2,7 +2,13 @@
 """Tests the xonsh lexer."""
 import os
 
-from tools import check_eval, check_parse, skip_if_on_unix, skip_if_on_windows
+from tools import (
+    check_eval,
+    check_exec,
+    check_parse,
+    skip_if_on_unix,
+    skip_if_on_windows,
+)
 
 import pytest
 
@@ -138,3 +144,12 @@ def test_echo_line_cont():
 )
 def test_two_echo_line_cont(code):
     assert check_parse(code)
+
+
+def test_eval_eol():
+    assert check_eval("0") and check_eval("0\n")
+
+
+def test_exec_eol():
+    locs = dict()
+    assert check_exec("a=0", locs=locs) and check_exec("a=0\n", locs=locs)
diff --git a/tests/tools.py b/tests/tools.py
index 0b9de84a..767887f9 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -174,8 +174,6 @@ class DummyEnv(MutableMapping):
 
 
 def check_exec(input, **kwargs):
-    if not input.endswith("\n"):
-        input += "\n"
     builtins.__xonsh__.execer.exec(input, **kwargs)
     return True
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.7
requirements/tests.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
black==23.3.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
charset-normalizer==3.4.1
click==8.1.8
codecov==2.1.13
coverage==7.2.7
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.12.2
flake8==5.0.4
identify==2.5.24
idna==3.10
importlib-metadata==4.2.0
iniconfig==2.0.0
mccabe==0.7.0
mypy-extensions==1.0.0
nodeenv==1.9.1
packaging==24.0
pathspec==0.11.2
platformdirs==2.6.2
pluggy==1.2.0
pre-commit==2.21.0
prompt_toolkit==3.0.48
py==1.11.0
pycodestyle==2.9.1
pyflakes==2.5.0
Pygments==2.17.2
pytest==7.4.4
pytest-cov==4.1.0
pytest-flake8==1.1.3
pytest-timeout==2.3.1
PyYAML==6.0.1
requests==2.31.0
tomli==2.0.1
typed-ast==1.5.5
typing_extensions==4.7.1
urllib3==2.0.7
virtualenv==20.16.2
wcwidth==0.2.13
-e git+https://github.com/xonsh/xonsh.git@78b2121130b858307607a5d0195d33e758fa7bb5#egg=xonsh
zipp==3.15.0

name: xonsh
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - black==23.3.0
      - cfgv==3.3.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - coverage==7.2.7
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.12.2
      - flake8==5.0.4
      - identify==2.5.24
      - idna==3.10
      - importlib-metadata==4.2.0
      - iniconfig==2.0.0
      - mccabe==0.7.0
      - mypy-extensions==1.0.0
      - nodeenv==1.9.1
      - packaging==24.0
      - pathspec==0.11.2
      - platformdirs==2.6.2
      - pluggy==1.2.0
      - pre-commit==2.21.0
      - prompt-toolkit==3.0.48
      - py==1.11.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - pygments==2.17.2
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-flake8==1.1.3
      - pytest-timeout==2.3.1
      - pyyaml==6.0.1
      - requests==2.31.0
      - tomli==2.0.1
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - virtualenv==20.16.2
      - wcwidth==0.2.13
      - xonsh==0.9.14
      - zipp==3.15.0
prefix: /opt/conda/envs/xonsh

tests/test_execer.py::test_eval_eol tests/test_execer.py::test_exec_eol
tests/test_execer.py::test_bin_ls tests/test_execer.py::test_ls_dashl tests/test_execer.py::test_which_ls tests/test_execer.py::test_echo_hello tests/test_execer.py::test_echo_star_with_semi tests/test_execer.py::test_simple_func tests/test_execer.py::test_lookup_alias tests/test_execer.py::test_lookup_anon_alias tests/test_execer.py::test_simple_func_broken tests/test_execer.py::test_bad_indent tests/test_execer.py::test_good_rhs_subproc tests/test_execer.py::test_bad_rhs_subproc tests/test_execer.py::test_indent_with_empty_line tests/test_execer.py::test_command_in_func tests/test_execer.py::test_command_in_func_with_comment tests/test_execer.py::test_pyeval_redirect tests/test_execer.py::test_pyeval_multiline_str tests/test_execer.py::test_echo_comma tests/test_execer.py::test_echo_comma_val tests/test_execer.py::test_echo_comma_2val tests/test_execer.py::test_echo_line_cont tests/test_execer.py::test_two_echo_line_cont[echo tests/test_execer.py::test_two_echo_line_cont[if
news/execx-evalx-newline.rst xonsh/execer.py
news/execx-evalx-newline.rst xonsh/execer.py