【问题标题】:How to use `input()` after reading a file from `stdin`?从`stdin`读取文件后如何使用`input()`?
【发布时间】:2021-05-14 11:51:56
【问题描述】:

上下文

我想要一个简单的脚本,在 Unix/Linux 上选择多个管道输入中的 1 个,而不会出现 EOF when reading a line 错误。

它试图:

  1. 接受多行管道文本
  2. 等待用户选择一个选项
  3. 将该选项打印到标准输出

所需用途:

$ printf "A\nB" | ./select.py | awk '{print "OUTPUT WAS: " $0}'
Select 0-1:
  0) A
  1) B
> 1
OUTPUT WAS: B

最后的awk '{print "[OUTPUT WAS] " $0}' 只是为了表明唯一的标准输出应该是选择。

目前的做法:

#!/bin/python3
import sys
from collections import OrderedDict

def print_options(options):
    """print the user's options"""
    print(f"Select 0-{len(options)-1}:", file=sys.stderr)
    for n, option in options.items():
        print(f"  {n}) {option}", file=sys.stderr)

def main():
    # options are stored in an ordered dictionary to make order consistent
    options = OrderedDict()
    # read in the possible options one line at a time
    for n, line in enumerate(sys.stdin):
        options[n] = line.rstrip('\n')
        
    valid_selection = False
    # loop until we get a valid selection
    while not valid_selection:
        print_options(options)
        try:
            print('> ', end='', file=sys.stderr)
            selection = int(input()) # <- doesn't block like it should
            # use the selection to extract the output that will be printed
            output = options[selection]
            valid_selection = True
        except Exception as e:
            print(f"Invalid selection. {e}", file=sys.stderr)
                
    print(output)

if __name__ == '__main__':
    main()

错误:

脚本陷入无限循环打印:

...
> Invalid selection. EOF when reading a line
Select 0-1:
  0) A
  1) B
> Invalid selection. EOF when reading a line
Select 0-1:
  0) A
  1) B
> Invalid selection. EOF when reading a line
...

重现错误的最小脚本:

#!/bin/python3
import sys

options = []
# read in the possible options one line at a time
for line in sys.stdin:
    options.append(line.rstrip('\n'))
    
user_input = input('> ')
            
print(user_input)

这会抛出:

EOFError: EOF when reading a line

当我想查看和输入时:

$ printf "text" | ./testscript.py
> sometext
sometext

所需的解决方案:

我认为这是因为标准输入已达到 EOF。但我的问题是如何重置/消除 EOF 的影响,以便 input() 再次像往常一样阻塞并等待用户。

简而言之:stdin读取文件后如何使用input()

如果这是不可能的,as this answer implies,有什么优雅的解决方案可以得到与我在这个问题开头描述的类似的行为?我对非 python 解决方案持开放态度(例如 bash|zsh、rust、awk、perl)。

【问题讨论】:

  • @user 这不正确。如果您检查 while 循环,它的节会显示 while not valid_selection: 检查最小脚本以重现我正在尝试修复的错误。编辑:用户删除了他们的评论,但我将把它留给其他人。

标签: python python-3.x shell scripting


【解决方案1】:

我可以回答您有关 Linux/Unix 操作系统的问题。抱歉,我不使用 Windows。

我在您的示例代码中添加了两行,见下文。

特殊设备/dev/tty 已连接到您的终端。除非重定向,否则它是您的标准输入/输出。基本上你想恢复你的标准输入连接到你的终端的状态。在底层,它使用文件描述符 0。close 关闭它,open 获取第一个空闲的,在这种情况下为 0。

import sys 

options = []
# read in the possible options one line at a time
for line in sys.stdin:
    options.append(line.rstrip('\n'))

# restore input from the terminal   
sys.stdin.close()
sys.stdin=open('/dev/tty')

user_input = input('> ')
                 
print(user_input)

【讨论】:

  • 在极少数情况下 /dev/tty 不是控制终端,那么os.ctermid() 可能会返回正确的终端。
  • @tdelaney 你能举个例子吗?一个进程可能没有控制终端,但如果有控制终端,/dev/tty 怎么会出错?
  • @VPID 许多系统定义“/dev/tty”来给控制终端起别名。在我的笔记本电脑上,os.ctermid() 返回“/dev/tty”,而os.ttyname(1) 是“/dev/pty/X”。 linux ctermid man page 很腼腆,说它可能是/dev/tty。在某些系统上,它始终定义为“/dev/tty”,但不一定非要如此。
  • @tdelaney 是 os.ctermid() 是 /dev/tty 的更好替代品吗?
  • @Connor - 好问题!我想是的,但我在许多环境中工作过,有时使用多个会话组标题和 tty。闲逛,freebsd 似乎规范地将 /dev/tty 定义为正确的。 GNU 说 在 GNU C 库中,它总是返回相同的字符串:“/dev/tty”,但 linux 文档通常没有那么提交。所有 linux 或 linuxy 的东西都符合吗?我不知道。在过去,ctermid 是实现此目的的 方式。现在它可能更加退化,但对我来说,调用ctermid 并使其成为操作系统的问题仍然是有意义的。
【解决方案2】:

VPfB's answer 是我需要的,这是最终脚本,以防有人想使用它。

用法

$ printf "A\nB\nC" | ./select.py | awk '{print "Selected: " $0}'
Select 0-1:
  0) A
  1) B
  2) C
> 2 <- your input
Selected: C

完整解决方案

#!/bin/python3
"""
A simple script to allow selecting 1 of multiple piped inputs. 

Usage: 
printf "A\nB" | ./choose.py

If your input is space separated, make sure to convert with: 
printf "A B" | sed 's+ +\n+g' | ./choose.py

Source: https://stackoverflow.com/a/66143667/7872793
"""
import sys
from collections import OrderedDict

def print_options(options):
    """print the user's options"""
    print(f"Select 0-{len(options)-1}:", file=sys.stderr)
    for n, option in options.items():
        print(f"  {n}) {option}", file=sys.stderr)

def select_loop(options):
    valid_selection = False
    # loop until we get a valid selection
    while not valid_selection:
        print_options(options)
        try:
            print('> ', end='', file=sys.stderr)
            selection = int(input())
            # use the selection to extract the output that will be printed
            output = options[selection]
            valid_selection = True
        except Exception as e:
            print(f"Invalid selection. {e}", file=sys.stderr)
            
    return output

def main():
    # options are stored in an ordered dictionary to fix iteration output
    options = OrderedDict()
    # read in the possible options one line at a time
    for n, line in enumerate(sys.stdin):
        options[n] = line.rstrip('\n')
        
    # restore input from the terminal
    sys.stdin.close()
    sys.stdin=open('/dev/tty')
        
    # if only one option is given, use it immediately
    output = options[0] if len(options) == 1 else select_loop(options)
    print(output)

if __name__ == '__main__':
    main()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-27
    • 2010-12-17
    • 1970-01-01
    • 2016-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多