How to kill a sleep process using a script - bash

I want to write a script that will do the following things:
start a sleep 500 command in the foreground.
then send the sleep 500 command in the background so that I can get my prompt back
I have tried doing this inside the script:
sleep 500
kill `pidof sleep`
But when I am running the script, the sleep command does not stop and I do not get my prompt back.

I can't find the exact dup I was looking for so here is a short example. If I understand what you are wanting, you want to start a sleep process and then at any time you want to be able to quit the sleep process returning you to your prompt. You say "background" the sleep process, but I'm not sure whether you actually need to background it to let it expire on its own in the future, or if just need a way out of the sleep whenever you choose (that would make more sense)
While you can simply use Ctrl + C to interrupt the process, you won't be returned to your script. So if you need a way to return to your script, you need to trap SIGINT. When you set a trap 'some command' SIGINT your script will intercept and process the interrupt and control will return to the point following the sleep command, e.g.
#!/bin/bash
## set trap
trap 'printf "\n[Ctrl + C] SIGINT intercepted\n"' SIGINT
printf "sleeping 500...\n"
sleep 500
## rest of script after sleep
printf "control returned to script\ncontinuing to process commands\n"
Example Running Script Above
$ bash trap_int.sh
sleeping 500...
^C
[Ctrl + C] SIGINT intercepted
control returned to script
continuing to process commands
That may or may not satisfy what you need. However, you can build on that to control what happens after an interrupt a bit further. When you set the trap you can have it call a separate function within your script that can conditionally execute code and end the script on that separate branch by calling exit. That has the effect of transferring control back to your script to only execute commands from the path that starts with the function you specify. This provides away to avoid executing code that follows the sleep, while providing a separate execution path in your script to run whatever is needed after interrupt from sleep.
Conditionally Executing Commands After Interrupt
Rather than just sleep 500, in the next example we will repeatedly call two functions sleep_a which calls a nested sleep function sleep_b. Following SIGINT control will be passed to a function named handle_interrupt where additional commands can be executed calling exit to exit the script and return you to your prompt. An additional function called actual_exit is called to perform the exit.
#!/bin/bash
## function called to exit, returning you to prompt
function actual_exit
{
printf " Exiting after '%s'\n\n" "$1"
exit
}
## function called after SIGINT, calls actual_exit to disply signal caught
function handle_interrupt
{
printf "\n Signal SIGINT Caught! [ctrl + c]\n\n"
actual_exit 'SIGINT'
}
## set trap
trap handle_interrupt SIGINT # calls handle_interrupt on [ctrl + c]
declare -i idx=0 ## loop counter
function sleep_b
{
printf "\nentering sleep_b\n"
sleep 2
printf "executing post sleep_b index: %s\n" $((idx++))
}
function sleep_a
{
printf "\nentering sleep_a\n"
sleep 2
printf "executing post sleep_a index: %s\n" $((idx++))
sleep_b
}
## repeatedly call nested sleep until signal received
while :; do
sleep_a
done
## rest of script after sleep
printf "control returned to script\ncontinuing to process commands\n"
When the script is interrupted, the command following below the while loop are never reached as the script exits through the code path provided by the function called on SIGINT.
Providing Common Commands On exit
You can add yet another layer of execution by setting a trap on EXIT. If you have a collection of signal you set a trap for, you can have all end though a common function. For example if you monitored multiple signals each returning to a function ultimately calling exit, you can set a trap on EXIT calling a final function holding commands to be executed before the script exits.
For example, you could do:
trap final_function EXIT # calls final_function on exit() from script
where a call to exit anywhere within the script would invoke the trap on EXIT calling final_function before returning control back to you.
Let me know if something like one of the incremental levels of handling canceling sleep though an interrupt will do what you need. Technically it doesn't "background" sleep, but does allow you to cancel the sleep at any moment conditionally returning control within your script to wherever you choose.

Related

stop currently running bash script lazily/gracefully

Say I have a bash script like this:
#!/bin/bash
exec-program zero
exec-program one
the script issued a run command to exec-program with the arg "zero", right? say, for instance, the first line is currently running. I know that Ctrl-C will halt the process and discontinue executing the remainder of the script.
Instead, is there a keypress that will allow the current-line to finish executing and then discontinue the script execution (not execute "exec-program one") (without modifying the script directly)? In this example it would continue running "exec-program zero" but after would return to the shell rather than immediately halting "exec-program zero"
TL;DR Something runtime similar to "Ctrl-C" but more lazy/graceful ??
In the man page, under SIGNALS section it reads:
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
This is exactly what you're asking for. You need to set an exit trap for SIGINT, then run exec-program in a subshell where SIGINT is ignored; so that it'll inherit the SIG_IGN handler and Ctrl+C won't kill it. Below is an implementation of this concept.
#!/bin/bash -
trap exit INT
foo() (
trap '' INT
exec "$#"
)
foo sleep 5
echo alive
If you hit Ctrl+C while sleep 5 is running, bash will wait for it to complete and then exit; you will not see alive on the terminal.
exec is for avoiding another fork() btw.

Basic signal communication

I have a bash script, its contents are:
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
sleep 60
done
I execute this on a terminal with:
./my_script
And then do this on another terminal
kill -SIGTERM my_script_pid # obviously the PID is the one echoed from my_script
I would expect to see the message "Foo!" from the other terminal, but It's not working. SIGKILL works and the EXIT code is also executed.
Using Ctrl-C on the terminal my_script is running on triggers foo normally, but somehow I can't send the signal SIGTERM from another terminal to this one.
Replacing SIGTERM with any other signal doesn't change a thing (besides Ctrl-C not triggering anything, it was actually mapped to SIGUSR1 in the beginning).
It may be worth mentioning that just the signal being trapped is not working, and any other signal is having the default behaviour.
So, what am I missing? Any clues?
EDIT: I also just checked it wasn't a privilege issue (that would be weird as I'm able to send SIGKILL anyway), but it doesn't seem to be that.
Bash runs the trap only after sleep returns.
To understand why, think in C / Unix internals: While the signal is dispatched instantly to bash, the corresponding signal handler that bash has setup only does something like received_sigterm = true.
Only when sleep returns, and the wait system call which bash issued after starting the sleep process returns also, bash resumes its normal work and executes your trap (after noticing received_sigterm).
This is done this way for good reasons: Doing I/O (or generally calling into the kernel) directly from a signal handler generally results in undefined behaviour as far as I know - although I can't tell more about that.
Apart from this technical reason, there is another reason why bash doesn't run the trap instantly: This would actually undermine the fundamental semantics of the shell. Jobs (this includes pipelines) are executed strictly in a sequential manner unless you explicitly mess with background jobs.
The PID that you originally print is for the bash instance that executes your script, not for the sleep process that it is waiting on. During sleep, the signal is likely to be ignored.
If you want to see the effect that you are looking for, replace sleep with a shorter-lived process like ps.
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
ps > /dev/null
done

How do I stop a signal from killing my Bash script?

I want an infinite loop to keep on running, and only temporarily be interrupted by a kill signal. I've tried SIGINT, SIGUSR1, SIGUSR2. All of them seem to halt the loop. I even tried SIGINFO, but that wasn't supported by Linux.
#!/bin/bash
echo $$ > /tmp/pid # Save the pid
function do_something {
echo "I am doing stuff" #let's do this now, and go back to doing the thing that is to be done over and over again.
#exit
}
while :
do
echo "This should be done over and over again, but always wait for someething else to be done in between"
trap do_something SIGINT
while `true`
do
sleep 1 #so we're waiting for that other thing.
done
done
My code runs the function once, after getting a INT signal from another script, but then never again. It halts.
EDIT: Although I accidentally put en exit at the end of the function, here on Stack Overflow, I didn't in the actual code I used. Either way, it made no difference. The solution is SIGTERM as described by Tiago.
I believe you're looking for SIGTERM:
Example:
#! /bin/bash
trap -- '' SIGINT SIGTERM
while true; do
date +%F_%T
sleep 1
done
Running this example cTRL+C won't kill it nor kill <pid> you can however kill it with kill -9 <pid>.
If you don't want CTRL+Z to interrupt use: trap -- '' SIGINT SIGTERM SIGTSTP
trap the signal, then either react to it appropriately, in the function associate with the trap, or ignore it by for example associate : as command to get executed when the signal occurs.
to trap signals, bash knows the trap command
Reset trap to former action by executing trap with signal name only.
Therefore you want to (i think that's what you say you want with "only temporarily be interrupted by a kill signal"):
trap the signal at the begin of your script: trap signal custom_action
just before you want the signal to allow interrupting your script, execute: trap signal
At the end of that phase, trap again by: signal custom_action
to specify signals, you can also use their respective signal numbers. A list of signal names is printed with the command:
trap -l
the default signal sent by kill is SIGTERM (15), unless you specify a different signal after the kill command
don't exit in your do_something function. Simply let the function return to the section in your code where it was interrupted when the signal occured.
The mentioned ":" command has another potential use in your script, if you feel thusly inclined:
while :
do
sleep 1
done
can be an alternative to "while true" - no backticks needed for that, btw.
You just want to ignore the exit status.
If you want your script to keep running and not exit, without worrying about handling traps.
(my_command) || true
The parentheses execute that command in a subshell. The true is for compatibility with set -e, if you use it. It simply overrides the status to always report a success.
See the source.
I found this question to be helpful:
How to run a command before a Bash script exits?

Does Bash support a way of "triggering" an event?

I have a couple of bahs scripts running at the same time, and they communicate with each other by saving trigger variables in a folder. So one script will do something, and when its done it will echo "done" > variablefolder. The second script has a loop, checking every now and then if there is a "done" in the variable folder. If it is, the script executes something.
Does Bash support any better way of doing this? I know about export name=value, but that in practice does pretty much the same as what I'm doing now. I'm thinking, is there any way of pushing information to a Bash script that reacts on it? So when something is pushed to it, the Bash script will run a function, or something?
One way to handle inter-process communications is to use signals...
To send a signal to another process you can use the kill command.
The kill command uses the process id to identify the process.
You can save the process id to a file after the script starts using the $$ variable
Here is an example of a script that will catch a signal:
#!/bin/bash
echo $$ > /tmp/pid # Save the pid
function do_stuff {
echo "I am doing stuff"
exit
}
trap do_stuff SIGINT
while `true`
do
echo "Waiting for a signal"
sleep 1
done
So to send it a signal you can do this:
#!/bin/bash
pid=`cat /tmp/pid` # Read the pid
kill -s INT $pid

How to handle signals in bash during synchronous execution?

I have a bash script process which at some point executes a long-running subprocess synchronously.
During the run of that subprocess, a signal is sent directly to the bash script process requesting the script to terminate.
Is there any way to intercept that signal, terminate the subprocess and then exit the bash process?
Apparently, bash's signal handling never interrupts synchronous calls?
I cannot control the fact that the termination signal is sent to the bash process. Although if the signal could propagate to the child process, that would also solve my issue.
thanks in advance,
Broes
See the man page of bash, chapter SIGNALS:
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes. When bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.
So, run your external program asynchronously and use wait. Kill it using $!.
Here's a bash utility function I wrote to handle this. It's proved useful and robust. I hope you find it useful.
# Run a command in a way that can be interrupted by a signal (eg SIGTERM)
#
# When bash receives a SIGTERM it normally simply exits. If it's executing a subprocess
# that subprocess isn't signaled. (Typically that's not a problem for interactive shells
# because the entire Process Group gets sent the signal.)
#
# When running a script it's sometimes useful for the script to propagate a SIGTERM
# to the command that was running. We can do that by using the trap builtin to catch
# the signal. But it's a little tricky, per the bash manual:
#
# If bash is waiting for a command to complete and receives a signal for
# which a trap has been set, the trap will not be executed until the
# command completes.
#
# so a script executing a long-running command with a signal trap set won't
# notice the signal until later. There's a way around that though...
#
# When bash is waiting for an asynchronous command via the wait builtin, the
# reception of a signal for which a trap has been set will cause the wait
# builtin to return immediately with an exit status greater than 128,
# immediately after which the trap is executed.
#
# Usage:
#
# interruptable [options] command [args]
#
# Options:
# --killall - put the child into a process group (via setsid)
# and send the SIGTERM to the process group
# --debug - print a message including pid of the child
#
# Usage examples:
#
# interruptable sleep 3600
#
# If not interrupted, the exit status of the specified command is returned.
# If interrupted, the specified command is sent a SIGTERM and the current
# shell exits with a status of 143.
interruptable() {
# handle options
local setsid=""
local debug=false
while true; do
case "${1:-}" in
--killall) setsid=setsid; shift ;;
--debug) debug=true; shift ;;
--*) echo "Invalid option: $1" 1>&2; exit 1;;
*) break;; # no more options
esac
done
# start the specified command
$setsid "$#" &
local child_pid=$!
# arrange to propagate a signal to the child process
trap '
exec 1>&2
set +e
trap "" SIGPIPE # ensure a possible sigpipe from the echo does not prevent the kill
echo "${BASH_SOURCE[0]} caught SIGTERM while executing $* (pid $child_pid), sending SIGTERM to it"
# (race) child may have exited in which case kill will report an error
# if setsid is used then prefix the pid with a "-" to indicate that the signal
# should be sent to the entire process group
kill ${setsid:+-}$child_pid
exit 143
' SIGTERM
# ensure that the trap doesn't persist after we return
trap 'trap - SIGTERM' RETURN
$debug && echo "interruptable wait (child $child_pid, self $$) for: $*"
# An error status from the child process will trigger an exception (via set -e)
# here unless the caller is checking the return status
wait $child_pid # last command, so status of waited for command is returned
}
Yes, the signal can be intercepted with the trap command. See the example below:
#!/bin/bash
function wrap {
local flag=0
trap "flag=1" SIGINT SIGTERM
xeyes &
subppid=$!
while :
do
if [ $flag -ne 0 ] ; then
kill $subppid
break
fi
sleep 1
done
}
flag=0
trap "flag=1" SIGINT SIGTERM
wrap &
wrappid=$!
while : # This is the same as "while true".
do
if [ $flag -ne 0 ] ; then
kill $wrappid
break
fi
sleep 1 # This script is not really doing anything.
done
echo 'end'
What trap basically does is that it executes the command between "". So here the main function is in the while loop below. In every iteration the script checks if the flag is set, if not, it sleeps for a second. Before that, we remembered the pid of the child process via $!. The trap issues the command when the SIGINT or SIGTERM is caught (for other signals see kill manual).
The wrapper function does the same, as the main function. Additionally it calls the actual subprocess function (in this case subprocess is xeyes). When the wrapper function receives SIGTERM signal from the main function (main function also caught one of the signals), the wrapper function can clean up things before actually killing the subprocess. After that it breaks from the while loop and exits the wrapper function. Then the main function also breaks and prints 'end'.
edit:
Hope I understand this correctly, you are forced to execute xeyes &. Then the steps would be as follows (in terminal):
xeyes &
subpid=$!
trap "kill $subpid && exit " SIGINT SIGTERM
.... other stuff
.... more stuff
^C #TERMINATE - this firstly kills xeyes and then exits the terminal

Resources