如何在Windows的Python中检查stdin是否有可读数据?

8 投票
2 回答
4533 浏览
提问于 2025-04-11 19:25

这段代码

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

在Linux上完全符合我的需求,但在Windows上却不行。

我之前用过kbhit()这个函数,它在msvcrt库里,可以用来检查标准输入(stdin)是否有数据可以读取,但在这种情况下,它总是返回0,表示没有数据。此外,msvcrt.getch()返回的是'\xff',而sys.stdin.read(1)返回的是'\x01'。看起来msvcrt里的函数表现得不太正常。

不幸的是,我不能使用TCP套接字,因为我无法控制与我的Python程序通信的应用程序。

2 个回答

1

我运行了一个线程,这个线程从标准输入(stdin)读取数据,然后把这些数据转发到一个套接字(socket)。这个套接字是可以选择的,所以标准输入也可以选择。

在最近的一个项目中,我需要不断地从一个网络套接字读取数据,然后转发到另一个套接字,直到用户在控制台输入 q。有人可能会想用多线程来解决这个问题,但我不想处理多线程的麻烦。最后,我找到了一种不太明显的解决方案,并且它有效。

我创建了一个线程——没错,是线程,但没有多线程的烦恼——这个线程打开了一个服务器套接字,监听一个随机的端口,然后打开一个客户端套接字连接到这个服务器。服务器套接字接受连接后,调用 sys.stdin.read(),这样就会阻塞,所有从标准输入读取的数据都会写入到这个接受的连接中。因此,客户端套接字就能接收到从标准输入读取的数据。现在,客户端套接字就成了一个可以选择的标准输入,并且是线程安全的。

源代码:

# coding=UTF-8
""" === Windows stdio ===
@author ideawu@163.com
@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()
2

在一些少见的情况下,你可能会关心标准输入(stdin)连接到了什么地方。大多数时候,你并不在意——你只需要读取stdin。

someprocess | python myprogram.py 这种情况下,stdin 连接到了一个管道;也就是前一个程序的输出。你只需从 sys.stdin 读取数据,这样就能从另一个程序那里获取信息。 [不过在Windows系统中,可能还有一个“CON”设备和键盘连接。只不过这不会是 sys.stdin。]

python myprogram.py <someFile 中,stdin 连接到了一个文件。你只需从 sys.stdin 读取数据,这样就能从文件中获取信息。

python myprogram.py 中,stdin 连接到了控制台(在类Unix系统中是 /dev/ttyxx)。你只需从 sys.stdin 读取数据,这样就能从键盘获取输入。

注意上面这三种情况的共同点。你只需从 sys.stdin 读取数据,而你的程序环境已经为你定义了一切。你不需要检查“stdin上是否有数据可以读取”。数据已经可以用了。

有时候,你可能需要一个键盘中断(或者其他一些操作)。顺便说一下,Python将键盘中断作为输入输出的一个重要特性。按下Control-C会在输入输出过程中引发中断(它不会打断一个紧密的循环,但会给一个定期打印的程序发送信号)。

有时你需要找出stdin连接的是哪种类型的文件。

可以使用类似 os.isatty( sys.stdin.fileno() ) 的方法。如果 sys.stdin 是一个TTY设备,说明你的程序连接到了Windows的“CON”(键盘)。如果 sys.stdin 不是TTY设备,那它连接的是一个文件或管道。


示例

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
>>>

如果返回值是 True,说明Python是在没有文件或管道连接的情况下运行的。此时 sys.stdin 是键盘。使用Windows的 kbhit 就没必要了。

如果返回值是 False,说明Python是在有文件或管道连接的情况下运行的。此时 sys.stdin 不是键盘。检查 kbhit 可能就有意义了。此外,我还可以打开 CON: 设备,直接读取键盘输入,这与 sys.stdin 是分开的。


我不太明白你为什么需要“检查stdin上是否有数据可以读取”。如果你能提供更多你想要实现的目标的细节,可能会更有帮助。

撰写回答