1

Setup

I've been playing with a script to pipe tail -f into and highlight certain keywords. Not a big project but something I find useful.

Right now the main loop is basically:

...
line = True
while line:
    line = sys.stdin.readline()
    sys.stdout.write(highlight(line))
...

I'd like to listen for certain keypresses during this loop to print a marker line in the log. The method I've found that looks like it would work for getting a keypress is described on http://code.activestate.com/recipes/134892/ but it reads a single character at a time from stdin which won't work when my main loop is reading entire lines from it.

Question

Is there a way in Python to listen for keypresses while also reading piped input?

I have wrapped the main function in a try block which excepts KeyboardInterrupt and prints a nice little goodbye message instead of an error stack trace. Is there a way to piggyback on this behavior with another key?

I'd rather not use a beefy (compared to my small script) module like pygame or tkinter and be forced to use their main loop just to get access to keypresses. (also I'm not familiar with how either would behave when receiving piped input)

Community
  • 1
  • 1
rennat
  • 2,529
  • 3
  • 26
  • 30

1 Answers1

2

The simplest solution would be to avoid using a pipe from tail at all. Stick to using your stdin for user input, and instead write your tool so that it can open and follow the file you want to read from natively without using tail at all. tail's not a terribly complex tool if you just want to reimplement that part of it.

Alternately, you could create a named pipe which will allow you to pipe tail into it, and then your program can read it like a file and still have access to its own stdin.

If neither of these are options, I think going with a separate library that directly polls the keyboard might be your best bet. For the record, pygame doesn't actually have a "main loop", it has an event pump function that you should call as often as possible (in your main loop) but if you don't, it's not going to die. It can buffer lots and lots of events between pumps, and you can set filters on it to discard all the events you don't care about and only keep the ones you do (keypresses). The main problem with pygame would be that it's a pretty heavyweight library to be requiring for such a simple tool, so I understand your hesitation to go that route.

Oh, and as a final option (least desirable, in my opinion) you could abuse the keyboard interrupt mechanic to create "Emacs-like" key chords. Like, press Ctrl-C, then Ctrl-(whatever). The Ctrl-C invokes the KeyboardInterrupt, then in the handler for that you read the next character off stdin and do something with that. Once you're done, return to your loop. But that's ugly and hacky and I'm not even sure if it would work reliably.

cecilkorik
  • 1,411
  • 12
  • 17
  • I'd never seen named pipes before I'll look into that. Also thanks for the clarification on pygame. – rennat Nov 01 '11 at 06:37
  • The reason I wanted to go with piped input is so you can use it to highlight anything quickly. I've actually already got it set up to execute any command you pass into it after `--` with `popen` then use it's output. I was hoping to allow piped input as well for versatility. – rennat Nov 01 '11 at 06:43
  • You might want to take a look at [this discussion](http://stackoverflow.com/questions/292095/polling-the-keyboard-in-python). The `curses` module seems like it might be able to do keyboard access with a significantly smaller footprint than pygame. – cecilkorik Nov 01 '11 at 06:46