4

I current have a script that looks like this.

# code

mplayer "$vid"

# more code

The problem is that if this script is killed the mplayer process lives. I wondering how I could make it so that killing the script would kill mplayer as well.

I can't use exec because I need to run commands after mplayer.

exec mplayer "$vid"

The only possible solution I can think of is to spawn it in the background and wait until it finishes manually. That way I can get it's PID and kill it when the script gets killed, not exactly elegant. I was wondering what the "proper" or best way of doing this is.

Kevin Cox
  • 3,080
  • 1
  • 32
  • 32

5 Answers5

4

I was able to test the prctl idea I posted about in a comment and it seems to work. You will need to compile this:


#include "sys/prctl.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"

int main(int argc, char ** argv){

  prctl(PR_SET_PDEATHSIG, atoi(argv[1]),0,0,0);

  char * argv0 = strdup(argv[2]);

  char * slashptr = strrchr(argv0, '/');
  if(slashptr){
    argv0 = slashptr + 1;
  }

   return execvp(argv0, &(argv[2]));



}

Let's say you have compiled the above to an executable named "prun" and it is in your path. Let's say your script is called "foo.sh" and it is also in your path. Make a wrapper script that calls

prun 15 foo.sh

foo.sh should get SIGTERM when the wrapper script is terminated for any reason, even SIGKILL.

Note: this is a linux only solution and the c source code presented is without detailed checking of arguments

frankc
  • 11,290
  • 4
  • 32
  • 49
  • This is really great (haven't had a test yet but I'm going to assume it works). It would be awesome to have a bash solution though. I think I will accept this answer even though my answer is the one I plan on using (I'm not too worried about SIGKILL) because this one completely solves the issue. – Kevin Cox Nov 06 '12 at 18:31
  • Also, do you want to repost or add a link to this answer on this related question (http://unix.stackexchange.com/questions/54963/how-can-terminal-emulators-kill-their-children-after-recieving-a-sigkill) as it is a better answer than any of the other ones given. – Kevin Cox Nov 06 '12 at 18:42
  • Okay, I got around to trying it and it had some bugs. I fixed it up and submitted and edit. The old script was listening for the parent's death but it was the parent. The new script `fork`s and the child asks to receive the parent's death before `exec`ing. I also removed the name-mangling because it broke for files that are not on the path or in the current directory. – Kevin Cox Nov 06 '12 at 19:17
  • The way I had set it up with exec it that it was designed to be wrapped by a shell script. When the shell script dies, then whatever the shell script lauched via prun would get the signal. Doing it with fork is fine but it presents different semantics. It would be when prun itself dies that a signal is delivered. As for the name mangling, i made a mistake. I meant for argv0 to be the new argv0 of the args, not the command executed. Anyway, its not critical to the central idea. – frankc Nov 06 '12 at 20:18
  • Oh, I get it. My script would be the same except instead of `mplayer ...` I would run `prun mplayer ...`. Okay. That works great then. Maybe you should change the answer to use `mplayer` rather than `foo.sh` as `mplayer` was the example used in the question. That would help make it more clear. – Kevin Cox Nov 06 '12 at 20:21
  • Just for refrence [here is my alternative version](http://pastebin.com/12k2j6Q7) Which serves as a wrapper and gives the called script the prescribed signal when it dies (no matter how). Useage: `killcatch 15 myscript.sh`. – Kevin Cox Nov 06 '12 at 20:39
3

Thanks to Mux for the lead. It appears that there is no way to do this in bash except for manually catching signals. Here is a final working (overly commented) version.

trap : SIGTERM SIGINT # Trap these two (killing) signals.  These will cause wait
                      # to return a value greater than 128 immediately after received.

mplayer "$vid" & # Start in background (PID gets put in `$!`)
pid=$!

wait $pid # Wait for mplayer to finish.
[ $? -gt 128 ] && { kill $pid ; exit 128; } ; # If a signal was recieved
                                              # kill mplayer and exit.

Refrences: - traps: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html

Kevin Cox
  • 3,080
  • 1
  • 32
  • 32
  • you can't catch signal 9. It immediately kills the process. if it could be caught it wouldn't be useful. Unfortunately that case can't be handled. – Kevin Cox Nov 06 '12 at 02:09
  • Actually, I did a little research and (xfce-) terminal has a way of doing this. I will look into it further (adding SIGKILL to trap doesn't work). – Kevin Cox Nov 06 '12 at 04:55
  • I appears to have to do with sessions. I have a snipit that, when placed at the top of the script, causes mplayer to die when it is killed http://pastebin.com/UUf5muW5. Unfortunately, this doesn't work in xscreensaver for some reason and xscreensaver actually sets the script as the session controller. It might be a bug in xscreensaver or mplayer might be doing something funny. – Kevin Cox Nov 06 '12 at 16:31
2

(Updated) I think I understand what you are looking for now:

You can accomplish this by spawning a new terminal to run your script:

gnome-terminal -x /path_to_dir_of_your_script/your_script_name

(or use xterm -e or konsole -e instead of gnome-terminal -x, depending on what system you are on)

So now whenever your script ends / exits (I assume you have exit 0 or exit 1 in certain parts of the script), the newly spawned terminal will also exit since the script is finished - this will in turn also kill any applications spawned under that new terminal.

For example, I just tested the above command with this script:

#!/bin/bash

gedit &
pid=$!
echo "$pid"

sleep 5
exit 0

As you can see, there are no explicit calls to kill the new gedit process, but the application (gedit) closes as soon as the script exits anyway.

(Previous answer: alternatively, if you were simply asking about how to kill a process) Here's a short example of how you can accomplish that with kill.

#!/bin/bash

gedit &
pid=$!
echo "$pid"

sleep 5
kill -s SIGKILL $pid

Unless I misunderstood your question, you can get the PID of the spawned process right away instead of waiting until it finishes.

sampson-chen
  • 45,805
  • 12
  • 84
  • 81
  • The problem is that I am not running my script, it is being run my another program. – Kevin Cox Nov 04 '12 at 19:30
  • 2
    @KevinCox which other program is running your script? Can you have the program execute `gnome-terminal -x /path_to_dir_of_your_script/your_script_name` instead of just the name of your script? – sampson-chen Nov 04 '12 at 19:36
  • XScreensaver. I guess I could but it definatly wouldn't be as elegant. I will do this if I can't find anything better. – Kevin Cox Nov 04 '12 at 23:36
  • 1
    I think this is the best answer. The terminal is delivering sighup for you so you don't have to trap and deliver your own signals. I think anything you do would be to essentially reimplement what the termimal is doing – frankc Nov 05 '12 at 22:28
  • Yes, but it is opening a terminal. This is dependent on X. It isn't really an issue for mplayer and xscreensaver because no one would but it isn't exactly "elegant". The thing that makes it really nice though is that it handles SIGKILL properly which I haven't figured out how to do yet. – Kevin Cox Nov 06 '12 at 05:21
  • 1
    if you are on linux you could try to wrap you current script in another script, then have one signal of your choice delivered to your current script when the wrapper is killed (even by SIGKILL), which you can then use to kill your process tree. see: http://stackoverflow.com/questions/12193581/detect-death-of-parent-process though I don't know how to call prctl from a shell script... – frankc Nov 06 '12 at 13:28
  • I can't get this to work. I think I need to somehow get a SIGHUP sent so that I can die. I have been working on this all day but even becoming a session leader won't send SIGHUP to my children. I don't know why because as far as I understand that should occur. – Kevin Cox Nov 06 '12 at 18:25
2

Well, you can simply kill the process group instead, this way the whole process tree will be killed, first find out the group id

ps x -o  "%p %r  %c" | grep <name>

And then use kill like so:

kill -TERM -<gid>

Note the dash before the process group id. Or a one-liner:

kill -TERM -$(pgrep <name>)
iabdalkader
  • 17,009
  • 4
  • 47
  • 74
  • The problem is that I am not killing my script. It is being run and killed by a program that I (for all intents and purposes) have no control over. – Kevin Cox Nov 04 '12 at 19:29
  • 1
    @KevinCox in that case I see no other way of doing that other than catching the signal and killing the child processes, or something like that. It's not that bad. – iabdalkader Nov 04 '12 at 19:32
0

Perhaps use command substitution to run mplayer "$vid" in a subshell:

$(mplayer "$vid")

I tested it this way:

tesh.sh:

#!/bin/sh
$vid = "..."
$(mplayer "$vid")

% test.sh

In a separate terminal:

% pkill test.sh

In the orginal terminal, mplayer stops, printing to stderr

Terminated
MPlayer interrupted by signal 13 in module: av_sync
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677