diff --git a/CHANGELOG.md b/CHANGELOG.md
index c67fee2c..561ddcee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
 * Enhancements
     * `pyscript` limits a command's stdout capture to the same period that redirection does.
       Therefore output from a command's postparsing and finalization hooks isn't saved in the StdSim object.
+    * `StdSim.buffer.write()` now flushes when the wrapped stream uses line buffering and the bytes being written
+      contain a newline or carriage return. This helps when `pyscript` is echoing the output of a shell command
+      since the output will print at the same frequency as when the command is run in a terminal.
     
 ## 0.9.12 (April 22, 2019)
 * Bug Fixes
diff --git a/cmd2/utils.py b/cmd2/utils.py
index 35c953e0..44a58c35 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -8,7 +8,7 @@ import subprocess
 import sys
 import threading
 import unicodedata
-from typing import Any, BinaryIO, Iterable, List, Optional, TextIO, Union
+from typing import Any, Iterable, List, Optional, TextIO, Union
 
 from wcwidth import wcswidth
 
@@ -297,7 +297,7 @@ class StdSim(object):
                  encoding: str = 'utf-8', errors: str = 'replace') -> None:
         """
         Initializer
-        :param inner_stream: the emulated stream
+        :param inner_stream: the wrapped stream. Should be a TextIO or StdSim instance.
         :param echo: if True, then all input will be echoed to inner_stream
         :param encoding: codec for encoding/decoding strings (defaults to utf-8)
         :param errors: how to handle encoding/decoding errors (defaults to replace)
@@ -350,6 +350,17 @@ class StdSim(object):
         else:
             return False
 
+    @property
+    def line_buffering(self) -> bool:
+        """
+        Handle when the inner stream doesn't have a line_buffering attribute which is the case
+        when running unit tests because pytest sets stdout to a pytest EncodedFile object.
+        """
+        try:
+            return self.inner_stream.line_buffering
+        except AttributeError:
+            return False
+
     def __getattr__(self, item: str):
         if item in self.__dict__:
             return self.__dict__[item]
@@ -361,6 +372,9 @@ class ByteBuf(object):
     """
     Used by StdSim to write binary data and stores the actual bytes written
     """
+    # Used to know when to flush the StdSim
+    NEWLINES = [b'\n', b'\r']
+
     def __init__(self, std_sim_instance: StdSim) -> None:
         self.byte_buf = bytearray()
         self.std_sim_instance = std_sim_instance
@@ -374,14 +388,22 @@ class ByteBuf(object):
         if self.std_sim_instance.echo:
             self.std_sim_instance.inner_stream.buffer.write(b)
 
+            # Since StdSim wraps TextIO streams, we will flush the stream if line buffering is on
+            # and the bytes being written contain a new line character. This is helpful when StdSim
+            # is being used to capture output of a shell command because it causes the output to print
+            # to the screen more often than if we waited for the stream to flush its buffer.
+            if self.std_sim_instance.line_buffering:
+                if any(newline in b for newline in ByteBuf.NEWLINES):
+                    self.std_sim_instance.flush()
+
 
 class ProcReader(object):
     """
     Used to captured stdout and stderr from a Popen process if any of those were set to subprocess.PIPE.
     If neither are pipes, then the process will run normally and no output will be captured.
     """
-    def __init__(self, proc: subprocess.Popen, stdout: Union[StdSim, BinaryIO, TextIO],
-                 stderr: Union[StdSim, BinaryIO, TextIO]) -> None:
+    def __init__(self, proc: subprocess.Popen, stdout: Union[StdSim, TextIO],
+                 stderr: Union[StdSim, TextIO]) -> None:
         """
         ProcReader initializer
         :param proc: the Popen process being read from
@@ -457,17 +479,14 @@ class ProcReader(object):
                 self._write_bytes(write_stream, available)
 
     @staticmethod
-    def _write_bytes(stream: Union[StdSim, BinaryIO, TextIO], to_write: bytes) -> None:
+    def _write_bytes(stream: Union[StdSim, TextIO], to_write: bytes) -> None:
         """
         Write bytes to a stream
         :param stream: the stream being written to
         :param to_write: the bytes being written
         """
         try:
-            if hasattr(stream, 'buffer'):
-                stream.buffer.write(to_write)
-            else:
-                stream.write(to_write)
+            stream.buffer.write(to_write)
         except BrokenPipeError:
             # This occurs if output is being piped to a process that closed
             pass
@@ -500,7 +519,7 @@ class ContextFlag(object):
 class RedirectionSavedState(object):
     """Created by each command to store information about their redirection."""
 
-    def __init__(self, self_stdout: Union[StdSim, BinaryIO, TextIO], sys_stdout: Union[StdSim, BinaryIO, TextIO],
+    def __init__(self, self_stdout: Union[StdSim, TextIO], sys_stdout: Union[StdSim, TextIO],
                  pipe_proc_reader: Optional[ProcReader]) -> None:
         # Used to restore values after the command ends
         self.saved_self_stdout = self_stdout

Investigate potential StdSim and/or ByteBuf improvements
The `StdSim` and `ByteBuf` classes were initially created as a convenient hack for use in unit and transcript testing with little to no thought given to factors like performance.  However, ``StdSim`` is now used by *pyscript_bridge.py* and hence by the **py**, **pyscript**, and **ipy** commands.

Issues that should be investigated include the following:
1. `ByteBuf` uses an immutable `bytes` object as a buffer to store text in the inner buffer.  If a lot of data gets sent here, constantly recreating new versions might be inefficient.  We should consider replacing it with some mutable type like a `bytearray`.
1. `StdSim` should probably behave like it's inner_stream. 
    * If the `inner_stream` is a `_io.TextIOWrapper` like `sys.stdout` typically is, then `StdSim` should provide the `buffer` interface.
    * If the `inner_stream` is a _`io.BufferedWriter` like a file opened in binary mode, then no `buffer` interface would be provided.
    *  In both cases, the `write` method should behave like the inner stream.
        * In the text case, `write(`) should accept strings, and in the binary case, bytes.

These changes will support wrapping `stdout` no matter what type of stream it is.

diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4574e579..c0b16990 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -126,23 +126,12 @@ def stdout_sim():
     stdsim = cu.StdSim(sys.stdout, echo=True)
     return stdsim
 
-@pytest.fixture
-def stringio_sim():
-    import io
-    stdsim = cu.StdSim(io.StringIO(), echo=True)
-    return stdsim
-
 
 def test_stdsim_write_str(stdout_sim):
     my_str = 'Hello World'
     stdout_sim.write(my_str)
     assert stdout_sim.getvalue() == my_str
 
-def test_stdsim_write_str_inner_no_buffer(stringio_sim):
-    my_str = 'Hello World'
-    stringio_sim.write(my_str)
-    assert stringio_sim.getvalue() == my_str
-
 def test_stdsim_write_bytes(stdout_sim):
     b_str = b'Hello World'
     with pytest.raises(TypeError):
@@ -218,6 +207,26 @@ def test_stdsim_pause_storage(stdout_sim):
     stdout_sim.buffer.write(b_str)
     assert stdout_sim.getbytes() == b''
 
+def test_stdsim_line_buffering(base_app):
+    # This exercises the case of writing binary data that contains new lines/carriage returns to a StdSim
+    # when line buffering is on. The output should immediately be flushed to the underlying stream.
+    import os
+    import tempfile
+    file = tempfile.NamedTemporaryFile(mode='wt')
+    file.line_buffering = True
+
+    stdsim = cu.StdSim(file, echo=True)
+    saved_size = os.path.getsize(file.name)
+
+    bytes_to_write = b'hello\n'
+    stdsim.buffer.write(bytes_to_write)
+    assert os.path.getsize(file.name) == saved_size + len(bytes_to_write)
+    saved_size = os.path.getsize(file.name)
+
+    bytes_to_write = b'hello\r'
+    stdsim.buffer.write(bytes_to_write)
+    assert os.path.getsize(file.name) == saved_size + len(bytes_to_write)
+
 
 @pytest.fixture
 def pr_none():

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
anyio==4.9.0
astroid==3.3.9
attrs==25.3.0
babel==2.17.0
backports.tarfile==1.2.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
-e git+https://github.com/python-cmd2/cmd2.git@7a20c2e312513b8a84a6cfb56263cac9c67df04d#egg=cmd2
codecov==2.1.13
colorama==0.4.6
coverage==7.8.0
cryptography==44.0.2
dill==0.3.9
distlib==0.3.9
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
h11==0.14.0
id==1.5.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
invoke==2.2.0
isort==6.0.1
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.6.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mccabe==0.7.0
mdurl==0.1.2
more-itertools==10.6.0
nh3==0.2.21
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pycparser==2.22
Pygments==2.19.1
pylint==3.3.6
pyperclip==1.9.0
pyproject-api==1.9.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
readme_renderer==44.0
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.0.0
SecretStorage==3.3.3
sniffio==1.3.1
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-autobuild==2024.10.3
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
starlette==0.46.1
tomli==2.2.1
tomlkit==0.13.2
tox==4.25.0
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.3.0
uvicorn==0.34.0
virtualenv==20.29.3
watchfiles==1.0.4
wcwidth==0.2.13
websockets==15.0.1
zipp==3.21.0

name: cmd2
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
  - 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=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - anyio==4.9.0
      - astroid==3.3.9
      - attrs==25.3.0
      - babel==2.17.0
      - backports-tarfile==1.2.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - colorama==0.4.6
      - coverage==7.8.0
      - cryptography==44.0.2
      - dill==0.3.9
      - distlib==0.3.9
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - h11==0.14.0
      - id==1.5.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - invoke==2.2.0
      - isort==6.0.1
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==25.6.0
      - markdown-it-py==3.0.0
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mdurl==0.1.2
      - more-itertools==10.6.0
      - nh3==0.2.21
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycparser==2.22
      - pygments==2.19.1
      - pylint==3.3.6
      - pyperclip==1.9.0
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - readme-renderer==44.0
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - secretstorage==3.3.3
      - sniffio==1.3.1
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-autobuild==2024.10.3
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - starlette==0.46.1
      - tomli==2.2.1
      - tomlkit==0.13.2
      - tox==4.25.0
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - uvicorn==0.34.0
      - virtualenv==20.29.3
      - watchfiles==1.0.4
      - wcwidth==0.2.13
      - websockets==15.0.1
      - zipp==3.21.0
prefix: /opt/conda/envs/cmd2

tests/test_utils.py::test_stdsim_line_buffering
tests/test_utils.py::test_strip_ansi tests/test_utils.py::test_ansi_safe_wcswidth tests/test_utils.py::test_strip_quotes_no_quotes tests/test_utils.py::test_strip_quotes_with_quotes tests/test_utils.py::test_remove_duplicates_no_duplicates tests/test_utils.py::test_remove_duplicates_with_duplicates tests/test_utils.py::test_unicode_normalization tests/test_utils.py::test_unicode_casefold tests/test_utils.py::test_alphabetical_sort tests/test_utils.py::test_try_int_or_force_to_lower_case tests/test_utils.py::test_natural_keys tests/test_utils.py::test_natural_sort tests/test_utils.py::test_is_quoted_short tests/test_utils.py::test_is_quoted_yes tests/test_utils.py::test_is_quoted_no tests/test_utils.py::test_quote_string_if_needed_yes tests/test_utils.py::test_quot_string_if_needed_no tests/test_utils.py::test_stdsim_write_str tests/test_utils.py::test_stdsim_write_bytes tests/test_utils.py::test_stdsim_buffer_write_bytes tests/test_utils.py::test_stdsim_buffer_write_str tests/test_utils.py::test_stdsim_read tests/test_utils.py::test_stdsim_read_bytes tests/test_utils.py::test_stdsim_clear tests/test_utils.py::test_stdsim_getattr_exist tests/test_utils.py::test_stdsim_getattr_noexist tests/test_utils.py::test_stdsim_pause_storage tests/test_utils.py::test_context_flag_bool tests/test_utils.py::test_context_flag_exit_err
cmd2/utils.py CHANGELOG.md
cmd2/utils.py CHANGELOG.md