Sending output to stdout and also to a file

David Bolen db3l at fitlinxx.com
Mon Jul 17 22:06:47 EDT 2000


"donotspam-jen at personic.com" <dnotspam-jen at personic.com> writes:

> Is there a way to have output from system commands go to stdout and also to
> a file?  For example, is there a way to get the results of the MS DOS "DIR"
> command and have it appear both in a file and also on the screen?
> 
> The particular issue I'm trying to solve is that I have a script that calls
> PVCS commands.  PVCS sends things to both stderr and stdout, which by
> default go to the screen.  I'd like to capture this output and put it both
> on the screen and also in a log file that can be reviewed later.

To trap output of a system command you want to use popen() to run the
command giving you the output via a File object.  (For
Win95/98/NT-without-console, you should use win32pipe.popen() from the
Win32 extensions for reliable pipes to child processes, but there's a
side effect in that you won't get the result code of the process on
the close()).

If you just want to capture all output of your command (stdout and
stderr), you can add a "2>&1" to the end of the command to reroute
stderr to the same place stdout is going - which is your process).

Once you have the output, you can do anything you want with it.

For example, here's a small function that I use to execute commands
and dump their output prefaced with a few spaces (to indent it with
respects to the other output that my script is creating).  I also
include a dump of the command and some execution time information
since this normally gets used with background scripts that run lots of
commands across a bunch of sites.

	  - - - - - - - - - - - - - - - - - - - - - - - - -
import os
import time

def Execute (Command):
    """
    Executes a command string, accepting all output and logging it.
    The function automatically appends a stderr redirect to include
    stderr in the output.
    """
    print "Executing", Command
    start_time = time.time()
    pipe = os.popen(Command + " 2>&1")
    while 1:
        line = pipe.readline()
        if not line:
            break
        print "  ", line,
    rc = pipe.close()
    stop_time = time.time()
    if rc:
        print "Command completed with exit code", rc,
    else:
        print "Command completed successfully",
    print "(elapsed time = %d s)" % (stop_time - start_time)

    return rc
	  - - - - - - - - - - - - - - - - - - - - - - - - -

Now the above function just prints the command output with the
indentation, but what I normally do is redirect sys.stdout to a log
class that I have which logs to a log file with an optional copy to
stdout.  The log class also prepends a timestamp (and a "site" id
which is just a string used when I walk a bunch of sites for
operations).  This ensures I get a log of everything that is happening
in my script, including all child processes.

	  - - - - - - - - - - - - - - - - - - - - - - - - -
import sys
import time
import os

class LogClass:
    "Class for generating log messages to the log file and optionally stdout"
    def __init__ (self, Filename=None, DupStdout=0):
        if (Filename != None):
            self._file = open(Filename,"a")
        else:
            self._file = sys.stdout
        self._needprefix = 1
        self._sitename   = None
        if DupStdout:
            self._dupstdout = sys.stdout
        else:
            self._dupstdout = None

    def write(self, Message):
        message = ""
        if (self._needprefix):
            message = message + "[%s-%d" % \
                      (time.strftime("%H:%M:%S",
                                     time.localtime(time.time())),
                       os.getpid())
            if (self._sitename):
                message = message + "-" + self._sitename
            message = message + "] "
            self._needprefix = 0

        message = message + Message
        self._file.write(message)
        self._file.flush()
        if self._dupstdout:
            self._dupstdout.write(message)
            self._dupstdout.flush()
        if (Message[-1] == "\n"):
            self._needprefix = 1

    def log(self, Message):
        self.write(Message)
        self.write("\n")
        if self._dupstdout:
            self._dupstdout.write(Message)
            self._dupstdout.write("\n")

    def setsite(self, SiteName):
        self._sitename = SiteName
	  - - - - - - - - - - - - - - - - - - - - - - - - -


So from a script you would do something like:

    # Create log class to log file
    log = LogClass('filename')

or

    # Create log class to log file and include duplicate to stdout
    log = LogClass('filename',DupStdout=1)

and then reassign sys.stdout (and sys.stderr if you want) to the log:

    sys.stdout = log

and then whenever you want to execute a command, just use:

    Execute('command string')

It's normally nice to save the old sys.stdout/stderr and then restore
them at the end of your script (say perhaps in a top level try/finally
clause) in case your script is included as a module from some other
script.

Hope this gives you some ideas.

--
-- David
-- 
/-----------------------------------------------------------------------\
 \               David Bolen            \   E-mail: db3l at fitlinxx.com  /
  |             FitLinxx, Inc.            \  Phone: (203) 708-5192    |
 /  860 Canal Street, Stamford, CT  06902   \  Fax: (203) 316-5150     \
\-----------------------------------------------------------------------/



More information about the Python-list mailing list