Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out if there is data to be read from stdin on Windows in Python?

Tags:

python

This code

select.select([sys.stdin], [], [], 1.0)

does exactly what I want on Linux, but not in Windows.

I've used kbhit() in msvcrt before to see if data is available on stdin for reading, but in this case it always returns 0. Additionally msvcrt.getch() returns '\xff' whereas sys.stdin.read(1) returns '\x01'. It seems as if the msvcrt functions are not behaving properly.

Unfortunately I can't use TCP sockets as I'm not in control of the application talking my Python program.

like image 886
awatts Avatar asked Nov 27 '08 13:11

awatts


2 Answers

In some rare situations, you might care what stdin is connected to. Mostly, you don't care -- you just read stdin.

In someprocess | python myprogram.py, stdin is connected to a pipe; in this case, the stdout of the previous process. You simply read from sys.stdin and you're reading from the other process. [Note that in Windows, however, there's still (potentially) a "CON" device with a keyboard. It just won't be sys.stdin.]

In python myprogram.py <someFile, stdin is connected to a file. You simply read from sys.stdin and you're reading from the file.

In python myprogram.py, stdin is left connected to the console (/dev/ttyxx in *nix). You simple read from sys.stdin and you're reading from the keyboard.

Note the common theme in the above three cases. You simply read from sys.stdin and your program's environment defines everything for you. You don't check "to see if data is available on stdin for reading". It's already available.

Sometimes, you want a keyboard interrupt (or other shenanigans). Python, BTW, has a keyboard interrupt as a first-class feature of the I/O elements. Control-C raises an interrupt during I/O (it won't break into a tight loop, but it will signal a program that prints periodically.)

Sometimes you need to find out what kind of file stdin is connected to.

Something like os.isatty( sys.stdin.fileno() ) If sys.stdin is a TTY, you're program was left connected to the windows "CON" (the keyboard). If sys.stdin is not a TTY, it's connected to a file or a pipe.


Example

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\slott>python
Python 2.5.2 (r252:60911, Feb 21 2008, 13:11:45) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> import sys
>>> os.isatty( sys.stdin.fileno() )
True
>>>

The value of True tells me Python is running without a file or pipe attached. sys.stdin is the keyboard. Using windows kbhit is needless.

A value of False tells me Python is running with a file or pipe attached. sys.stdin is NOT the keyboard. Checking kbhit might be meaningful. Also, I could open the CON: device and read the keyboard directly, separate from sys.stdin.


I'm not sure why you need "to see if data is available on stdin for reading". It might help to update your question with additional details of what you're trying to accomplish.

like image 95
S.Lott Avatar answered Sep 27 '22 19:09

S.Lott


I run a thread that reads from stdin, then forward the data to a socket. The socket is selectable, so, stdin is selectable too.

In a recent project, I must continuously read from one network socket, forward to another socket, until the user inputs q from the console. One may think to use threading, but I don’t want to deal with multi-thread stuffs. Finally, I found a none-clear solution, and it worked.

I create one thread – YES, thread, but no multi-thread concerns – this thread open a server socket listening on random port, then open a client socket connect to this server. The server socket accept the connection, then call sys.stdin.read() in a block way, all data read from stdin will write to that accepted connection. So client socket receive data reading from stdin. Now, the client socket is a selectable stdin, and it is thread-safe.

Source code:

# coding=UTF-8
""" === Windows stdio ===
@author [email protected]
@link http://www.ideawu.net/
File objects on Windows are not acceptable for select(),
this module creates two sockets: stdio.s_in and stdio.s_out,
as pseudo stdin and stdout.

@example
from stdio import stdio
stdio.write('hello world')
data = stdio.read()
print stdio.STDIN_FILENO
print stdio.STDOUT_FILENO
"""
import thread
import sys, os
import socket

# socket read/write in multiple threads may cause unexpected behaviors
# so use two separated sockets for stdin and stdout

def __sock_stdio():
    def stdin_thread(sock, console):
        """ read data from stdin, and write the data to sock
        """
        try:
            fd = sys.stdin.fileno()
            while True:
                # DO NOT use sys.stdin.read(), it is buffered
                data = os.read(fd, 1024)
                #print 'stdin read: ' + repr(data)
                if not data:
                    break
                while True:
                    nleft = len(data)
                    nleft -= sock.send(data)
                    if nleft == 0:
                        break
        except:
            pass
        #print 'stdin_thread exit'
        sock.close()

    def stdout_thread(sock, console):
        """ read data from sock, and write to stdout
        """
        try:
            fd = sys.stdout.fileno()
            while True:
                data = sock.recv(1024)
                #print 'stdio_sock recv: ' + repr(data)
                if not data:
                    break
                while True:
                    nleft = len(data)
                    nleft -= os.write(fd, data)
                    if nleft == 0:
                        break
        except:
            pass
        #print 'stdin_thread exit'
        sock.close()


    class Console:
        def __init__(self):
            self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.serv.bind(('127.0.0.1', 0))
            self.serv.listen(5)
            port = self.serv.getsockname()[1]

            # data read from stdin will write to this socket
            self.stdin_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.stdin_sock.connect(('127.0.0.1', port))
            self.s_in, addr = self.serv.accept()
            self.STDIN_FILENO = self.s_in.fileno()
            thread.start_new_thread(stdin_thread, (self.stdin_sock, self))

            # data read from this socket will write to stdout
            #self.stdout_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #self.stdout_sock.connect(('127.0.0.1', port))
            #self.s_out, addr = self.serv.accept()
            #self.STDOUT_FILENO = self.s_out.fileno()
            #thread.start_new_thread(stdout_thread, (self.stdout_sock, self))

            self.read_str = '' # read buffer for readline

        def close(self):
            self.s_in.close()
            self.s_out.close()
            self.stdin_sock.close()
            self.stdout_sock.close()
            self.serv.close()

        def write(self, data):
            try:
                return self.s_out.send(data)
            except:
                return -1

        def read(self):
            try:
                data = self.s_in.recv(4096)
            except:
                return ''
            ret = self.read_str + data
            self.read_str = ''
            return ret

        def readline(self):
            while True:
                try:
                    data = self.s_in.recv(4096)
                except:
                    return ''
                if not data:
                    return ''
                pos = data.find('\n')
                if pos == -1:
                    self.read_str += data
                else:
                    left = data[0 : pos + 1]
                    right = data[pos + 1 : ]
                    ret = self.read_str + left
                    self.read_str = right
                    return ret

    stdio = Console()
    return stdio

def __os_stdio():
    class Console:
        def __init__(self):
            self.STDIN_FILENO = sys.stdin.fileno()
            self.STDOUT_FILENO = sys.stdout.fileno()

        def close(self):
            pass

        def write(self, data):
            try:
                return os.write(self.STDOUT_FILENO, data)
            except:
                return -1

        def read(self):
            try:
                return os.read(self.STDIN_FILENO, 4096)
            except:
                return ''

        def readline(self):
            try:
                return sys.stdin.readline()
            except:
                return ''

    stdio = Console()
    return stdio

if os.name == 'posix':
    stdio = __os_stdio()
else:
    stdio = __sock_stdio()
like image 44
ideawu Avatar answered Sep 27 '22 19:09

ideawu