Freeze stdin when in the background, unfreeze it w

2019-07-15 06:02发布

I am trying to run a sript in the background:

nohup script.py > out 2> err < /dev/null &

The script (Python 3.4) does at some point:

answer = input('? ')

(it has a menu running in one of the threads)

And the nohup call is crashing with:

EOFError: EOF when reading a line

Because of the /dev/null redirection of stdin I imagine. If I run it without stdin redirection:

nohup script.py > out 2> err &

It crashes with:

OSError: [Errno 9] Bad file descriptor

If I run it with:

script.py > out 2> err

It works, but blocks my terminal (it is in the foreground)

If I run it with:

script.py > out 2> err &

It runs in the background alright, but it gets stopped as soon as the input call is reached.

What I would like is:

  • be able to redirect stdout and stderr to the filesystem
  • be able to put the script in the background
  • be able to move if to the foreground and interact with the menu normally (so stdin must be enabled somehow). stdout and stderr would still be redirected to the filesystem, but stdin would behave normally.
  • the script must run fine in the background and in the foreground (of course, the menu is not working in the background, because stdin is "frozen")

Basically, what I would like is that when it is in the background, stdin is kind of "frozen", and whenever it comes to the foreground it works again normally.

Is this possible? The solution does not need to involve nohup

1条回答
爷的心禁止访问
2楼-- · 2019-07-15 06:38

What you want (and how input works and fails on EOF under python) with an interactive menu means that you cannot safely pass a file as stdin when invoking your program. This means your only option is to invoke this like so:

$ script.py > out 2> err &

As a demonstration, this is my script:

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)

Essentially, every second it will write to stdout, every two seconds it will write to stderr, and lastly, every ten seconds it will wait for input. If I were to run this and wait a bit over a seconds (to allow disk flush), and chain this together, like so:

$ python script.py > out 2> err & sleep 2.5; cat out err
[1] 32123
1000
2000
2000
$ 

Wait at least 10 seconds and try cat out err again:

$ 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
$ 

Note that the prompt generated by input is also written to stdout, and the program effectively continued running up to where it is expecting stdin to give it data. You simply have to bring the process back into foreground by %, and start feeding it the required data, then suspend with ^Z (CtrlZ) and keep it running again in the background with %&. Example:

$ %
python script.py > out 2> err
Test input
^Z
[1]+  Stopped                 python script.py > out 2> err
$ %&
[1]+ python script.py > out 2> err &
$

Now cat out again, after waiting another ten seconds:

$ cat out
1000
...
10000
? The answer is Test input
11000
...
20000
? 
[1]+  Stopped                 python script.py > out 2> err
$

This is essentially a basic crash course in how standard processes typically functions in both foreground and background, and things just simply work as intended if the code handles the standard IO correctly.

Lastly, you can't really have it both ways. If the application expects stdin and none is provided, then the clear option is failure. If one is provided however but application got sent to background and kept running, it will be Stopped as it expects further input. If this stopped behaviour is unwanted, the application is at fault, there is nothing can be done but to change the application to not result in an error when EOF is encountered when executed with /dev/null as its stdin. If you want to keep stdin as is, with the application being able to somehow keep running when it is in the background you cannot use the input function as it will block when stdin is empty (resulting in process being stopped).


Now that you have clarified via the comment below that your "interactive prompt" is running in a thread, and since usage of input directly reads from stdin and you seem unwilling to modify your program (you asking for general case) but expects a utility to do this for you, the simple solution is to execute this within a tmux or screen session as they fully implement a pseudo tty that is independent from whichever console that started (so you can disconnect and send the session to the background, or start other virtual sessions, see manual pages) which will provide stdio that the program expects.

Finally, if you actually want your application to support this natively, you cannot simply use input as is, but you should check whether input can safely be called (i.e. perhaps making use of select), or by checking whether the process is currently in the foreground or background (An example you could start working from is How to detect if python script is being run as a background process, although you might want to check using sys.stdin, maybe.) to determine if input can be safely called (however if user suspends the task as input comes it would still hang as input waits), or use unix sockets for communication.

查看更多
登录 后发表回答