Skip to content

Commit e267731

Browse files
committed
gh-47798: Docs: lead with run_pipeline for shell pipes; whatsnew entry
The "Replacing shell pipeline" recipe now recommends run_pipeline() first and demotes the manual Popen chain to the streaming case. Note that PipelineError is a sibling of CalledProcessError, not a subclass.
1 parent 33fec2c commit e267731

2 files changed

Lines changed: 44 additions & 15 deletions

File tree

Doc/library/subprocess.rst

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,16 @@ underlying :class:`Popen` interface can be used directly.
448448
that returned a non-zero exit status. This is similar to the shell's
449449
``pipefail`` behavior.
450450

451+
:exc:`PipelineError` is a *sibling* of :exc:`CalledProcessError`, not a
452+
subclass: a pipeline carries N commands and N return codes, so there is
453+
no single ``returncode`` or ``cmd`` value an existing
454+
``except CalledProcessError:`` handler could be shown without being
455+
misleading. To handle both single-command and pipeline failures with
456+
one ``except`` clause, catch :exc:`SubprocessError` (which is also the
457+
common base of :exc:`TimeoutExpired`). Retry helpers and decorators
458+
that match on :exc:`CalledProcessError` will not catch pipeline
459+
failures by default.
460+
451461
.. attribute:: commands
452462

453463
List of commands that were used in the pipeline.
@@ -1606,24 +1616,32 @@ Replacing shell pipeline
16061616
16071617
becomes::
16081618

1609-
p1 = Popen(["dmesg"], stdout=PIPE)
1610-
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
1611-
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
1612-
output = p2.communicate()[0]
1619+
result = run_pipeline(["dmesg"], ["grep", "hda"],
1620+
capture_output=True, check=True)
1621+
output = result.stdout
16131622

1614-
The ``p1.stdout.close()`` call after starting the p2 is important in order for
1615-
p1 to receive a SIGPIPE if p2 exits before p1.
1623+
:func:`run_pipeline` connects the stages, closes the parent's copies of the
1624+
intermediate pipe ends, waits for every process, and (with ``check=True``)
1625+
raises :exc:`PipelineError` if *any* stage fails -- equivalent to the
1626+
shell's ``set -o pipefail`` without needing a shell.
16161627

1617-
Alternatively, for trusted input, the shell's own pipeline support may still
1618-
be used directly:
1628+
If you need to read the final stage's output incrementally rather than
1629+
waiting for the whole pipeline to finish (for example, streaming a large
1630+
decompressed file), chain :class:`Popen` instances directly::
16191631

1620-
.. code-block:: bash
1621-
1622-
output=$(dmesg | grep hda)
1623-
1624-
becomes::
1625-
1626-
output = check_output("dmesg | grep hda", shell=True)
1632+
p1 = Popen(["dmesg"], stdout=PIPE)
1633+
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
1634+
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
1635+
for line in p2.stdout:
1636+
...
1637+
p2.wait()
1638+
p1.wait()
1639+
if p1.returncode or p2.returncode:
1640+
... # handle failure in either stage
1641+
1642+
The ``p1.stdout.close()`` call after starting p2 is important in order for
1643+
p1 to receive a SIGPIPE if p2 exits before p1. Each process must be
1644+
waited on and its return code checked to detect failure in any stage.
16271645

16281646

16291647
Replacing :func:`os.system`

Doc/whatsnew/3.15.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,17 @@ ssl
11211121
subprocess
11221122
----------
11231123

1124+
* Added :func:`subprocess.run_pipeline` for running a sequence of commands
1125+
connected stdout-to-stdin, similar to a shell ``a | b | c`` pipeline,
1126+
without invoking a shell. It returns a
1127+
:class:`~subprocess.CompletedPipeline` carrying the return codes of every
1128+
stage; passing ``check=True`` raises :exc:`~subprocess.PipelineError` if
1129+
any stage fails (pipefail semantics). This replaces the manual
1130+
:class:`~subprocess.Popen`-chaining recipe and the
1131+
``bash -c "set -o pipefail; ..."`` workaround previously needed to detect
1132+
failures in non-final stages.
1133+
(Contributed by Gregory P. Smith in :gh:`47798`.)
1134+
11241135
* :meth:`subprocess.Popen.wait`: when ``timeout`` is not ``None`` and the
11251136
platform supports it, an efficient event-driven mechanism is used to wait for
11261137
process termination:

0 commit comments

Comments
 (0)