# PEP 787: Safer subprocess usage using t-strings

**URL:** <https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311>\
**Category:** PEPs\
**Created:** [April 14, 2025, 3:25pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311 "2025-04-14T15:25:18Z")\
**Posts on this page:** 20\
**Page:** 5

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 23, 2025, 6:51pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/81 "2025-04-23T18:51:44Z")

</div>

> [@oscarbenjamin](#):
>
> I don’t think that the reasons for any points made here are related to meta-level considerations.

Perhaps they should be? With so many people trying to contribute to Python simultaneously, taking each individual contribution in isolation is a recipe for ~~becoming C++~~ conflicting or inconsistent outcomes.

> [@oscarbenjamin](#):
>
> the specific proposal on the table to change that API as compared to alternatives such as adding a new function

Nobody has shown that the new function would be any less convoluted (modulo the deprecated-but-not-removed arguments) than the current one. The only proposal that’s been made is we get to give different `TypeError`s from each one if the user changes the argument without also changing the function.

But probably nobody has shown it because it’s not related to this PEP, so that’s a good thing - we’re keeping somewhat on topic.

> [@oscarbenjamin](#):
>
> When Barry talks about putting lipstick on a pig I’m confident that his reasons for doing so lie somewhere below the meta-level.

I didn’t see this quote, and certainly wasn’t commenting on it. Though my interpretation here would be that the OS APIs are the pig and subprocess is the lipstick (hence my surprise that the lipstick didn’t manage to hide the different breeds of pig beneath it).

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [April 23, 2025, 8:16pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/82 "2025-04-23T20:16:12Z")

</div>

> [@steve.dower](#):
>
> Nobody has shown that the new function would be any less convoluted (modulo the deprecated-but-not-removed arguments) than the current one.

I consider the fact that

```python
subprocess.run("some command with spaces")

```

means something fundamentally different than

```python
subprocess.run(t"some command with spaces")

```

to be pretty convoluted, and explaining _why_ that’s the case to be likely to result in extremely convoluted documentation.

For anyone who hasn’t checked the subprocess docs, the former runs an executable called `some command with spaces`, whereas the latter runs the command `some` with arguments `["command", "with", "spaces"]`.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 23, 2025, 8:26pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/83 "2025-04-23T20:26:39Z")

</div>

> [@pf\_moore](#):
>
> For anyone who hasn’t checked the subprocess docs, the former runs an executable called `some command with spaces`, whereas the latter runs the command `some` with arguments `["command", "with", "spaces"]`.

On POSIX. yes. On Windows they both run `some` with three arguments. And the fact that it’s different is bad, but adding t-string support here doesn’t actually make the inconsistency worse.

Arguably we could deprecate str-with-spaces on POSIX and change it to imply `shlex.split`, and then we’d have consistency again.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [April 23, 2025, 9:12pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/84 "2025-04-23T21:12:48Z")

</div>

> [@steve.dower](#):
>
> On Windows they both run `some` with three arguments.

That’s inconsistent with the documentation, which says:

> If passing a single string, either _shell_ must be [`True`](https://docs.python.org/3.13/library/constants.html#True) (see below) or else the string must simply name the program to be executed without specifying any arguments.

All I was trying to do was point out that I _had_ claimed the new proposal was more convoluted than the existing behaviour. I don’t think you can claim that undocumented behaviour makes the existing behaviour _more_ convoluted, or changing the existing semantics to make things more consistent makes the _proposed_ new behaviour _less_ convoluted.

> [@steve.dower](#):
>
> But probably nobody has shown it because it’s not related to this PEP, so that’s a good thing - we’re keeping somewhat on topic.

It’s entirely on topic to flag potential issues with the proposal, and I consider adding yet more complexity to an already complex and easily misunderstood\[1\] API to be very much a potential issue.

Streamlining the existing API is a useful improvement to consider, and if we _did_ do that, we may well end up with a cleaner basis to add t-string support to, but I’m happy to accept that’s off-topic here, as long as it’s clear that putting _that_ proposal off topic doesn’t somehow magically mean that the existing API is any better as a basis for trying to add t-string support.

* * *

1. as evidenced by the confusion over the case of a single string argument with `shell=False`

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 23, 2025, 9:23pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/85 "2025-04-23T21:23:29Z")

</div>

> [@pf\_moore](#):
>
> That’s inconsistent with the documentation, which says: …

The documentation for the `args` parameter to `Popen` constructor also says “If _args_ is a string, the interpretation is platform-dependent and described below”, so nobody is winning any consistency arguments based on our own docs 🙂 If the docs were updated to reflect reality, then that earlier section you quoted would be changed.

> [@pf\_moore](#):
>
> It’s entirely on topic to flag potential issues with the proposal, and I consider adding yet more complexity to an already complex and easily misunderstood[[1]](#footnote-242842-1) API to be very much a potential issue.

That’s what I said, yeah. The “nobody has shown it” I was referring to was a complete new API design that somehow has fewer arguments than `Popen`. At best, it’d end up like the .NET API where you assign a whole lot of attributes to an object rather than (optionally) passing them to a constructor - this is just a complicated task with a huge amount of variation (the current implementation still isn’t sufficient to implement, e.g., the `py` launcher). I’m highly skeptical that a new API would make the simple cases simpler without excluding other important cases.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [April 24, 2025, 1:15am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/86 "2025-04-24T01:15:24Z")

</div>

> [@barry](#):
>
> wonder if we’re just [putting lipstick on a pig](https://en.wikipedia.org/wiki/Lipstick_on_a_pig). In other forums, I’ve seen recommendations for [plumbum](https://plumbum.readthedocs.io/en/latest/) which @henryiii is a maintainer of. I’ve never used it, but the API looks interesting, and it makes me think there’s gotta be better ways to shell out from Python than the `subprocess` API.

[`plumbum`’s approach](https://plumbum.readthedocs.io/en/latest/local_commands.html#guide-local-commands) does indeed look like it would produce quite easy to read code. Even it ends up suffering from an(“over-abundance”, “of”, “explicit”, “quotation”, “marks”, when, “writing”, out, “long”, commands, though).

The suggestion affirms for me though that the most useful core idea the PEP can offer is an as-general-as-we-can-make-it algorithm that:

- accepts a template string
- quotes all of the input fields
- emits the _list_ form of the resulting command invocation (since that is the form that can be safely passed to the widest variety of platform subprocess APIs)

Approaching it that way may also help separate any discussion of the existing cross platform differences in the way list inputs are handled.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 24, 2025, 11:04am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/87 "2025-04-24T11:04:30Z")

</div>

> [@ncoghlan](#):
>
> - accepts a template string
> - quotes all of the input fields
> - emits the _list_ form of the resulting command invocation (since that is the form that can be safely passed to the widest variety of platform subprocess APIs)

No quoting required (step 2), as the list form will get platform-specific quoting when you use it. If the result is a list, then the t-string here is just about the rules for splitting it up into argument-sized chunks.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [April 24, 2025, 1:21pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/88 "2025-04-24T13:21:55Z")

</div>

> [@steve.dower](#):
>
> No quoting required (step 2), as the list form will get platform-specific quoting when you use it. If the result is a list, then the t-string here is just about the rules for splitting it up into argument-sized chunks.

Unfortunately, splitting without somehow quoting the interpolated fields first will mess up file names that contain whitespace. Some examples (with the incorrect result that trying them with f-strings instead would give you):

- `fname = "filename with spaces"; shlex.split(t"ls {fname}")` should give `["ls", "filename with spaces"]`, NOT `["ls", "filename", "with", "spaces"]`
- `dirname="some dir"; fname="some file"; shlex.split(t"ls {dirname}/{fname}")` should give `["ls", "some dir/some file"]`, NOT `["ls", "some", "dir/some", "file"]`

The key _benefit_ of considering the problem at the `shlex.split` level, though, is that we’re not escaping for arbitrary shells any more, we’re only escaping for our own `shlex.lex` algorithm.

For POSIX mode splitting, `shlex.quote` is already up to that task:

```python
>>> fname="filename with spaces"
>>> shlex.split(f"ls {fname}")
['ls', 'filename', 'with', 'spaces']
>>> shlex.split(f"ls {shlex.quote(fname)}")
['ls', 'filename with spaces']

```

```python
>>> dirname="some dir"
>>> fname="some file"
>>> shlex.split(f"ls {dirname}/{fname}")
['ls', 'some', 'dir/some', 'file']
>>> shlex.split(f"ls {shlex.quote(dirname)}/{shlex.quote(fname)}")
['ls', 'some dir/some file']

```

However, splitting in non-posix mode fails to do the right thing if `shlex.quote` is used to do the quoting, so we’d need to decide how to handle that:

```python
>>> shlex.split(f"ls {shlex.quote(fname)}", posix=False)
['ls', "'filename with spaces'"]

```

```python
>>> shlex.split(f"ls {shlex.quote(dirname)}/{shlex.quote(fname)}", posix=False)
['ls', "'some dir'", "/'some", "file'"]

```

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 24, 2025, 1:26pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/89 "2025-04-24T13:26:41Z")

</div>

> [@ncoghlan](#):
>
> Unfortunately, splitting without somehow quoting the interpolated fields first will mess up file names that contain whitespace.

You split before you interpolate,\[1\] so it goes:

```python
d = "dir with spaces"
shlex.split(t"ls {d}/file")
--> ["ls", "{d}/file"] # intermediate step
--> ["ls", "dir with spaces/file"]

```

And then whatever accepts a list knows how to quote/escape each argument (if necessary).

And yes, the splitting logic of a t-string here is platform independent, though it’d be nice for it to be pretty close to “normal” behaviour for as many platforms as possible. The quoting behaviour is entirely platform-specific, because we don’t have to quote arguments that are in a list.

* * *

1. Not to belabour the point, but this is the _main_ advantage of a t-string over an f-string. We get to process the _source_ string (after it’s already split into parts, which is a bit annoying, but manageable) before interpolating.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [April 24, 2025, 2:16pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/90 "2025-04-24T14:16:02Z")

</div>

> [@steve.dower](#):
>
> We get to process the _source_ string (after it’s already split into parts, which is a bit annoying, but manageable) before interpolating.

The “bit annoying, but manageable” part of having to actually split each text segment is the aspect I was trying to avoid by quoting the inputs, but I suspect you’re right, and all the ways of potentially avoiding handling the input incrementally are actually worse once the goal is to support both the POSIX and non-POSIX splitting algorithms.

Where it potentially gets complicated is when programmatic quoting gets combined with inline quoting, but it may be that treating the inline quotes the same as any other non-whitespace character will actually give better results than the status quo gives in that case (the last two examples here are unlikely to be what the user wanted):

```python
>>> dirname="some dir"
>>> fname="some file"
>>> shlex.split(f"ls {shlex.quote(dirname)}/{shlex.quote(fname)}")
['ls', 'some dir/some file']
>>> shlex.split(f"ls '{shlex.quote(dirname)}/{shlex.quote(fname)}'")
['ls', 'some', 'dir/some', 'file']
>>> shlex.split(f'ls "{shlex.quote(dirname)}/{shlex.quote(fname)}"')
['ls', "'some dir'/'some file'"]

```

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [April 24, 2025, 2:51pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/91 "2025-04-24T14:51:06Z")

</div>

The main advantage I see for t-strings here would be avoiding mis-usable apis. shlex “works”, but as multiple examples have pointed out, use of it is still error prone. Some of the examples here actually mirror bugs I’ve seen in scripts pushed to production pipelines before. If t-strings can provide a one-shot API that is unambiguous and hard to misuse, that’s a benefit to everyone, reviewers, novices, and everyone in between.

I still think such an API needs to be a new API to have that benefit though, there’s no way to reconcile such a goal with the existing pile of options. It needs to encapsulate every part of this in a way that is resistant to misunderstanding, reliance on platform specific “oh, it works on my machine”, and so on.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [April 24, 2025, 2:51pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/92 "2025-04-24T14:51:36Z")

</div>

> [@ncoghlan](#):
>
> Where it potentially gets complicated is when programmatic quoting gets combined with inline quoting, but it may be that treating the inline quotes the same as any other non-whitespace character will actually give better results than the status quo gives in that case

For the user explicitly calling `shlex.quote`, the third ought to be what they get (I’m not sure which of those is actually the real result, but I do note that they’re all f-strings, not t-strings).

But I think it’s actually not all that bad. If you start by substituting each variable with something that never needs quoting, then you can do the normal `shlex.split` to get arguments, and then go back and substitute the original values into the list.

```python
>>> dirname="some dir"
>>> fname="some file"
>>> magic_split_function(t"ls {dirname}/{fname}")
# 1: shlex.split("ls $1/$2") -> ["ls", "$1/$2"]
# 2: sub(...) -> ["ls", "some dir/some file"]
["ls", "some dir/some file"]
>>> magic_split_function(t'ls "{dirname}/sub dir/{fname}"')
# 1: shlex.split('ls "$1/sub dir/$2"') -> ["ls", "$1/sub dir/$2"]
# 2: sub(...) -> ["ls", "some dir/sub dir/some file"]
["ls", "some dir/sub dir/some file"]

```

We would discourage explicit shlex.quote, because the only logical outcome is to get unnecessary quotes, which would then be escaped (or checked and ignored, but this is `subprocess` and not the t-string) and passed explicitly later on (because they’re part of the variable, not the template):

```python
>>> magic_split_function(t'ls {shlex.quote(dirname)}/{shlex.quote(fname)}')
# 1: shlex.split("ls $1/$2") -> ["ls", "$1/$2"]
# 2: sub(...) -> ["ls", '"some dir"/"some file"']
["ls", '"some dir"/"some file"']

```

So basically:

- quotes in the template are to delimit literal text into arguments, and may be omitted from the result
- substitutions don’t re-split arguments, and all characters become quoted/escaped
- don’t use `shlex.quote` explicitly with the t-string

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [April 24, 2025, 3:06pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/93 "2025-04-24T15:06:13Z")

</div>

> [@ncoghlan](#):
>
> Some examples (with the incorrect result that trying them with f-strings instead would give you)

The examples you give would work equally well with an algorithm that just split on whitespace (while treating substitutions as non-whitespace for the purposes of splitting). Are there any examples that actually need anything more complex? I guess something like

```python
shlex.split(t"'a b' {dirname}/{fname}") -> ["a b", "some dir/some file"]

```

would be a case that needs “shlex logic”, but are there realistic cases of things like that? Specifically ones where the shlex version is demonstrably better than just switching to list format?

I suppose it doesn’t matter that much though - if this is isolated to the shlex module, it’s clearly marked as both non-portable and relying on _our_ interpretation of the POSIX rules\[1\]. So while it may not be the most useful feature (shlex isn’t exactly the most popular stdlib module) it will be well defined - and available to people who _do_ get an advantage from its specific feature set.

* * *

1. As opposed to, say, bash’s or zsh’s

---

<div class="post-metadata">

**Author:** ![Nodd](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nodd/32/6805_2.png) [@Nodd](https://discuss.python.org/u/Nodd)\
**Post date:** [April 24, 2025, 3:38pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/94 "2025-04-24T15:38:33Z")

</div>

There are still ways to shoot yourself in the foot using t-strings, for example:

```python
if option:
    options = "-l -h"
else:
    options = ""
magic_split_function(t"ls {options} {dirname}/{fname}")

```

This would give either `["ls", "-l -h", "some dir/some file"]` or `["ls", "", "some dir/some file"]` which would be both wrong. Of course it can simply ruled as “don’t to that”, but I just wanted to raise the fact that a substitution can be either a full argument or a part of an argument, but not cover multiple arguments. Contrary to f-string, the special handling of substitutions may have unexpected consequences. Maybe it should be explicit in the documentation of `magic_split_function`.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [April 24, 2025, 3:48pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/95 "2025-04-24T15:48:53Z")

</div>

> [@Nodd](#):
>
> Of course it can simply ruled as “don’t to that”, but I just wanted to raise the fact that a substitution can be either a full argument or a part of an argument, but not cover multiple arguments.

One of the advantages of template processors is that it’s relatively straightforward to add further type handling later (as long as unknown input types raise exceptions when processed). For example, if tuples were added as a valid substitution field value type, then interpolating a dynamic list of arguments might look like:

```python
if option:
    options = ("-l", "-h")
else:
    options = ()
magic_split_function(t"ls {options} {dirname}/{fname}")

```

(accepting arbitrary iterables can be a bit more problematic, since that’s such a broad type it can significantly reduce your flexibility in changing the interpretation of other types in later API updates)

---

<div class="post-metadata">

**Author:** ![Nodd](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nodd/32/6805_2.png) [@Nodd](https://discuss.python.org/u/Nodd)\
**Post date:** [April 24, 2025, 4:50pm UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/96 "2025-04-24T16:50:51Z")

</div>

Thanks, I didn’t think about other types than `str`. That’s a way better solution than “don’t do that”!

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [April 25, 2025, 1:25am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/97 "2025-04-25T01:25:15Z")

</div>

> [@ncoghlan](#):
>
> For example, if tuples were added as a valid substitution field value type, then interpolating a dynamic list of arguments might look like:
> 
> ```python
> if option:
> options = ("-l", "-h")
> else:
> options = ()
> magic_split_function(t"ls {options} {dirname}/{fname}")
> 
> ```

But with `options` being a tuple of string literals we’re going back to reading the noise of quotes and commas between arguments, defeating one of the important purposes of the proposal.

I suggest that we allow nested templates to be flattened instead:

```python
if option:
    options = t"-l -h"
else:
    options = t""
magic_split_function(t"ls {options} {dirname}/{fname}")

```

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [April 25, 2025, 2:18am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/98 "2025-04-25T02:18:31Z")

</div>

> [@barry](#):
>
> I wonder if we’re just [putting lipstick on a pig](https://en.wikipedia.org/wiki/Lipstick_on_a_pig). In other forums, I’ve seen recommendations for [plumbum](https://plumbum.readthedocs.io/en/latest/) which @henryiii is a maintainer of. I’ve never used it, but the API looks interesting, and it makes me think there’s gotta be better ways to shell out from Python than the `subprocess` API.

> [@ncoghlan](#):
>
> [`plumbum`’s approach](https://plumbum.readthedocs.io/en/latest/local_commands.html#guide-local-commands) does indeed look like it would produce quite easy to read code. Even it ends up suffering from an(“over-abundance”, “of”, “explicit”, “quotation”, “marks”, when, “writing”, out, “long”, commands, though).

I know a lot of people use `subprocess` with `shell=True` even when knowing the security risks because they don’t want to deal with the relative clunkiness of `subprocess` API’s I/O redirections and would rather take advantage of the convenience of `|` and `>` that a shell has to offer.

I agree that `subprocess` can use a new, higher-level API similar to `pathlib` as a wrapper library to `os.path` so that invoking external commands in Python feels a bit more at home for those who are more used to shell scripting.

`plumbum` is a good step in that direction, but I imagine that with a t-string we can make a library (or expand the existing `subprocess`) with an arguably friendlier API like this:

```python
from subprocess2 import run, cmd, Result

target = "Python World"
output = Result()
run(
    cmd-
    t"echo Hello {target}" |
    t"cat -n" >> output
)
print(output.stdout) # 1 Hello Python World

```

Like `pathlib`’s repurposing of the `/` operator, repurposing the `|`, `<<` and `>>` operators should feel intuitive in this context.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [April 25, 2025, 2:21am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/99 "2025-04-25T02:21:35Z")

</div>

This runs into the same problem that makes a new API that only accepts t-strings, not normal strings, appealing: namely, the behavior changes in a way that requires deep knowledge to spot an issue in a casual reading between a t-string and a string. It’s not an API where accepting either is a good thing as it adds to subtleties reviewers need to be aware of, and makes it impossible for static analysis tools to go “hey you shouldn’t do that”

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [April 25, 2025, 2:34am UTC](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311/100 "2025-04-25T02:34:20Z")

</div>

If you’re suggesting that this new API should accept only t-strings then I agree with you. Notice how I made `t"-l -h"`, `t""` and `t"cat -n"` all template strings in my examples even when they’re fixed values.

[Previous page](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311.md?page=4)

[Next page](https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/88311.md?page=6)
