diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index e2dd2d087..b73d70121 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -13,7 +13,7 @@
 from codeop import compile_command
 import re
 import tokenize
-from typing import List, Tuple
+from typing import List, Tuple, Union
 import warnings
 
 _indent_re = re.compile(r'^[ \t]+')
@@ -87,7 +87,7 @@ def cell_magic(lines):
             % (magic_name, first_line, body)]
 
 
-def _find_assign_op(token_line):
+def _find_assign_op(token_line) -> Union[int, None]:
     """Get the index of the first assignment in the line ('=' not inside brackets)
 
     Note: We don't try to support multiple special assignment (a = b = %foo)
@@ -97,9 +97,9 @@ def _find_assign_op(token_line):
         s = ti.string
         if s == '=' and paren_level == 0:
             return i
-        if s in '([{':
+        if s in {'(','[','{'}:
             paren_level += 1
-        elif s in ')]}':
+        elif s in {')', ']', '}'}:
             if paren_level > 0:
                 paren_level -= 1
 
@@ -449,11 +449,14 @@ def transform(self, lines):
 
         return lines_before + [new_line] + lines_after
 
-def make_tokens_by_line(lines):
+def make_tokens_by_line(lines:List[str]):
     """Tokenize a series of lines and group tokens by line.
 
-    The tokens for a multiline Python string or expression are
-    grouped as one line.
+    The tokens for a multiline Python string or expression are grouped as one
+    line. All lines except the last lines should keep their line ending ('\\n',
+    '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
+    for example when passing block of text to this function.
+
     """
     # NL tokens are used inside multiline expressions, but also after blank
     # lines or comments. This is intentional - see https://bugs.python.org/issue17061
@@ -461,6 +464,8 @@ def make_tokens_by_line(lines):
     # track parentheses level, similar to the internals of tokenize.
     NEWLINE, NL = tokenize.NEWLINE, tokenize.NL
     tokens_by_line = [[]]
+    if len(lines) > 1 and not lines[0].endswith(('\n', '\r', '\r\n', '\x0b', '\x0c')):
+        warnings.warn("`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified")
     parenlev = 0
     try:
         for token in tokenize.generate_tokens(iter(lines).__next__):

Capturing shell command output after indentation causes SyntaxError in IPython console.
To reproduce, type this in console:

```python
def test():
  for i in range(1):
    print(i)
  res =! ls
```

But if something is put above the line, everything will seem to be fine.
```python
def test():
  for i in range(1):
    print(i)
  'just a separate line'
  res =! ls
```
Find this in IPython 7.1.0.dev
diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py
index d6c2fa3bd..9c92c394e 100644
--- a/IPython/core/tests/test_inputtransformer2.py
+++ b/IPython/core/tests/test_inputtransformer2.py
@@ -8,7 +8,7 @@
 import string
 
 from IPython.core import inputtransformer2 as ipt2
-from IPython.core.inputtransformer2 import make_tokens_by_line
+from IPython.core.inputtransformer2 import make_tokens_by_line, _find_assign_op
 
 from textwrap import dedent
 
@@ -53,6 +53,22 @@
 g()
 """.splitlines(keepends=True))
 
+#####
+
+MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
+def test():
+  for i in range(1):
+    print(i)
+  res =! ls
+""".splitlines(keepends=True), (4, 7), '''\
+def test():
+  for i in range(1):
+    print(i)
+  res =get_ipython().getoutput(\' ls\')
+'''.splitlines(keepends=True))
+
+######
+
 AUTOCALL_QUOTE = (
     [",f 1 2 3\n"], (1, 0),
     ['f("1", "2", "3")\n']
@@ -103,6 +119,7 @@
 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
 )
 
+
 def null_cleanup_transformer(lines):
     """
     A cleanup transform that returns an empty list.
@@ -144,18 +161,21 @@ def test_continued_line():
 def test_find_assign_magic():
     check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
     check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
+    check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
 
 def test_transform_assign_magic():
     check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
 
 def test_find_assign_system():
     check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
+    check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
     check_find(ipt2.SystemAssign, (["a =  !ls\n"], (1, 5), None))
     check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
     check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
 
 def test_transform_assign_system():
     check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
+    check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
 
 def test_find_magic_escape():
     check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
@@ -203,6 +223,17 @@ def test_transform_help():
     tf = ipt2.HelpEnd((1, 0), (2, 8))
     nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
 
+def test_find_assign_op_dedent():
+    """
+    be carefull that empty token like dedent are not counted as parens
+    """
+    class Tk:
+        def __init__(self, s):
+            self.string = s
+
+    nt.assert_equal(_find_assign_op([Tk(s) for s in ('','a','=','b')]), 2)
+    nt.assert_equal(_find_assign_op([Tk(s) for s in ('','(', 'a','=','b', ')', '=' ,'5')]), 6)
+
 def test_check_complete():
     cc = ipt2.TransformerManager().check_complete
     nt.assert_equal(cc("a = 1"), ('complete', None))

pip install -e .[test]
parse_log_pytest
requirements.txt
nose requests testpath pygments nbformat ipykernel numpy pytest
apt-get update apt-get install -y gcc
3.5
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
backcall==0.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
decorator==5.1.1
entrypoints==0.4
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
ipykernel==5.5.6
-e git+https://github.com/ipython/ipython.git@4b3baed67bceb4a10f6189d87580d7fcfe5ef079#egg=ipython
ipython-genutils==0.2.0
jedi==0.17.2
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
nbformat==5.1.3
nest-asyncio==1.6.0
nose==1.3.7
numpy==1.19.5
packaging==21.3
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
pluggy==1.0.0
prompt-toolkit==2.0.10
ptyprocess==0.7.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
python-dateutil==2.9.0.post0
pyzmq==25.1.2
requests==2.27.1
six==1.17.0
testpath==0.6.0
tomli==1.2.3
tornado==6.1
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
wcwidth==0.2.13
zipp==3.6.0

name: ipython
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=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - backcall==0.2.0
      - charset-normalizer==2.0.12
      - decorator==5.1.1
      - entrypoints==0.4
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - ipykernel==5.5.6
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - nbformat==5.1.3
      - nest-asyncio==1.6.0
      - nose==1.3.7
      - numpy==1.19.5
      - packaging==21.3
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pluggy==1.0.0
      - prompt-toolkit==2.0.10
      - ptyprocess==0.7.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pyzmq==25.1.2
      - requests==2.27.1
      - six==1.17.0
      - testpath==0.6.0
      - tomli==1.2.3
      - tornado==6.1
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
      - zipp==3.6.0
prefix: /opt/conda/envs/ipython

IPython/core/tests/test_inputtransformer2.py::test_find_assign_system IPython/core/tests/test_inputtransformer2.py::test_find_assign_op_dedent
IPython/core/tests/test_inputtransformer2.py::test_continued_line IPython/core/tests/test_inputtransformer2.py::test_find_assign_magic IPython/core/tests/test_inputtransformer2.py::test_transform_assign_magic IPython/core/tests/test_inputtransformer2.py::test_transform_assign_system IPython/core/tests/test_inputtransformer2.py::test_find_magic_escape IPython/core/tests/test_inputtransformer2.py::test_transform_magic_escape IPython/core/tests/test_inputtransformer2.py::test_find_autocalls IPython/core/tests/test_inputtransformer2.py::test_transform_autocall IPython/core/tests/test_inputtransformer2.py::test_find_help IPython/core/tests/test_inputtransformer2.py::test_transform_help IPython/core/tests/test_inputtransformer2.py::test_check_complete IPython/core/tests/test_inputtransformer2.py::test_check_complete_II IPython/core/tests/test_inputtransformer2.py::test_null_cleanup_transformer
IPython/core/inputtransformer2.py
IPython/core/inputtransformer2.py