I am having an issue with terminating the execution of a process inside a bash script.
Basically my script does the following actions:
Issue some starting commands
Start a program who waits for CTRL+C to stop
Do some post-processing on data retreived by the program
My problem is that when I hit CTRL+C the whole script terminates, not just the "inner" program.
I have seen around some scripts that do this, this is why I think it's possible.
You can set up a signal handler using trap:
trap 'myFunction arg1 arg2 ...' SIGINT;
I suggest keeping your script abortable overall, which you can do by using a simple boolean:
#!/bin/bash
# define signal handler and its variable
allowAbort=true;
myInterruptHandler()
{
if $allowAbort; then
exit 1;
fi;
}
# register signal handler
trap myInterruptHandler SIGINT;
# some commands...
# before calling the inner program,
# disable the abortability of the script
allowAbort=false;
# now call your program
./my-inner-program
# and now make the script abortable again
allowAbort=true;
# some more commands...
In order to reduce the likelihood of messing up with allowAbort, or just to keep it a bit cleaner, you can define a wrapper function to do the job for you:
#!/bin/bash
# define signal handler and its variable
allowAbort=true;
myInterruptHandler()
{
if $allowAbort; then
exit 1;
fi;
}
# register signal handler
trap myInterruptHandler SIGINT;
# wrapper
wrapInterruptable()
{
# disable the abortability of the script
allowAbort=false;
# run the passed arguments 1:1
"$#";
# save the returned value
local ret=$?;
# make the script abortable again
allowAbort=true;
# and return
return "$ret";
}
# call your program
wrapInterruptable ./my-inner-program
Related
I want to delete an envrionment variable from background process which is sleep a little. I setted the "asd" variable with value "foo"
export asd=foo
after that I want to delete from background process. I try this, but doesn't work:
(sleep 3;unset asd;) &
When 3 seconds elapsed, the "export" command still show the previous setting. What do I wrong?
My goal is the "asd" variable removed after 3 seconds.
How to unset envrionment variable from background in linux?
You can set a trap in parent process and unset it inside the trap while setting the background process to deliver a signal after specified time.
asd=foo
trap 'unset asd' SIGUSR1
p=$BASHPID
( sleep 1; kill -SIGUSR1 $p ) &
echo $asd # will print foo
sleep 2
echo $asd # will print empty line
Note that it will not unset a variable "exactly" after the specified time, but when the handler for the signal gets executed.
I guess alternatively I could imagine patchin bash and writing a bash builtin command that would create a thread that after specified time would unset the variable. Note that setenv is not thread safe, so such setup would have to synchronized with other bash code.
What do I wrong?
You did unset the variable in a subshell. Subshell environment doesn't affect parent shell.
I have function where I am expecting it to hang sometime. So I am setting one global variable and then reading it, if it didn't come up after few second I give up. Below is not complete code but it's not working as I am not getting $START as value 5
START=0
ineer()
{
sleep 5
START=5
echo "done $START" ==> I am seeing here it return 5
return $START
}
echo "Starting"
ineer &
while true
do
if [ $START -eq 0 ]
then
echo "Not null $START" ==> But $START here is always 0
else
echo "else $START"
break;
fi
sleep 1;
done
You run inner function call in back ground, which means the START will be assigned in a subshell started by current shell. And in that subshell, the START value will be 5.
However in your current shell, which echo the START value, it is still 0. Since the update of START will only be in the subshell.
Each time you start a shell in background, it is just like fork a new process, which will make a copy of all current shell environments, including the variable value, and the new process will be completely isolate from your current shell.
Since the subshell have been forked as a new process, there is no way to directly update the parent shell's START value. Some alternative ways include signals passing when the subshell which runs inner function exit.
common errors:
export
export could only be used to make the variable name available to any subshells forked from current shell. however, once the subshell have been forked. The subshell will have a new copy of the variable and the value, any changes to the exported variable in the shell will not effect the subshell.
Please take the following code for details.
#!/bin/bash
export START=0
ineer()
{
sleep 3
export START=5
echo "done $START" # ==> I am seeing here it return 5
sleep 1
echo "new value $START"
return $START
}
echo "Starting"
ineer &
while true
do
if [ $START -eq 0 ]
then
echo "Not null $START" # ==> But $START here is always 0
export START=10
echo "update value to $START"
sleep 3
else
echo "else $START"
break;
fi
sleep 1;
done
The problem is that ineer & runs the function in a subshell, which is its own scope for variables. Changes made in a subshell will not apply to the parent shell. I recommend looking into kill and signal catching.
Save pid of inner & by:
pid=$!
and use kill -0 $pid (that is zero!!) to detect if your process still alive.
But better redesign inner to use lock file, this is safer check!
UPDATE From KILL(2) man page:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
If sig is 0, then no signal is sent, but error checking is still
performed; this can be used to check for the existence
of a process ID or process group ID.
The answer is: in this case you can use export.
This instruction allow all subprocesses to use this variable.
So when you'll call the ineer function it will fork a process that is copying the entire environment, including the START variable taken from the parent process.
You have to change the first line from:
START=0
to:
export START=0
You may also want to read this thread: Defining a variable with or without export
...... (some awesome script).............
echo "I just wanna kill the function, not this"
myFunction()
{
while true
do
echo "this is looping forever"
done
}
myFunction
...... (some awesome script)...............
How to kill a running function without killing the script itself from terminal ?
First you cannot "kill" a function, "killing" refers to processes.
However you can install some special signal handling inside your function, that can make it react the way you want.
For this in bash you use trap, to define a signal handler for the signal you want to catch.
The function that is used as a signal handler here, also clears the trap, as traps are global and the defined handler would be called on any subsequent SIGUSR1 that could occur.
echo "I just wanna kill the function, not this"
trap_myFunction()
{
trap - SIGUSR1
return
}
myFunction()
{
trap trap_myFunction SIGUSR1
while true
do
echo "this is looping forever"
sleep 1
done
}
myFunction
echo "Continuing processing .."
Now, if you start this script and kill it, using:
kill -SIGUSR1 pid_of_process
it will enter the signal handler installed, which is simply return and continue with the echo-command after myFunction.
If you kill it by using any other signal, the trap will not be triggered and the process will be terminated completely.
I am having a problem getting bash to do exactly what I want, it's not a major issue, but annoying.
1.) I have a third party software I run that produces some output as stderr. Some of it is useful, some of it is regularly stuff I don't care about and I don't want this dumped to screen, however I do want the useful parts of the stderr dumped to screen. I figured the best way to achieve this was to pass stderr to a function, then use conditions in that function to either show the stderr or not.
2.) This works fine. However the solution I have implemented dumped out my errors at the right time, but then returns a bash prompt and I want to summarise the status of the errors at the end of the function, but echo-ing here prints the text after the prompt meaning that I have to press enter to get back to a clean prompt. It shall become clear with the example below.
My error stream generator:
./TestErrorStream.sh
#!/bin/bash
echo "test1" >&2
My function to process this:
./Function.sh
#!/bin/bash
function ProcessErrors()
{
while read data;
do
echo Line was:"$data"
done
sleep 5 # This is used simply to simulate the processing work I'm doing on the errors.
echo "Completed"
}
I source the Function.sh file to make ProcessErrors() available, then I run:
2> >(ProcessErrors) ./TestErrorStream.sh
I expect (and want) to get:
user#user-desktop:~/path$ 2> >(ProcessErrors) ./TestErrorStream.sh
Line was:test1
Completed
user#user-desktop:~/path$
However what I really get is:
user#user-desktop:~/path$ 2> >(ProcessErrors) ./TestErrorStream.sh
Line was:test1
user#user-desktop:~/path$ Completed
And no clean prompt. Of course the prompt is there, but "Completed" is being printed after the prompt, I want to printed before, and then a clean prompt to appear.
NOTE: This is a minimum working example, and it's contrived. While other solutions to my error stream problem are welcome I also want to understand how to make bash run this script the way I want it to.
Thanks for your help
Joey
Your problem is that the while loop stay stick to stdin until the program exits.
The release of stdin occurs at the end of the "TestErrorStream.sh", so your prompt is almost immediately available compared to what remains to process in the function.
I suggest you wrap the command inside a script so you'll be able to handle the time you want before your prompt is back (I suggest 1sec more than the suspected time needed for the function to process the remaining lines of codes)
I successfully managed to do this like that :
./Functions.sh
#!/bin/bash
function ProcessErrors()
{
while read data;
do
echo Line was:"$data"
done
sleep 5 # simulate required time to process end of function (after TestErrorStream.sh is over and stdin is released)
echo "Completed"
}
./TestErrorStream.sh
#!/bin/bash
echo "first"
echo "firsterr" >&2
sleep 20 # any number here
./WrapTestErrorStream.sh
#!/bin/bash
source ./Functions.sh
2> >(ProcessErrors) ./TestErrorStream.sh
sleep 6 # <= this one is important
With the above you'll get a nice "Completed" before your prompt after 26 seconds of processing. (Works fine with or without the additional "time" command)
user#host:~/path$ time ./WrapTestErrorStream.sh
first
Line was:firsterr
Completed
real 0m26.014s
user 0m0.000s
sys 0m0.000s
user#host:~/path$
Note: the process substitution ">(ProcessErrors)" is a subprocess of the script "./TestErrorStream.sh". So when the script ends, the subprocess is no more tied to it nor to the wrapper. That's why we need that final "sleep 6"
#!/bin/bash
function ProcessErrors {
while read data; do
echo Line was:"$data"
done
sleep 5
echo "Completed"
}
# Open subprocess
exec 60> >(ProcessErrors)
P=$!
# Do the work
2>&60 ./TestErrorStream.sh
# Close connection or else subprocess would keep on reading
exec 60>&-
# Wait for process to exit (wait "$P" doesn't work). There are many ways
# to do this too like checking `/proc`. I prefer the `kill` method as
# it's more explicit. We'd never know if /proc updates itself quickly
# among all systems. And using an external tool is also a big NO.
while kill -s 0 "$P" &>/dev/null; do
sleep 1s
done
Off topic side-note: I'd love to see how posturing bash veterans/authors try to own this. Or perhaps they already did way way back from seeing this.
How can we trap signals in shell script, where can we trap signals?
also can someone explain
# trap commands signals
You can write a shell script :
trap ctl_c INT # trap <name_of_function_to_called> <Signal to be handled>
function ctl_c(){
// signal handling logic needed.
}
Now whenever you will send SIGINT (key-press CTRL + C), this function will get called, instead of default functionality.