2

I'm writing a Python 3.6 program on Windows to record all the input to a subprocess for backup. (I cannot modify the subprocess code as the real program is a commercial product (exe file) and I don't have the source code.)

I tried the following code, and it doesn't work. The text only shows in the terminal, and the txt files used for logging are empty.

Main.py:

import subprocess
import sys    

class dup_stream():
    def __init__(self, original_stream, file):
        self.original_stream = original_stream
        self.log = open(file, 'a')

    def write(self, message):
        self.log.write(message)
        self.original_stream.write(message)

    def fileno(self):
        return self.original_stream.fileno()


completed_process = subprocess.run('python Hello.py',
                                   stdin=dup_stream(sys.stdin, 'stdin.txt'),
                                   stderr=dup_stream(sys.stderr, 'stderr.txt'),
                                   stdout=dup_stream(sys.stdout, 'stdout.txt'))

Hello.py:

name = input("Your name:") # e.g. "Jane Doe"
print('Hello,',name)

Expected result:

In terminal:

Your name: Jane Doe
Hello, Jane Doe

stdin.txt:

Jane Doe

stdout.txt:

Your name:
Hello, Jane Doe

I've asked a more general question before (Capture inputs to subprocess.run()), but there aren't any practical answer.

liyuanhe211
  • 671
  • 6
  • 15
  • Maybe you can try async subprocess https://docs.python.org/3/library/asyncio-subprocess.html – C. Yduqoli Dec 19 '18 at 08:51
  • 2
    Possible duplicate of [Capture inputs to subprocess.run()](https://stackoverflow.com/questions/53810417/capture-inputs-to-subprocess-run) – tripleee Dec 19 '18 at 08:53

2 Answers2

2

subprocess will very likely bypass this because it uses the fileno directly.

Use stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE. However, it is not totally straightforward how to forward everything then. Maybe the easiest way is to have 3 threads, one for each stdin, stdout, and stderr, where you read from the source (e.g. sys.stdin, or process.stdout) and then write it to the file and to the target (process.stdin, or sys.stdout). See also here for some other options.

Albert
  • 65,406
  • 61
  • 242
  • 386
0

I had a similar problem which I solved with a wrapper class, which I passed instead of sys.stdin (i only needed to log read and readline):

class StdinWrapper(object):

  def __getattribute__(self, name):
    attr = getattr(sys.stdin, name)

    if name == 'readline' or name == 'read':
      def method_with_log(*args, **kwargs):
        result = attr(*args, **kwargs)
        f = open('/tmp/log-input', 'a')
        f.write(result)
        f.close()
        return result
      return method_with_log

    return attr
prumand
  • 160
  • 10