1

I'm working on a research project and am trying to read what a user is entering into the terminal, stopping the execution while the command is evaluated, and then resuming (or closing) the session.

I found this example in the docs that opens a new session, records everything, and writes it to a file.

After cutting extras I ended up with this:

import os, pty

shell = os.environ.get('SHELL', 'sh')

with open('typescript', 'w') as script:
    def read(fd):
        data = os.read(fd, 1024)
        # Parse on the hash
        cmd = data.decode()
        before, sep, after = cmd.rpartition("#")
        script.write(after)
        return data

    pty.spawn(shell, read)

This has two problems, one is that it's reactive and the other is that it also captures any output from a given command.

When looking for another way I found this answer to a different question:

def wait_key():
''' Wait for a key press on the console and return it. '''
result = None
if os.name == 'nt':
    import msvcrt
    result = msvcrt.getch()
else:
    import termios
    fd = sys.stdin.fileno()

    oldterm = termios.tcgetattr(fd)
    newattr = termios.tcgetattr(fd)
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
    termios.tcsetattr(fd, termios.TCSANOW, newattr)

    try:
        result = sys.stdin.read(1)
    except IOError:
        pass
    finally:
        termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)

return result

I read the terminos docs and think this may be the way to go but I don't understand how to adapt it to act on the ENTER key.

EDIT: What I'm doing is evaluating the commands with a machine learning model and deciding whether or not to allow the command to execute, like preventing someone from sudo rm -rf /. This is really just a proof-of-concept and demonstration.

Is there possibly a better way to go about this?

1 Answers1

0

Trying not to actively block the main thread is a general principle in UI design. If you want the console to be open for the user to interact with at all times, you could be running your code asynchronously in another thread.

Implementing the pausing feature that you want could be done by having a lock or semaphore (see python thread api) that you would lock from the main thread to tell the worker thread to stop. A quick google search suggests that it's pretty simple to implement stoppable threads in python for the case where you want to just terminate the program.

Community
  • 1
  • 1
  • Thank you for answering! I hadn't considered spinning off a different thread. For this program the main goal is to test my back-end and the UI is not important. My primary focus is simply capturing the command `ls -al` (or any other) and making an evaluation. –  Mar 21 '17 at 03:03
  • Okay, like [subprocess?](https://docs.python.org/2/library/subprocess.html). Are you just spitting out the results back to the terminal? – Ian Harvey Mar 21 '17 at 03:32
  • Most of the answers to the second link you provided just say to use `raw_input`. `raw_input` would only return when you press enter so why are you opting for this other method of data entry? – Ian Harvey Mar 21 '17 at 03:54
  • What I'm doing is evaluating the commands with a machine learning model and deciding whether or not to allow the command to execute, like preventing someone from `sudo rm -rf /`. This is really just a proof-of-concept and demonstration. –  Mar 21 '17 at 04:12
  • Okay sounds like it would be really useful. So why have you decided not to just do something like `while(input=raw_input("user: ")):` `#ML code` `if itsAllGood:` `subprocess(input.split())` `else:` `#shutItDown` – Ian Harvey Mar 21 '17 at 04:30
  • My thought was to keep it a real terminal so things like tab completion, git status, and the like still worked. –  Mar 21 '17 at 04:48