1

I am trying to figure out how to create a little Python script that can take the following parameters:

  1. prompt - string
  2. time to wait as an integer
  3. number of characters before stopping

The last one is the number of characters I can enter before the program stops accepting characters and starts processing the input. I've seen some people use Python's select.select method, but that doesn't account for the 3rd item. I'm leaning towards curses, although I don't know if it supports a timeout which is making me think of threads. Any insights would be great! This will run on Linux with Python 2.6.

4
  • Err, select() can account for the 3rd item. Assuming it will return (continue loop execution) every time there's input, you can count characters and either stop or run another select() call. Commented Aug 29, 2012 at 18:01
  • Really? Sorry. I read the select docs, but I didn't notice that Commented Aug 29, 2012 at 18:15
  • Should I provide you with a simple example or do you want to try yourself? :) Commented Aug 29, 2012 at 18:17
  • Sure. If you could provide an example, that would be great. Commented Aug 29, 2012 at 18:22

2 Answers 2

7

Ok, I have achieved it :D.

#!/usr/bin/env python

import sys
from select import select

def main(argv):
    timeout = 3
    prompt = '> '
    max_chars = 3

    # set raw input mode if relevant
    # it is necessary to make stdin not wait for enter
    try:
        import tty, termios

        prev_flags = termios.tcgetattr(sys.stdin.fileno())
        tty.setraw(sys.stdin.fileno())
    except ImportError:
        prev_flags = None

    buf = ''
    sys.stderr.write(prompt)

    while True: # main loop
        rl, wl, xl = select([sys.stdin], [], [], timeout)
        if rl: # some input
            c = sys.stdin.read(1)
            # you will probably want to add some special key support
            # for example stop on enter:
            if c == '\n':
                break

            buf += c
            # auto-output is disabled as well, so you need to print it
            sys.stderr.write(c)

            # stop if N characters
            if len(buf) >= max_chars:
                break
        else:
            # timeout
            break

    # restore non-raw input
    if prev_flags is not None:
        termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, prev_flags)
    # and print newline
    sys.stderr.write('\n')

    # now buf contains your input
    # ...

if __name__ == "__main__":
    main(sys.argv[1:])

It's fairly incomplete; I just put a few values to test it. A few words of explanation:

  1. You need to switch the tty to 'raw' mode — otherwise you wouldn't be able to get input without it being confirmed by enter key,
  2. in raw mode the typed in characters are no longer output by default — you need to output them yourself if you want user to see what he is typing,
  3. you probably want to handle special keys like enter and backspace — I've added enter handling here. Maybe you could reuse parts of curses for that,
  4. I've assumed the timeout is '3 seconds after last key'. If you want timeout for whole process, I think the easiest way would be to get current time, increase it by timeout (i.e. get end_time), and then pass end_time - current_time in seconds as timeout to select(),
  5. I've made unix-specific imports optional. I don't know whether it will work on Windows correctly, though.
Sign up to request clarification or add additional context in comments.

3 Comments

I had actually forgotten to mention that I needed a way to hide the input from the user, but it looks like your raw mode does that for me, so this works all the way around. Thanks so much!
Glad to help. Although I'm not sure if they are not output in Windows, if that may be relevant.
It doesn't matter. This was for a purely Linux project anyway.
2

Okay. This thread is a few years idle, but I spent a good hour exploring this (and related) threads for a clean solution. In the end, I decided to use the tool that already works well: bash's read. Here is an example asking for a power level. The first line in the try block addresses the question at hand (of course, this only works if you are starting your python script from a bash shell.) Here you have 3 seconds to enter up to 3 characters. (The rest of the try block converts to int and makes sure it is in an expected range.)

import os
try:
  pwr=os.popen('read -t 3 -n 3 -p "enter power level: " power; echo ${power:-0}').read().strip()
  print ''
  pwr=int(pwr)
  if pwr < 0 or pwr > 100: raise ValueError("valid power levels [0..100]")
  print "power=%d"%pwr
except ValueError as e:
  print "Illegal Power:", e.message

1 Comment

I've actually come here trying to find a replacement for bash's buggy read. Finally got it to return " " and "\n", but the issue that I can't seem to fix is the broken timeout. When trying to read single characters that aren't space or enter, the timeout is a MINIMUM. I.E. it always waits for the timeout to finish, even if there's a character available.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.