Tab auto completion of commands is not working when sys.stdout is overwritten with custom Logger class object in a python program with cmd module

I want to store the commands and their output to be stored in log.txt . So, i am assigning customer Logger class object to sys.stdout with write method overriden to store the output both in file as well as to print in console. The problem here is that when sys.stdout is overriden, ‘tab’ autocompletion of commands in ABC class ( hi,hello,shutdown ) is not working and up and down arrows are also not working for forward and backward history commands.

import cmd
import sys
import readline
LOG_FILE=open("log.txt",'w+')
class Logger():
    def __init__(self):
        self.console=sys.stdout
    def write(self,message):
        self.console.write(message)
        LOG_FILE.write(message)
    def flush(self):
        self.console.flush()

class ABC(cmd.Cmd):
    def __init__(self):
        sys.stdout = Logger()
        readline.set_completer(self.complete)
        readline.parse_and_bind("tab: complete")
    def do_hello(self,args):
        print("hello entered")
    def do_hi(self,args):
        print("Hi entered")
    def do_shutdown(self,args):
        print("shutting down")
        global LOG_FILE
        LOG_FILE.close()
    #sys.exit()
    def emptyline(self):
        pass
    def precmd(self,args):
        LOG_FILE.write(args+"\n")

if __name__ == '__main__':
    ABC().cmdloop()

I expect that when ‘hel’ is typed in command above and then if tab is pressed, it should display ‘hello help’ in the next line. Pressing tab shouldn’t add 4 spaces to the command being typed. Pleas help with working solution in which auto completion of commands with tab,up/down arrow works and the commands and their output should store in a file ‘log.txt’. Please mind that i am running this program in red had linux 7 OS, not windows.

Hi @KITTU,

An excellent example of using cmd can be found in pdb module.

For example, the modified code looks like this:

class ABC(cmd.Cmd):
    def __init__(self, completekey='tab', stdin=None, stdout=None):
        super().__init__(completekey, stdin, stdout)

    def do_hello(self,args):
        print("hello entered")

    def do_hi(self,args):
        print("Hi entered")

    def do_shutdown(self,args):
        print("shutting down")
        global LOG_FILE
        LOG_FILE.close()

    def emptyline(self):
        pass

    def precmd(self, line):
        LOG_FILE.write(line+"\n")
        return line # <-- Don't forget this.

if __name__ == '__main__':
    abc = ABC(stdout=Logger())
    abc.cmdloop()

Hi @komoto48g ,

Thanks for your reply. But, output of commands is not getting written to file.
In above solution, we are passing Logger object as stdout to the cmd.py file. So, the content in cmd.py file( eg. output of help command, doc_string of any method) will only be written to log.txt. content in do_* methods ABC class is not written to log.txt. We want sys.stdout to be overriden with Logger object in current python file. Then only the content in do_* methods will be written to the log.txt file.

Tried the following code for tab auto completion using readline module. Still it’s not working.

import cmd
import sys
import readline
LOG_FILE=open("log.txt",'w+')
class Logger():
    def __init__(self):
        self.console=sys.stdout
    def write(self,message):
        self.console.write(message)
        LOG_FILE.write(message)
    def flush(self):
        self.console.flush()
# sys.stdout = Logger()
class ABC(cmd.Cmd):
    def __init__(self, completekey='tab', stdin=None, stdout=None):
        super().__init__(completekey, stdin, stdout)
        sys.stdout = Logger()
        readline.set_completer(self.complete)
        readline.parse_and_bind("tab: complete")
    def do_hello(self,args):
        print("hello entered")
    def do_hi(self,args):
        print("Hi entered")
    def do_shutdown(self,args):
        print("shutting down")
        global LOG_FILE
        LOG_FILE.close()
        #sys.exit()
    def emptyline(self):
        pass
    def precmd(self,args):
        LOG_FILE.write(args+"\n")
        return args
if __name__ == '__main__':
    abc=ABC(stdout=Logger())
    abc.cmdloop()

I am suspecting that sys module and readline module are interfering with each other and this functionality is not working because of that. Please help to suggest a solution for this.

    abc=ABC(stdout=Logger())

The abc.stdout is a Logger object and the command outputs (e.g. help string) will be put into the log file. If it is not preferable, just remove the argument.

        sys.stdout = Logger()

This line should be removed as it overwrites the system stdout and will prevent the readline completer from working. So, you should use the custom output instead of print function:

The modified code could be as follows:

import cmd
import sys

LOG_FILE=open("log.txt",'w+')

class Logger():
    def __init__(self):
        self.console=sys.__stdout__
    def write(self,message):
        self.console.write(message)
        LOG_FILE.write(message)
    def flush(self):
        self.console.flush()

class ABC(cmd.Cmd):
    def __init__(self, completekey='tab', stdin=None, stdout=None):
        super().__init__(completekey, stdin, stdout)

        self.log = Logger()

## cf. C:/Python310/Lib/pdb.py:447:
    def message(self, msg):
        ## print(msg, file=self.stdout)
        print(msg, file=self.log)

    def do_hello(self,args):
        self.message("hello entered")
    def do_hi(self,args):
        self.message("Hi entered")
    def do_shutdown(self,args):
        self.message("shutting down")
        LOG_FILE.close()
        #sys.exit()
    def emptyline(self):
        pass
    def precmd(self,args):
        LOG_FILE.write(args+"\n")
        return args

if __name__ == '__main__':
    ## abc=ABC(stdout=Logger())
    abc=ABC()
    abc.cmdloop()

I hope I understood your intention correctly.

Hi @komoto48g ,

This is working. Thanks.
One more thing…
Now, i want to redirect sys.stderr along with sys.stdout to the same file and console.
i tried the following class but it’s printing eveything twice.

class Logger():
    def __init__(self):
        self.console=sys.__stdout__
        self.err_console=sys.__stderr__
    def write(self,message):
        self.console.write(message)
        self.err_console.write(message)
        LOG_FILE.write(message)
    def flush(self):
        self.console.flush()

The challenge here is how do we know ‘message’ is from sys.stdout or sys.stderr ?
If we use above class, everything will be printed twice in console. Is there any condition we can try
so that the write method will be similar to following.

if condition : 
    sys.console.write(message)
else:
    sys.err_console.write(message)

Our intension here is to print message based on which method it was called from, either sys.stdout or sys.stderr.

Thanks,