我想在后台运行一个脚本:
nohup script.py > out 2> err < /dev/null &
脚本(Python 3.4
)在某些地方做:
answer = input('? ')
(它有一个菜单在其中一个线程中运行)
和nohup调用崩溃:
EOFError: EOF when reading a line
我想是因为stdin
的/dev/null
重定向。如果我在没有stdin
重定向的情况下运行:
nohup script.py > out 2> err &
它崩溃了:
OSError: [Errno 9] Bad file descriptor
如果我运行它:
script.py > out 2> err
它可以工作,但是阻塞了我的终端(它在前台)
如果我运行它:
script.py > out 2> err &
它在后台运行,但是一旦到达input
调用它就会停止。
我想要的是:
- 能够重定向
stdout
和stderr
到文件系统 - 可以把脚本放到后台
- 能够将其移动到前台并与菜单正常交互(因此
stdin
必须以某种方式启用)。stdout
和stderr
仍然会被重定向到文件系统,但stdin
会正常工作。 - 脚本必须在后台和前台运行良好(当然,菜单不能在后台工作,因为
stdin
是"冻结")
基本上,我想要的是,当它在背景中,stdin
是一种"冻结",每当它来到前景,它再次正常工作。
这可能吗?解决方案不需要涉及nohup
您想要的交互式菜单(以及python下input
在EOF
上的工作方式和失败方式)意味着在调用程序时无法安全地将文件作为stdin
传递。这意味着您唯一的选择是像这样调用它:
$ script.py > out 2> err &
作为演示,下面是我的脚本:
from time import sleep
import sys
c = 0
while True:
sleep(0.001)
c += 1
if c % 1000 == 0:
print(c, flush=True)
if c % 2000 == 0:
print(c, file=sys.stderr, flush=True)
if c % 10000 == 0:
answer = input('? ')
print('The answer is %s' % answer, flush=True)
本质上,它每隔一秒就会写入stdout
,每隔两秒就会写入stderr
,最后,每隔十秒它会等待输入。如果我要运行这个并等待一秒钟多一点(允许磁盘刷新),然后将它们链接在一起,如下所示:
$ python script.py > out 2> err & sleep 2.5; cat out err
[1] 32123
1000
2000
2000
$
等待至少10秒,再尝试cat out err
:
$ cat out err
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
? 2000
4000
6000
8000
10000
[1]+ Stopped python script.py > out 2> err
$
注意,input
生成的提示符也被写入标准输出,程序有效地继续运行,直到它期望stdin
为它提供数据的地方。您只需通过%
将进程带回前台,并开始向其提供所需的数据,然后使用^Z
暂停(CtrlZ),并使用%&
在后台再次运行它。例子:
$ %
python script.py > out 2> err
Test input
^Z
[1]+ Stopped python script.py > out 2> err
$ %&
[1]+ python script.py > out 2> err &
$
再等待10秒后,再次输入cat out
:
$ cat out
1000
...
10000
? The answer is Test input
11000
...
20000
?
[1]+ Stopped python script.py > out 2> err
$
这本质上是一个基本的速成班,关于标准进程如何在前台和后台工作,如果代码正确处理标准IO,事情就会像预期的那样工作。
最后,你不可能真的两全其美。如果应用程序期望stdin,而没有提供,那么明确的选项是失败。如果提供了一个,但是应用程序被发送到后台并继续运行,它将是Stopped
,因为它期望进一步的输入。如果这个停止的行为是不需要的,应用程序有错误,没有什么可以做的,只有改变应用程序,当/dev/null
作为stdin
执行时遇到EOF
时不会导致错误。如果你想保持stdin
不变,当应用程序在后台能够以某种方式保持运行时,你不能使用input
函数,因为当stdin
为空时它会阻塞(导致进程停止)。
现在你已经通过下面的评论澄清了你的"交互式提示符"是在一个线程中运行的,因为使用input
直接从stdin
读取,你似乎不愿意修改你的程序(你要求一般情况下),但期望一个实用程序为你做这件事,简单的解决方案是在tmux
或screen
会话中执行此操作,因为它们完全实现了独立于启动的任何控制台的伪tty(因此您可以断开连接并将会话发送到后台,或启动其他虚拟会话,请参阅手册页),这将提供程序期望的stdio。
最后,如果你真的想让你的应用程序支持这个本机,你不能简单地使用input
,但你应该检查input
是否可以安全地被调用(即也许利用select
),或者通过检查进程是否当前在前台或后台(一个例子,你可以开始工作是如何检测python脚本是否作为后台进程运行,尽管你可能想检查使用sys.stdin
,),以确定是否可以安全地调用input
(然而,如果用户在输入时挂起任务,它仍然会在input
等待时挂起),或者使用unix套接字进行通信。