It is currently possible to pass specific file descriptors to a subprocess using the pass_fds
parameter of subprocess.Popen()
. That parameter is a sequence of file descriptors that will be kept, in addition to 0, 1, and 2.
However it is not possible to map those file descriptors to other numbers before starting the subprocess, like it is possible in shell (e.g. cmd 7> output.log 4>&3
). Those fds will keep the number they are currently.
It should be easy to change pass_fds
to accept a mapping in addition to a sequence. Another more compatible option would be to introduce a separate parameter, that would be exclusive with pass_fds
.
Use case
When a shell script grows in complexity, it is often desirable to rewrite it in Python. This gives easier error-handling, support for lists and dictionaries, and more portability. This translation is currently easy and can be done in an iterative fashion (script be script) as Popen()
supports everything the shell does, including support for passing files or pipes from other processes as stdin
, stdout
, and stderr
.
# printf 'Hello world\n' | tr a-z A-Z
p1 = Popen(['printf', 'Hello world\\n'], stdout=PIPE)
p2 = Popen(['tr', 'a-z', 'A-Z'], stdin=p1.stdout)
The one blind spot I have found is passing specific file descriptors to a command. Sometimes a command will have a parameter or environment variable to specify which file descriptor to use, but often this is not the case, especially with shell scripts. The only way to do this is to use the os.dup2()
interface and hope the file descriptor isn’t already used, or do it in preexec_fn
which is even more error-prone.
# cmd 3> output.log
log = open('output.log', 'w')
p1 = Popen(['cmd'], pass_fds=[log.fileno()]) # might be 3, might be another fd...
p1 = Popen(['cmd'], pass_fds={3: log.fileno()}) # proposed
This was already mentioned in the ticket for the pass_fds
feature by ferringb: add pass_fds paramter to subprocess.Popen() · Issue #50808 · python/cpython · GitHub