5

In my bash script, I'm running an external command that's stored in $cmd variable. (It could be anything, even some simple bash oneliner.)

If ctrl+C is pressed while running the script, I want it to kill the currently running $cmd but it should still continue running the main script. However, I would like to preserve the option to kill the main script with ctrl+C when the main script is running.

#!/bin/bash
cmd='read -p "Ooook?" something; echo $something; sleep 4 ' 
while true; do
    echo "running cmd.."
    eval "$cmd"     # ctrl-C now should terminate the eval and print "done cmd"
    echo "done cmd"
    sleep 5         # ctrl-C now should terminate the main script
done

Any idea how to do it some nice bash way?

Changes applied based on answers:

#! /bin/bash

cmd='read -p "Ooook1?" something; read -p "Oook2?" ; echo $something; sleep 4 ' 
while true; do
    echo "running cmd.."
    trap "echo Interrupted" INT
    eval "($cmd)"     # ctrl-C now should terminate the eval and print "done cmd"
    trap - INT
    echo "done cmd"
    sleep 5         # ctrl-C now should terminate the main script
done

Now, pressing ctrl+C while "Ooook1?" read will break the eval only after that read is done. (it will interrupt just before "Oook2") However it will interrupt "sleep 4" instantly.

In both cases it will do the right thing - it will just interrupt the eval subshell, so we're almost there - just that weird read behaviour..

Alfe
  • 56,346
  • 20
  • 107
  • 159
Miroslav
  • 528
  • 1
  • 6
  • 11

3 Answers3

4

If you can afford having the eval part run in a subshell, "all" you need to do is trap SIGINT.

#! /bin/bash

cmd='read -p "Ooook1?" something; read -p "Oook2?" ; echo $something; sleep 4 ' 
while true; do
    echo "running cmd.."
    trap "echo Interrupted" INT
    eval "($cmd)"     # ctrl-C now should terminate the eval and print "done cmd"
    trap - INT
    echo "done cmd"
    sleep 5         # ctrl-C now should terminate the main script
done

Don't know if that will fit your specific need though.

$ ./t.sh 
running cmd..
Ooook1?^CInterrupted
done cmd
^C
$ ./t.sh 
running cmd..
Ooook1?qsdqs^CInterrupted
done cmd
^C
$ ./t.sh 
running cmd..
Ooook1?qsd
Oook2?^CInterrupted
done cmd
^C
$ 
GNU bash, version 4.1.9(2)-release (x86_64-pc-linux-gnu)
Mat
  • 202,337
  • 40
  • 393
  • 406
  • Yeah, I'm running it in a subshell anyway so that's not a problem. Your example is almost there, however there seems to be some weird problem in the way "read" command is handled - it will break the "$cmd" subshell only after I press enter, no matter how many times I have pressed the ctrl-C. – Miroslav Feb 08 '12 at 15:52
  • Don't know what you mean. "Works for me", whether I've started to type text at the Oook prompt or not. You did change the `eval` command, right? – Mat Feb 08 '12 at 15:53
  • I have updated the main question. In my case the read isn't interrupted until I press enter. – Miroslav Feb 08 '12 at 16:08
  • `running cmd.. Ooook?^C^C^C^C^C^C^C^C^C^C^C <-pressed ctrl-C couple times, and now enter: done cmd running cmd.. Ooook?^C^C^C^C^C <- and again.. done cmd ^C` ^---- see my output – Miroslav Feb 08 '12 at 16:10
  • Sorry, can't repro your issue. "Works for me" still with your updated command. – Mat Feb 08 '12 at 16:10
  • Okay, I have tried it with two different bash versions: doesn't work: GNU bash, version 4.2.10(1)-release (i386-redhat-linux-gnu) works: GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) – Miroslav Feb 08 '12 at 16:24
  • Maybe some bug in 4.2.10 release? However it works on the older one, which is the one I care about now, so I would say my question is now answered. – Miroslav Feb 08 '12 at 16:26
0

You can determine whether the sleep command exited abnormally by examining the last exit status echo $?. A non-zero status probably indicates Ctrl-C.

diolemo
  • 2,621
  • 2
  • 21
  • 28
  • It's not really about detecting the exit status - I can do that, but I don't really care in this case. All I want is the ability to interrupt running "eval $cmd" without interrupting the main script. – Miroslav Feb 08 '12 at 15:57
0

No, read is not an external command, it is internal builtin bash command being executed in the same process as the other instructions. So at Ctrl-C all the process will be killed.

P.S. Yes. you can execute command in subshell. Something like this

#!/bin/bash
cmd='trap - INT; echo $$; read -p "Ooook?" something; echo $something; sleep 4 ' 
echo $$
while true; do
    echo "$cmd" > tmpfile
    echo "running cmd.."
    trap "" INT
    bash tmpfile
    rm tmpfile  
    trap - INT
    echo "done cmd"
    sleep 5         # ctrl-C now should terminate the main script   
done
mikithskegg
  • 806
  • 6
  • 10
  • Shouldn't this be resolved by running the read in a subshell? (however I still have some problems with it - see comment under Mat's answer.) – Miroslav Feb 08 '12 at 15:54