killing a script

Hans Mulder hansmu at xs4all.nl
Tue Aug 30 12:40:26 CEST 2011


On 30/08/11 06:13:41, Steven D'Aprano wrote:
> On Tue, 30 Aug 2011 08:53 am Arnaud Delobelle wrote:
>
> [...]
>>> Yes, but if I am not mistaken, that will require me to put a line or
>>> two after each os.system call. That's almost like whack-a-mole at the
>>> code level rather than the Control-C level. OK, not a huge deal for
>>> one script, but I was hoping for something simpler. I was hoping I
>>> could put one line at the top of the script and be done with it.
>>
>> Write a function!  That's what they're for after all :)
>
>
> I'm not sure that this is actually as simple as that, especially using
> os.system.
>
> As I understand it, the scenario is this:
>
> The main script looks something like this:
>
> for x in whatever:
>      os.system('something.py x')
>
> Each time through the loop, a new Python process is started. Each process
> runs in the foreground, capturing standard input, and so hitting Ctrl-C
> kills *that* process, not the main script. Unless, by chance, the Ctrl-C
> happens after the system call returns, but before the next one starts, it
> is completely invisible to the parent process (the main script). Wrapping
> os.system in a function does nothing to fix that.
>
> Possibly using the subprocess module may help.

Using the subprocess module is likely to help, because unlike os.system,
subprocess does not set control-C to 'ignore' in the parent process

> Otherwise, the only way around this I can think of is to ensure that
> the 'something.py' script (or scripts!) each return an error code for "User
> Cancelled":
>
> for x in whatever:
>      result = os.system('something.py x')
>      if result == 3:  # User Cancelled
>          break

On my system, os.system returns 2 if the subprocess was killed by a
control-C.  The portable way is to use the constant SIGINT from the
signal module.

Not that os.system does not return 2 if the child ended by doing
sys.exit(2); in that case, os.system in the parent returns 2<<8.

> But if the 'something.py' scripts are arbitrary scripts, I don't think you
> have any easy way around it.

If the arbitrary script does not trap SIGINT, then os.system
will return the number of the signal that kill the script, i.e.
signal.SIGINT.

If the script traps SIGINT and does some cleanup and then
exits, the best you can hope for, is that in the error case,
the script exits with a special value.  And you have to
remember the <<8 issue.

> Perhaps use threads, and have the main script
> ask the thread to kill the child process?

How would that help?

The problem is that the parent process ignores control-C while
os.system is running (by design).  If the parent uses threads,
it still won't receive the signal, because signal state is
global.

> Regardless, this is a hard problem, and it isn't possible to just have some
> magic switch at the top of your script to make it work. You actually have
> to do the work yourself.

You could monkey-patch the os module and replace os.system by
(untested):

def sub_system(command):
     pr = subprocess.Popen(command, shell=True)
     status = os.waitpid(pr.pid, 0)[1]
     return status

That should have the same effect as os.system, except it does
not ignore control-C.

> (But of course you can do the work inside a function, and re-use it
> elsewhere.)

Even if you don't re-use it, you should still do it in a
function, if only for readability.

Hope this helps,

-- HansM






More information about the Python-list mailing list