4

I'm trying to run a program inside an endless loop because it sometimes dies for no reason. I would like to be able to hit Ctrl-C to prevent the program being restarted though.

I don't want Ctrl-C to kill the program, just to wait until it dies, then not restart it again.

theprogram is a wine program (utorrent).

Bonus points for telling me how to make it so it will safely exit theprogram just like clicking on the 'x' in the top right of it. When I manually kill it from the command line or hit Ctrl-C, it doesn't get to run its cleanup code. Hence my attempt to just stop it being restarted.

I checked a few of the other questions about trapping SIGINT, but I couldn't work out how to do this.

Can anyone fix this code? My code seems to kill theprogram then exit the loop when Ctrl-C is pressed, without letting theprogram clean up.

#!/bin/bash

EXIT=0
trap exiting SIGINT

exiting() { echo "Ctrl-C trapped, will not restart utorrent" ; EXIT=1;}

while [ $EXIT -eq 0 ] ; do
        wine theprogram
        echo "theprogram killed or finished"
        date
        echo "exit code $?"
        echo "sleeping for 20 seconds, then restarting theprogram..."
        sleep 20
done

echo "out of loop"
localhost
  • 1,253
  • 4
  • 18
  • 29
  • your trap works... I'd put the function definition before the trap setting, though. Perhaps you should add the cleanup things **inside** the trap handler function? but you will have cases when it's not called while theprogram is running... so you'll have to make sure it's handled correctly then. – Olivier Dulac Nov 19 '13 at 12:16
  • and it could be tough for you to refrain using "ctrl-C" to Copy(/Paste), in that wine program? ... And to know if you did it or not. seems a weird way to handle restarting. You could setup a trap for one if the SIGUSR signals, and "kill -SIG.... pid" (or have a button/script that does it for you) instead – Olivier Dulac Nov 19 '13 at 12:21
  • 1
    http://www.cons.org/cracauer/sigint.html indicates how SIGINT works (and the different ways the shells react depending on their modes) – Olivier Dulac Nov 19 '13 at 12:45
  • @OlivierDulac I can't put the cleanup things inside the trap function because the cleanup stuff is run by `theprogram` (utorrent). I don't want to kill it without giving it a chance to finish what it needs to. – localhost Nov 20 '13 at 07:42
  • it seems the usual recommended way is `wineserver -k`: this will first ask apps to close, but then FORCE them to close if they didn't. Another possibility could be to have something like http://superuser.com/a/299213/174998 ? but I don't know how you could set it up and use it from "outside" of wine [I don't use wine. I guess it's possible to remotely access the command line from the host linux?] – Olivier Dulac Nov 20 '13 at 09:37

4 Answers4

2

Use a monitoring process:

This allows the SIGINT signal to hit the monitor process trap handler without affecting the child.

(this could also be done in perl, python or any language)

#!/bin/bash

cmd() {
    trap '' INT
    trap 'echo "Signal USR1 received (pid=$BASHPID)"; EXIT=1' USR1
    EXIT=0
    while [ $EXIT -eq 0 ]
    do
        echo "Starting (pid=$BASHPID)..."
        sleep 5 # represents "wine theprogram"
        echo "theprogram killed or finished"
        date
        echo "Exit code $?"
        if [ $EXIT -eq 0 ]; then
            echo "Sleeping for 2 seconds, then restarting theprogram..."
            sleep 2
        fi
    done
    echo "Exiting (pid=$BASHPID)"
}

run() { cmd & PID=$!; echo Started $PID; }
graceful_exit() { kill -s USR1 $PID && echo "$PID signalled to exit (USR1)"; }
shutdown() { kill -0 $PID 2>/dev/null && echo "Unexpected exit, killing $PID" && kill $PID; }

trap 'graceful_exit' INT
trap 'shutdown' EXIT
run

while :
do
    wait && break
done

echo "Exiting monitor process"
Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36
1

Try this:

while true
do
  xterm -e wine theprogram || break
  sleep 3
done

The trick is done by using another xterm to start the wine. That way the wine has a different controlling tty and won't be affected by the Ctrl-c press.

The drawback is that there will be an additional xterm lingering around on your desktop. You could use the option -iconic to start it iconified.

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • @Alfe I tried this, maybe I'm missing something, but with this code, hitting Ctrl-C in the first xterm window instantly closes the other xterm and utorrent, hitting it in the second xterm instantly kills utorrent and the second xterm, just to be restarted again. I just used this code without any `trap` command, just as you wrote it but with the `#!/bin/bash` at the start. Could you please edit your answer to put the complete bash script you think I should run in there, or explain what I'm doing wrong? – localhost Nov 20 '13 at 07:50
  • What happens if you try it with `xterm -e xterm || break` instead? Does the second xterm survive the Ctrl-c? I do not have your torrent program for testing this. Could be that wine-based stuff notices the closing of the father process and then closes itself. In this case you might go along using the complicated `xterm -e xterm -e wine theprogram || break`. – Alfe Nov 20 '13 at 08:03
1

Well, I ended up not using Ctrl-C as per my question because I couldn't find a good solution, but I used zenity to popup a box that I can click to exit the loop:

zenity popup

#!/bin/bash
zenity --info --title "thewineprogram" --text "Hit OK to disable thewineprogram auto-restart" &  # run zenity in the background
zen_pid=$!

while : 
do
    wine <wineprogramlocation>
    EXITCODE=$?
    echo "thewineprog killed or finished"
    echo "exit code was $EXITCODE"
    date

    kill -0 $zen_pid > /dev/null 2>&1  # kill -0 just checks if a pid exists
    if [ $? -eq 1 ] # process does not exist
    then
        break
    fi

    echo "sleeping for 5 seconds, then restarting the wine program..."
    sleep 5     
done

echo "finished"
localhost
  • 1,253
  • 4
  • 18
  • 29
0

It appears that trap on SIGINT must terminate the currently executing sub-command. The only exception appears to be the empty-string handler.

To demonstrate this: When ctrl-c is pressed this (trap "" INT;echo 1;sleep 5;echo 2) does not halt the sleep command. However this (trap "echo hi" INT;echo 1;sleep 5;echo 2) does. After this trap handler executes, execution continues on the command that follows, specifically "echo 2". So empty-string as a handler seems to be a special case which does not kill the current sub-command. There seems to be no way to run a handler plus not kill the current sub-command.

Why this happens: Shell forks + execs to execute each program. On system call exec, it resets signal handlers to their default behavior (calling process is overwritten so the handlers are gone). Ignored signals are inherited (see "man 2 execve", "man 7 signal" and POSIX.1; http://www.perlmonks.org/?node_id=1198044)

I had a second idea: use 'trap "" INT' to fully disable ctrl-c and then trap ctrl-z as the signal to gracefully exit your program. Only trapping ctrl-z (STP) seems to not work properly for me. When I run '(trap "echo test" TSTP;sleep 5)' and press ctrl-z, my shell is hung. sleep never completes after 5 seconds and oddly ctrl-c no longer works. I don't know any other hotkey-signals to use other than ctrl-c and ctrl-z. This is known behavior: see Bash script: can not properly handle SIGTSTP.

Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36