Unix script command breaks terminal when unbuffering

Viewed 58

Sorry for the wonky title, maybe someone can improve it.

I'm using the script command within a Python subprocess to unbuffer the output a command. I then read a single line from that process, and lastly I print some text. Here is my code (this file is named script.py):

import subprocess
import time

proc = subprocess.Popen(["script", "-q", "-c", "cat script.py", "/dev/null"],
                        stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# proc = subprocess.Popen(["stdbuf", "-oL", "-eL", "cat", "script.py"],
#                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)

proc.stdout.readline()

print("printing before...")
print("printing before...")
print("printing before...")

time.sleep(2)

print("printing after...")
print("printing after...")
print("printing after...")

I'm getting this absolutely bizarre behavior, such that when the subprocess writes to stdout, subsequent python print statements include several spaces after each line, until the process is terminated. Here's a screenshot of what it looks like:

enter image description here

As far as I can tell, it's printing the same number of spaces as there are columns in the terminal, after each line. I think this might have something to do with carriage returns, but I really have no idea. I can't understand why this would affect python's output at all, since I'm suppressing both stdout and stderr when doing a Popen.

I'm running this on arch linux, python version 3.8.6 and uname -a outputs:

Linux carbon 5.9.10-arch1-1 #1 SMP PREEMPT Sun, 22 Nov 2020 14:16:59 +0000 x86_64 GNU/Linux

Please let me know if there is any other information I can give.

1 Answers

Because your subprocess stdin is still a TTY, script has reached around and changed its mode for handling CR and LF. Your first 3 prints are made while script is still running in the background. During the sleep, script fixes the terminal and exits. If you wait for your subprocess to complete, the problem goes away... usually. There is a chance that a signal will keep script from cleaning up after itself and ruin the rest of your session. Likely the better option is to redirect stdin.

proc = subprocess.Popen(["script", "-q", "-c", "cat script.py", "/dev/null"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

This is all quite risky. If the executed program writes enough data to fill its stdout pipe, it will hang. If you just want the first line of a program that expects to run under at TTY, you could use pty.spawn to do the work. Interestingly, its documentation has an EXAMPLE script implementation.

Related