为了让程序在终端/后台运行,我是否需要在 raw_input 之前放置一个 if 语句来检查它是否在后台,或者我是否遗漏了其他有用的语句?
在某种程度上,这可能有效(我假设您在 *nix 上运行它),但是如果用户要将进程发送回后台(即使用 Ctrl 暂停它Z 然后在后台使用%&) 恢复它,而raw_input 正在等待用户输入,然后stdin 上的读取将被阻止,因为它在后台,从而导致内核停止进程,因为这就是 stdio 的工作方式。如果这是可以接受的(基本上用户必须在暂停进程之前按回车键),您可以简单地这样做:
import os
while True:
userInput = ""
if os.getpgrp() == os.tcgetpgrp(sys.stdout.fileno()):
userInput = raw_input("")
time.sleep(1)
os.getpgrp 所做的是返回当前 os 组的 id,然后os.tcgetpgrp 获取与该进程的 stdout 关联的进程组,如果它们匹配,则表示该进程当前在前台,这意味着您可能可以在不阻塞线程的情况下调用raw_input。
另一个问题提出了类似的问题,我有一个更长的解释:Freeze stdin when in the background, unfreeze it when in the foreground。
更好的方法是将它与 select.poll 结合起来,并从标准 I/O 中单独处理交互式 I/O(直接使用 /dev/tty),因为您不希望 stdin/stdout 重定向被“污染”那。这是包含这两个想法的更完整版本:
tty_in = open('/dev/tty', 'r')
tty_out = open('/dev/tty', 'w')
fn = tty_in.fileno()
poll = select.poll()
poll.register(fn, select.POLLIN)
while True:
if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10): # 10 ms
# poll should only return if the input buffer is filled,
# which is triggered when a user enters a complete line,
# which lets the following readline call to not block on
# a lack of input.
userInput = tty_in.readline()
# This should allow us to exit out
if userInput.strip() == "quit":
sys.exit()
仍然需要后台/前台检测,因为进程没有完全脱离外壳(因为它可以被带回前台)因此poll 将返回 tty 的fileno 如果发送任何输入进入shell,如果这触发了readline,它将停止进程。
这种解决方案的优点是不需要用户在raw_input捕获并阻止stdin停止进程之前快速暂停任务以将其发送回后台(因为poll检查是否有要读取的输入),并允许正确的标准输入/标准输出重定向(因为所有交互式输入都通过/dev/tty 处理),因此用户可以执行以下操作:
$ python script.py < script.py 2> stderr
input stream length: 2116
在下面的完整示例中,它还向用户提供提示,即每当发送命令或每当进程返回前台时都会显示>,并将整个内容包装在main 函数中,并修改了第二个线程以在 stderr 上吐出东西:
import os
import select
import sys
import time
from threading import Thread
def threadOne():
while True:
print("Thread 1")
time.sleep(1)
def threadTwo():
while True:
# python 2 print does not support file argument like python 3,
# so writing to sys.stderr directly to simulate error message.
sys.stderr.write("Thread 2\n")
time.sleep(1)
# Run the threads in the background
threadOne = Thread(target = threadOne)
threadOne.daemon = True
threadTwo = Thread(target = threadTwo)
threadTwo.daemon = True
def main():
threadOne.start()
threadTwo.start()
tty_in = open('/dev/tty', 'r')
tty_out = open('/dev/tty', 'w')
fn = tty_in.fileno()
poll = select.poll()
poll.register(fn, select.POLLIN)
userInput = ""
chars = []
prompt = True
while True:
if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10): # 10 ms
# poll should only return if the input buffer is filled,
# which is triggered when a user enters a complete line,
# which lets the following readline call to not block on
# a lack of input.
userInput = tty_in.readline()
# This should allow us to exit out
if userInput.strip() == "quit":
sys.exit()
# alternatively an empty string from Ctrl-D could be the
# other exit method.
else:
tty_out.write("user input: %s\n" % userInput)
prompt = True
elif not os.getpgrp() == os.tcgetpgrp(fn):
time.sleep(0.1)
if os.getpgrp() == os.tcgetpgrp(fn):
# back to foreground, print a prompt:
prompt = True
if prompt:
tty_out.write('> ')
tty_out.flush()
prompt = False
if __name__ == '__main__':
try:
# Uncomment if you are expecting stdin
# print('input stream length: %d ' % len(sys.stdin.read()))
main()
except KeyboardInterrupt:
print("Forcibly interrupted. Quitting")
sys.exit() # maybe with an error code
这是一个有趣的练习;如果我可以说,这是一个相当好的和有趣的问题。
最后一点:这不是跨平台的,它不会在 Windows 上运行,因为它没有 select.poll 和 /dev/tty。