【问题标题】:How do you read single chars from stdin in Python你如何在 Python 中从标准输入读取单个字符
【发布时间】:2018-10-18 18:04:54
【问题描述】:

关于如何在 python 中读取 stdin 有很多很好的答案,但我找不到任何关于读取单个字符而不是整行的内容。让我解释一下:

我需要读取 arduino 在串行端口上发送的信息,该信息被转发到标准输入。此信息被处理并存储在文本文件中。我在 arduino 上编写了程序,所以我可以更改信息的发送方式。计划是发送带有开头(<)和结尾字符(>)的信息,所以它看起来像这样:<height:2342>

还会写很多不相关的数据,所以我决定使用上面的表格,这样python脚本就可以检测到相关信息并捕获。

我的 python 脚本会单独检查每个字符的起始字符 <,最好是在输入时,然后捕获信息,直到收到 >。我尝试使用inputchar = sys.stdin.read(1) 获取输入。但这里的问题是,它永远从stdin 读取,直到捕获换行符(\n),然后返回输入的第一个字符。

我希望这个函数在发送到stdin 后立即返回输入的字符,而不是等待换行符。我如何做到这一点?

平台:Raspberry Pi Zero W、Raspbian Jessy、Python 2.7


我知道我可以使用 inputline = sys.stdin.readline() 并更改 Arduino 程序以在信息后发送换行符。然后分析整行(可能很长)并提取信息。但我不认为这是一种干净的方式。


串口更新:遗憾的是,我无法直接从 python 访问串口,因为有第二个 python 脚本必须写入串口。由于只有一个端口可以访问,解决方法是将串口重定向到stdinstdout。看我的问题Access one Serial Port with two Python Scripts

【问题讨论】:

  • 您可能会更幸运地直接从 Python 打开串行端口(例如,这样您就可以控制缓冲),而不是将端口重定向到标准输入。
  • 如果你使用的是pyserial,我很确定可以read single bytes from the serial port directly
  • 这个库只处理那个 --> pypi.org/project/getch
  • .read() 在管道上(我假设您的重定向程序使用管道)将在数据可用时立即返回。它不会等待换行符。我最好的猜测是重定向串行端口的程序正在缓冲其输出。也许它需要在它的输出管道上调用.write() 之后调用.flush()

标签: python stdin


【解决方案1】:

这是因为您的terminal is in cooked mode。您可以使用例如tty.setcbreakcurses.cbreak 禁用行缓冲。这是一个 Unix 的东西,不是特定于 Python 的。

例子:

import sys, tty
tty.setcbreak(sys.stdin.fileno())

请注意,这还有其他效果,例如禁用回显,并且会在您的程序退出时持续存在。通常,使用更高级的接口(例如 curses 上下文管理器)来处理键解析(例如,箭头键,发送转义序列)和清理。

Python 之外的主要命令行工具是stty

【讨论】:

    【解决方案2】:

    sys.stdin.read(1) 是正确的做法。

    在您回车之前,sys.stdin 没有输入。该行缓冲是在您的程序之外执行的,如果您想使用sys.stdin,您将无能为力。

    【讨论】:

      【解决方案3】:

      另一种方法是将 TTY 设置为原始模式。我想计算按键的持续时间并且需要这个,这样我就不必按回车键了:

      #!/usr/bin/env python3
      
      import datetime
      import sys
      import tty
      import termios
      
      
      # read a single character on a unix system
      # https://exceptionshub.com/python-read-a-single-character-from-the-user.html
      def read_unix(count: int):
          fd = sys.stdin.fileno()
      
          old_settings = termios.tcgetattr(fd)
          try:
              tty.setraw(fd)
              ch = sys.stdin.read(count)
          finally:
              termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
      
          return ch
      
      
      while True:
          start = datetime.datetime.now()
          char = read_unix(1)
          end = datetime.datetime.now()
      
          delta = end - start
          print(f'char={char}, delta={delta}')
      

      【讨论】:

        【解决方案4】:

        我无法重现您所描述的行为...

        在我的 Raspbian 上执行 - 在 RPI2 上:

        $ cat a.py
        #!/usr/bin/env python2
        import sys
        
        while True:
            print "Got", ord(sys.stdin.read(1))
        
        
        $ echo -n "Test"  | hexdump -C
        00000000  54 65 73 74                                       |Test|
        00000004
        
        $ # No newline in the data sent by echo, as shown by hexdump
        
        $ echo -n "Test"  | python2 a.py
        Got 84
        Got 101
        Got 115
        Got 116
        Got
        Traceback (most recent call last):
          File "a.py", line 5, in <module>
            print "Got", ord(sys.stdin.read(1))
        TypeError: ord() expected a character, but string of length 0 found
        

        基本上,sys.stdin.read(1) 会返回从 stdin 发送的任何字符,而无需等待任何换行符。

        【讨论】:

        • 我已经在 Ubuntu 上复制了它。在 CLI 中手动输入“test” - 因此没有任何内容发送到标准输入。
        • CLI 与从串行线路发送数据的管道不同——这是 OP 所要求的。
        • 我不确定管道是否可以在熟模式下工作 - 我希望对这个问题负责。
        • 管道不经过终端层——这就是pseutoterminals存在的原因,而openssh有一个-t switch
        猜你喜欢
        • 1970-01-01
        • 2010-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-18
        相关资源
        最近更新 更多