subprocess.Popen communicate() writes to console, but not to log file

大城市里の小女人 提交于 2020-06-28 05:47:29

问题


I have the following line in a Python script that runs a separate Python script from within the original script:

subprocess.Popen("'/MyExternalPythonScript.py' " + theArgumentToPassToPythonScript, shell=True).communicate()

Using the above line, any print() statements found in the separate Python file do appear in the console of the main Python script.

However, these statements are not reflected in the .txt file log that the script writes to.

Does anyone know how to fix this, so that the .txt file exactly reflects the true console text of the main Python script?


This is the method I am using to save the console as a .txt file, in real time:

import sys
class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("/ScriptLog.txt", "w", 0)
    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)


sys.stdout = Logger()

I am not necessarily attached to this method. I am interested in any method that will achieve what I've detailed.


回答1:


Keep in mind that subprocess spawns a new process, and doesn't really communicate with the parent process (they're pretty much independent entities). Despite its name, the communicate method is just a way of sending/receiving data from the parent process to the child process (simulate that the user input something on the terminal, for instance)

In order to know where to write the output, subprocess uses numbers (file identifiers or file numbers). When subprocess spawns a process, the child process only knows that the standard output is the file identified in the O.S. as 7 (to say a number) but that's pretty much it. The subprocess will independently query the operative system with something like "Hey! What is file number 7? Give it to me, I have something to write in it." (understanding what a C fork does is quite helpful here)

Basically, the spawned subprocess doesn't understand your Logger class. It just knows it has to write its stuff to a file: a file which is uniquely identified within the O.S with a number and that unless otherwise specified, that number corresponds with the file descriptor of the standard output (but as explained in the case #2 below, you can change it if you want)

So you have several "solutions"...

  1. Clone (tee) stdout to a file, so when something is written to stdout, the operative system ALSO writes it to your file (this is really not Python-related... it's OS related):

    import os
    import tempfile
    import subprocess
    
    file_log = os.path.join(tempfile.gettempdir(), 'foo.txt')
    p = subprocess.Popen("python ./run_something.py | tee %s" % file_log, shell=True)
    p.wait()
    
  2. Choose whether to write to terminal OR to the file using the fileno() function of each. For instance, to write only to the file:

    import os
    import tempfile
    import subprocess
    
    file_log = os.path.join(tempfile.gettempdir(), 'foo.txt')
    with open(file_log, 'w') as f:
        p = subprocess.Popen("python ./run_something.py", shell=True, stdout=f.fileno())
        p.wait()
    
  3. What I personally find "safer" (I don't feel confortable overwriting sys.stdout): Just let the command run and store its output into a variable and pick it up later (in the parent process):

    import os
    import tempfile
    import subprocess
    
    p = subprocess.Popen("python ./run_something.py", shell=True, stdout=subprocess.PIPE)
    p.wait()
    contents = p.stdout.read()
    # Whatever the output of Subprocess was is now stored in 'contents'
    # Let's write it to file:
    file_log = os.path.join(tempfile.gettempdir(), 'foo.txt')
    with open(file_log, 'w') as f:
        f.write(contents)
    

    This way, you can also do a print(contents) somewhere in your code to output whatever the subprocess "said" to the terminal.

For example purposes, the script "./run_something.py" is just this:

print("Foo1")
print("Foo2")
print("Foo3")



回答2:


Do you really need subprocess.Popen's communicate() method? It looks like you just want the output. That's what subprocess.check_output() is for.

If you use that, you can use the built-in logging module for "tee"-ing the output stream to multiple destinations.

import logging
import subprocess
import sys

EXTERNAL_SCRIPT_PATH = '/path/to/talker.py'
LOG_FILE_PATH = '/path/to/debug.log'

logger = logging.getLogger('')
logger.setLevel(logging.INFO)

# Log to screen
console_logger = logging.StreamHandler(sys.stdout)
logger.addHandler(console_logger)

# Log to file
file_logger = logging.FileHandler(LOG_FILE_PATH)
logger.addHandler(file_logger)

# Driver script output
logger.info('Calling external script')

# External script output
logger.info(
    subprocess.check_output(EXTERNAL_SCRIPT_PATH, shell=True)
)

# More driver script output
logger.info('Finished calling external script')

As always, be careful with shell=True. If you can write the call as subprocess.check_output(['/path/to/script.py', 'arg1', 'arg2']), do so!



来源:https://stackoverflow.com/questions/47999670/subprocess-popen-communicate-writes-to-console-but-not-to-log-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!