【问题标题】:Pipe input to script and later get input from user管道输入到脚本,然后从用户那里获取输入
【发布时间】:2011-08-21 21:34:33
【问题描述】:

假设我想通过管道将输入输入到 Python 程序,然后在命令行上从用户那里获取输入。

echo http://example.com/image.jpg | python solve_captcha.py

solve_captcha.py的内容是:

import sys 
image_url = sys.stdin.readline()

# Download and open the captcha...

captcha = raw_input("Solve this captcha:")
# do some processing...

以上将触发EOFError: EOF when reading a line 错误。

我还尝试添加sys.stdin.close() 行,它提示ValueError: I/O operation on closed file

您能否将信息传递给stdin,然后再从用户那里获得输入?

注意:这是一个精简的简化示例 - 请不要回答说“你为什么要在第一种情况下这样做”,这真的很令人沮丧。我只是想知道您是否可以将信息传递给stdin,然后再提示用户输入。

【问题讨论】:

    标签: python bash stdin


    【解决方案1】:

    这个问题没有通用的解决方案。最好的资源似乎是this mailing list thread

    基本上,通过管道将程序的stdin 连接到该管道,而不是终端。

    邮件列表线程有几个相对simple solutions for *nix:

    打开 /dev/tty 替换 sys.stdin:

    sys.stdin = open('/dev/tty')
    a = raw_input('Prompt: ')
    

    在您运行脚本时将标准输入重定向到另一个文件句柄,并从中读取:

    sys.stdin = os.fdopen(3)
    a = raw_input('Prompt: ')
    $ (echo -n test | ./x.py) 3<&0
    

    以及suggestion to use curses。请注意,邮件列表线程是古老的,因此您可能需要修改您选择的解决方案。

    【讨论】:

    • 感谢 agf。阅读后是否必须关闭 dev/tty?
    • 不,我不这么认为,但我不确定。让它敞开会有什么危害?
    • 我不知道...我只是想尝试防御性地编程并在打开它们后关闭它们。
    • curses 建议不起作用。它仍然会尝试从标准输入读取。
    • 请注意,如果您使用的是 Windows,则可以改为打开 CON:。看到这个答案:*.com/a/61962566/10625876
    【解决方案2】:

    bash 有进程替换,它会创建一个 FIFO,您可以将其视为文件,而不是

    echo http://example.com/image.jpg | python solve_captcha.py
    

    你可以使用

    python solve_capcha.py <(echo http://example.com/image.jpg)
    

    您可以将 solve_capcha.py 的第一个参数作为文件打开,我认为 sys.stdin 仍可用于从键盘读取输入。

    编辑:如果你不使用 bash,你可以使用 mkfifo 在任何 POSIX 系统上完成同样的事情:

    mkfifo my_pipe
    echo "http://example.com/image.jpg" > my_pipe
    python solve_captcha.py my_pipe
    

    FIFO 将阻塞(等待而不关闭)输出。

    【讨论】:

    • 这是否适用于来自 find 和 xargs 的多个输入参数,即python smth.py &lt;(find ... -print0 | xargs -0)
    • 似乎对我不起作用。输入无法访问 python 脚本
    【解决方案3】:

    您可以关闭标准输入,然后重新打开它以读取用户输入。

    import sys, os
    
    data = sys.stdin.readline()
    print 'Input:', data
    sys.stdin.close()
    sys.stdin = os.fdopen(1)
    captcha = raw_input("Solve this captcha:")
    print 'Captcha', captcha
    

    【讨论】:

    【解决方案4】:

    这是为了模仿raw_input(),因为我和你有同样的问题。整个stdinclear 丑只是为了让它看起来漂亮。这样您就可以看到您正在输入的内容。

    def getInputFromKeyPress(promptStr=""):
    
        if(len(promptStr)>0):
            print promptStr
        """
        Gets input from keypress until enter is pressed
        """
    
        def clear(currStr):
            beeString, clr="",""
    
            for i in range(0,len(currStr)):
                clr=clr+" "
                beeString=beeString+"\b"
    
            stdout.write(beeString)
            stdout.write(clr)
            stdout.write(beeString)
    
    
        from msvcrt import kbhit, getch
        from sys import stdout
        resultString, userInput="", ""
    
        while(userInput!=13):
            if (kbhit()):
                charG=getch()
                userInput= ord(charG)
    
                if(userInput==8):#backspace
                    resultString=resultString[:-1]
                    clear(resultString)
    
    
                elif(userInput!=13):
                    resultString="".join([resultString,charG])
    
                clear(resultString)
                stdout.write(resultString)
    
                if(userInput==13):
                    clear(resultString)
    
        #print "\nResult:",resultString
    
        return resultString.strip()
    

    【讨论】:

      【解决方案5】:

      我更新了@Bob 的答案以支持删除、ctrl + [left, right, home, end] 按键并简化了标准输出的清除和重写。

      def keypress_input(prompt_str=""):
          """
          Gets input from keypress using `msvcrt` until enter is pressed.
          Tries to emulate raw_input() so that it can be used with piping.
          :param prompt_str: optional string to print before getting input
          :type prompt_str: str
          """
          from re import finditer
          from msvcrt import getch
          from sys import stdout
      
          # print even if empty to create new line so that previous line won't be overwritten if it exists
          print prompt_str
      
          user_input = ""
          curr_chars = []
          cursor_pos = 0
      
          backspace = 8
          enter = 13
      
          escape_code = 224
          delete = 83
          left = 75
          right = 77
          home = 71
          end = 79
          ctrl_left = 115
          ctrl_right = 116
          ctrl_home = 119
          ctrl_end = 117
      
          while user_input != enter:
              char_g = getch()
              user_input = ord(char_g)
              prev_len = len(curr_chars)  # track length for clearing stdout since length of curr_chars might change
      
              if user_input == backspace:
                  if len(curr_chars) > 0 and cursor_pos <= len(curr_chars):
                      cursor_pos -= 1
                      curr_chars.pop(cursor_pos)
      
              elif user_input == escape_code:
                  user_input = ord(getch())
      
                  if user_input == delete:
                      curr_chars.pop(cursor_pos)
      
                  elif user_input == left:
                      cursor_pos -= 1
      
                  elif user_input == right:
                      if cursor_pos < len(curr_chars):
                          cursor_pos += 1
      
                  elif user_input == home:
                      cursor_pos = 0
      
                  elif user_input == end:
                      cursor_pos = len(curr_chars)
      
                  elif user_input == ctrl_home:
                      curr_chars = curr_chars[cursor_pos:]
                      cursor_pos = 0
      
                  elif user_input == ctrl_end:
                      curr_chars = curr_chars[:cursor_pos]
                      cursor_pos = len(curr_chars)
      
                  elif user_input == ctrl_left:
                      try:
                          chars_left_of_cursor = "".join(curr_chars[:cursor_pos])
                          left_closest_space_char_index = [m.span()[0] for m in finditer(" \w", chars_left_of_cursor)][-1]
                          pos_diff = cursor_pos - left_closest_space_char_index - 1
                          cursor_pos -= pos_diff
                      except IndexError:
                          cursor_pos = 0
      
                  elif user_input == ctrl_right:
                      try:
                          chars_right_of_cursor = "".join(curr_chars[cursor_pos + 1:])
                          right_closest_space_char_index = [m.span()[0] for m in finditer(" \w", chars_right_of_cursor)][0]
                          cursor_pos += right_closest_space_char_index + 2
                      except IndexError:
                          cursor_pos = len(curr_chars) - 1
      
              elif user_input != enter:
                  if cursor_pos > len(curr_chars) - 1:
                      curr_chars.append(char_g)
                  else:
                      curr_chars.insert(cursor_pos, char_g)
                  cursor_pos += 1
      
              # clear entire line, write contents of curr_chars, reposition cursor
              stdout.write("\r" + prev_len * " " + "\r")
              stdout.write("".join(curr_chars))
              pos_diff = len(curr_chars) - cursor_pos
              stdout.write("\b" * pos_diff)
      
          stdout.write("\r" + len(curr_chars) * " " + "\r")
          stdout.write("".join(curr_chars) + "\n")
      
          return "".join(curr_chars)
      

      【讨论】: