Search code examples
pythonforkstdoutstderr

python: fork without an external command, and capturing stdout and stderr separately


I'd like to fork a subprocess in python that does not run an external command ... it would just run a defined function. And I want to capture stdout and stderr separately.

I know how to use os.fork() and os.pipe(), but that mechanism only gives me two fd's to work with. I'm looking for three fd's: one for stdin, one for stdout, and one for stderr. This is easy to manage using subprocess.Popen when running an external command, but that function doesn't seem to allow a local function to be forked; only a separate executable.

In ruby, the popen3 command can take "-" as its command argument, and in this case, a fork takes place without any external command being invoked, and the 3 fd's I mentioned are returned. Is there some sort of python analog to this routine in python?


Solution

    • If you want to redirect the stdout and stderr separately from the child process, you can simply create two separate pipes for each, instead of one. I have shared the relevant code.

    • You can also read this thread to gain more knowledge on this subject: Redirect stdout to a file in Python?

    • I have mentioned two methods for writing to stdout, and stderr from the child process (Method1, Method2)

    • If you want to write to stdin of child process as well, you should create another file descriptor. This time the r would go to child process, and the w would go to the parent process.

    import os
    import sys
    import time
    
    # Create two pipes. One for sys.stdout, and one for sys.stderr
    r_out, w_out = os.pipe()
    r_err, w_err = os.pipe()
    
    pid = os.fork()
    if pid == 0:
        # Child process
        os.close(r_out)
        os.close(r_err)
    
        w1 = os.fdopen(w_out, "w")
        w2 = os.fdopen(w_err, "w")
        sys.stdout = w1
        sys.stderr = w2
    
        # Note that flush=True is necessary only if you want to ensure the order of messages printed
        # across method1, and method2 is maintained
    
        # Method 1: Standard Python print messages
        print("Redirected to stdout #2", flush=True)
        print("Redirected to stderr #2", file=sys.stderr, flush=True)
    
        # Method 2: Using system file descriptors
        stdout_fd = sys.stdout.fileno()
        os.write(stdout_fd, b'Redirected to stdout')
    
        stderr_fd = sys.stderr.fileno()
        os.write(stderr_fd, b'Redirected to stderr')
    
        # Restore original stdout, and stderr
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__
    
        # Close the file descriptors
        w1.close()
        w2.close()
    
    else:
        # Parent process
        os.close(w_out)
        os.close(w_err)
    
        r1 = os.fdopen(r_out)
        r2 = os.fdopen(r_err)
        for i in range(5):
            # Note that r1.read(), and r2.read() are non-blocking calls
            # You can run this while loop as long as you want.
            print("Read text (sysout):", r1.read())
            print("Read text (syserr):", r2.read())
            time.sleep(0.5)