2

I am using a 3rd-party python module which is normally called through terminal commands. When called through terminal commands it has a verbose option which prints to terminal in real time.

I then have another python program which calls the 3rd-party program through subprocess. Unfortunately, when called through subprocess the terminal output no longer flushes, and is only returned on completion (the process takes many hours so I would like real-time progress).

I can see the source code of the 3rd-party module and it does not set printing to be flushed such as print('example', flush=True). Is there a way to force the flushing through my module without editing the 3rd-party source code? Furthermore, can I send this output to a log file (again in real time)?

Thanks for any help.

QuantumChris
  • 963
  • 10
  • 21
  • I think this answers your question: https://stackoverflow.com/questions/4417546/constantly-print-subprocess-output-while-process-is-running – blues Jul 22 '19 at 14:26
  • @blues Sadly not. Because the 3rd-party software doesn't flush output, neither does using this method. – QuantumChris Jul 22 '19 at 14:43

1 Answers1

1

The issue is most likely that many programs work differently if run interactively in a terminal or as part of a pipe line (i.e. called using subprocess). It has very little to do with Python itself, but more with the Unix/Linux architecture.

As you have noted, it is possible to force a program to flush stdout even when run in a pipe line, but it requires changes to the source code, by manually applying stdout.flush calls.

Another way to print to screen, is to "trick" the program to think it is working with an interactive terminal, using a so called pseudo-terminal. There is a supporting module for this in the Python standard library, namely pty. Using, that, you will not explicitly call subprocess.run (or Popen or ...). Instead you have to use the pty.spawn call:

def prout(fd):
    data = os.read(fd, 1024)
    while(data):
        print(data.decode(), end="")
        data = os.read(fd, 1024)

pty.spawn("./callee.py", prout)

As can be seen, this requires a special function for handling stdout. Here above, I just print it to the terminal, but of course it is possible to do other thing with the text as well (such as log or parse...)

Another way to trick the program, is to use an external program, called unbuffer. Unbuffer will take your script as input, and make the program think (as for the pty call) that is called from a terminal. This is arguably simpler if unbuffer is installed or you are allowed to install it on your system (it is part of the expect package). All you have to do then, is to change your subprocess call as

p=subprocess.Popen(["unbuffer", "./callee.py"], stdout=subprocess.PIPE)

and then of course handle the output as usual, e.g. with some code like

for line in p.stdout:
    print(line.decode(), end="")
print(p.communicate()[0].decode(), end="")

or similar. But this last part I think you have already covered, as you seem to be doing something with the output.

JohanL
  • 6,671
  • 1
  • 12
  • 26
  • Thanks for the reply. We can't guarantee users will unbuffer so I've tried the pty solution. Unfortunately the command is returning permission denied when running through pty so I think we will have to drop this. We'll look into getting a pull request through to the 3rd-party software to just change their code. – QuantumChris Jul 25 '19 at 11:21