How to run multiple shell script which does not give prompt at the end - bash

I want to run three processes which all will stop showing that the service is started and prompt will not be given. I want to automate this procedure. I tried using "&" at the end but it pops in the terminal. I tried using "sh +x script1.sh & sh +x script2.sh" I need to stop the process by pressing ctrl+c for another script to run Please help in this

You need to define a general script that launches the three processes in background and waits for the user the press Control+C. Then you add a trap to the general script to launch a shutdown hook.
I think that the solution may look as this:
#!/bin/bash
end_processes() {
echo "Shutdown hook"
if [ -n $PID1 ]; then
echo "Killing PID 1 = $PID1"
kill -9 $PID1
fi
if [ -n $PID2 ]; then
echo "Killing PID 2 = $PID2"
kill -9 $PID2
fi
if [ -n $PID2 ]; then
echo "Killing PID 3 = $PID3"
kill -9 $PID3
fi
}
# Main code: Add trap
trap end_processes EXIT
# Main code: Launch scripts
./script1.sh &
PID1=$!
./script2.sh &
PID2=$!
./script3.sh &
PID3=$!
# Main code: wait for user to press Control+C
while [ 1 ]; do
sleep 1s
done
Notice that:
I have added some echo messages just to test.
Trap executes a function when EXIT is received on the script. You can change the received signal by capturing only a specific signal (i.e. SIGINT)
The trap function is now killing the processes with -9. I you wish, you can send other kill signals
The $! retrieves the PID of the most recent backgroud command.
You can modify the wait loop (the last while command) to sleep firstly for the aproximate time of the processes to finish and then to wait for a more smaller time:
APROX_TIME=30s
POLL_TIME=2s
sleep $APROX_TIME
while [ 1 ]; do
sleep $POLL_TIME
done

Related

shell script - how to stop "watch" command in the shell script [duplicate]

I have a bash script that launches a child process that crashes (actually, hangs) from time to time and with no apparent reason (closed source, so there isn't much I can do about it). As a result, I would like to be able to launch this process for a given amount of time, and kill it if it did not return successfully after a given amount of time.
Is there a simple and robust way to achieve that using bash?
P.S.: tell me if this question is better suited to serverfault or superuser.
(As seen in:
BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")
If you don't mind downloading something, use timeout (sudo apt-get install timeout) and use it like: (most Systems have it already installed otherwise use sudo apt-get install coreutils)
timeout 10 ping www.goooooogle.com
If you don't want to download something, do what timeout does internally:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
In case that you want to do a timeout for longer bash code, use the second option as such:
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &
or to get the exit codes as well:
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
sleep 999&
t=$!
sleep 10
kill $t
I also had this question and found two more things very useful:
The SECONDS variable in bash.
The command "pgrep".
So I use something like this on the command line (OSX 10.9):
ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done
As this is a loop I included a "sleep 0.2" to keep the CPU cool. ;-)
(BTW: ping is a bad example anyway, you just would use the built-in "-t" (timeout) option.)
Assuming you have (or can easily make) a pid file for tracking the child's pid, you could then create a script that checks the modtime of the pid file and kills/respawns the process as needed. Then just put the script in crontab to run at approximately the period you need.
Let me know if you need more details. If that doesn't sound like it'd suit your needs, what about upstart?
One way is to run the program in a subshell, and communicate with the subshell through a named pipe with the read command. This way you can check the exit status of the process being run and communicate this back through the pipe.
Here's an example of timing out the yes command after 3 seconds. It gets the PID of the process using pgrep (possibly only works on Linux). There is also some problem with using a pipe in that a process opening a pipe for read will hang until it is also opened for write, and vice versa. So to prevent the read command hanging, I've "wedged" open the pipe for read with a background subshell. (Another way to prevent a freeze to open the pipe read-write, i.e. read -t 5 <>finished.pipe - however, that also may not work except with Linux.)
rm -f finished.pipe
mkfifo finished.pipe
{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!
# Get command PID
while : ; do
PID=$( pgrep -P $SUBSHELL yes )
test "$PID" = "" || break
sleep 1
done
# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &
read -t 3 FINISHED <finished.pipe
if [ "$FINISHED" = finished ] ; then
echo 'Subprocess finished'
else
echo 'Subprocess timed out'
kill $PID
fi
rm finished.pipe
Here's an attempt which tries to avoid killing a process after it has already exited, which reduces the chance of killing another process with the same process ID (although it's probably impossible to avoid this kind of error completely).
run_with_timeout ()
{
t=$1
shift
echo "running \"$*\" with timeout $t"
(
# first, run process in background
(exec sh -c "$*") &
pid=$!
echo $pid
# the timeout shell
(sleep $t ; echo timeout) &
waiter=$!
echo $waiter
# finally, allow process to end naturally
wait $pid
echo $?
) \
| (read pid
read waiter
if test $waiter != timeout ; then
read status
else
status=timeout
fi
# if we timed out, kill the process
if test $status = timeout ; then
kill $pid
exit 99
else
# if the program exited normally, kill the waiting shell
kill $waiter
exit $status
fi
)
}
Use like run_with_timeout 3 sleep 10000, which runs sleep 10000 but ends it after 3 seconds.
This is like other answers which use a background timeout process to kill the child process after a delay. I think this is almost the same as Dan's extended answer (https://stackoverflow.com/a/5161274/1351983), except the timeout shell will not be killed if it has already ended.
After this program has ended, there will still be a few lingering "sleep" processes running, but they should be harmless.
This may be a better solution than my other answer because it does not use the non-portable shell feature read -t and does not use pgrep.
Here's the third answer I've submitted here. This one handles signal interrupts and cleans up background processes when SIGINT is received. It uses the $BASHPID and exec trick used in the top answer to get the PID of a process (in this case $$ in a sh invocation). It uses a FIFO to communicate with a subshell that is responsible for killing and cleanup. (This is like the pipe in my second answer, but having a named pipe means that the signal handler can write into it too.)
run_with_timeout ()
{
t=$1 ; shift
trap cleanup 2
F=$$.fifo ; rm -f $F ; mkfifo $F
# first, run main process in background
"$#" & pid=$!
# sleeper process to time out
( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
read sleeper <$F
# control shell. read from fifo.
# final input is "finished". after that
# we clean up. we can get a timeout or a
# signal first.
( exec 0<$F
while : ; do
read input
case $input in
finished)
test $sleeper != 0 && kill $sleeper
rm -f $F
exit 0
;;
timeout)
test $pid != 0 && kill $pid
sleeper=0
;;
signal)
test $pid != 0 && kill $pid
;;
esac
done
) &
# wait for process to end
wait $pid
status=$?
echo finished >$F
return $status
}
cleanup ()
{
echo signal >$$.fifo
}
I've tried to avoid race conditions as far as I can. However, one source of error I couldn't remove is when the process ends near the same time as the timeout. For example, run_with_timeout 2 sleep 2 or run_with_timeout 0 sleep 0. For me, the latter gives an error:
timeout.sh: line 250: kill: (23248) - No such process
as it is trying to kill a process that has already exited by itself.
#Kill command after 10 seconds
timeout 10 command
#If you don't have timeout installed, this is almost the same:
sh -c '(sleep 10; kill "$$") & command'
#The same as above, with muted duplicate messages:
sh -c '(sleep 10; kill "$$" 2>/dev/null) & command'

Kill not killing process if exiting properly

I have a simple bash script which I have written to simplify some work I am doing. All it needs to do is start one process, process_1, as a background process then start another, process_2. Once process_2 is finished I then need to terminate process_1.
process_1 starts a program which does not actually stop unless it receives the kill signal, or CTRL+C when I run it myself. The program is output into a file via {program} {args} > output_file
process_2 can take an arbitrary amount of time depending on the arguments it is given.
Code:
#!/bin/bash
#Call this on exit to kill all background processes
function killJobs () {
#Check process is still running before killing
if kill -0 "$PID"; then
kill $PID
fi
}
...Check given arguments are valid...
#Start process_1
eval "./process_1 ${Arg1} ${Arg2} ${Arg3}" &
PID=$!
#Lay a trap to catch any exits from script
trap killJobs TERM INT
#Start process_2 - sleep for 5 seconds before and after
#Need space between process_1 and process_2 starting and stopping
sleep 5
eval "./process_2 ${Arg1} ${Arg2} ${Arg3} ${Arg4} 2> ${output_file}"
sleep 5
#Make sure background job is killed on exit
killJobs
I check process_1 has been terminated by checking of its output file is still being updated after my script has ended.
If I run the script and then press CTRL+C the script is terminated and process_1 is also killed, the output file is no longer updated.
If I let the script run to its completion without my intervention process_2 and the script both terminate but when I check the output from process_1 it is still being updated.
To check this I put an echo statement just after process_1 is started and another within the if statement of killJobs, so it would only be echoed if kill $PID is called.
Doing this I can see that both ways of exiting start process_1 and then also enter the if statement to kill it. Yet kill does not actually kill the process in the case of normal exit. No error messages are produced either.
You're backgrounding the eval instead of process_1, which sets $! to the PID of the script itself, not to process_1. Change to:
#!/bin/bash
#Call this on exit to kill all background processes
function killJobs () {
#Check process is still running before killing
if kill -0 "$PID"; then
kill $PID
fi
}
...Check given arguments are valid...
#Start process_1
./process_1 ${Arg1} ${Arg2} ${Arg3} &
PID=$!
#Lay a trap to catch any exits from script
trap killJobs TERM INT
#Start process_2 - sleep for 5 seconds before and after
#Need space between process_1 and process_2 starting and stopping
sleep 5
./process_2 ${Arg1} ${Arg2} ${Arg3} ${Arg4} 2> ${output_file}
sleep 5
#Make sure background job is killed on exit
killJobs

Bash: Kill Process after timeout or after an event

I have a script which runs a long running process.
This process is currently stopped after a timeout.
#!/bin/bash
timeout 3600 ./longrunningprocess
My problem is now that this process does not return before the timeout is reached and sometimes I need to stop it earlier.
What do I need?
I want to start some other script in parallel which checks periodically if my longrunningprocess should stop. When this bash script returns, the timeout command should be killed.
Any idea how I could achieve that?
Is there anything like the timeout command? Not with a timespan but a script I could start and which is like the event trigger?
E.g.
#!/bin/bash
fancyCommandKillsSecondCommandIfFirstCommandReturns "./myPeriodicScript.sh" "timeout 3600 ./longrunningprocess"
Thanks!
Edit: Something like "Start 2 Processes in parallel and kill both if one returns" would also work...
Edit2: The answers gave me some ideas for a script:
#!/bin/bash
FirstProcess="${1}"
SecondProcess="${2}"
exec $FirstProcess &
PID1=$!
exec $SecondProcess &
PID2=$!
function killall {
if ps -p $PID1 > /dev/null
then
kill -9 $PID1
fi
if ps -p $PID2 > /dev/null
then
kill -9 $PID2
fi
}
trap killall EXIT
while true; do
if ! ps -p $PID1 > /dev/null
then
exit;
fi
if ! ps -p $PID2 > /dev/null
then
exit;
fi
sleep 5;
done
This kind of does what I want. Is there any native functionality or a better way to do this?
Start the longrunningprocess in the background and remember the pid.
#!/bin/bash
timeout 3600 ./longrunningprocess &
long_pid=$!
./myPeriodicScript.sh
kill -9 ${long_pid}
If you parse the output of the longrunningprocess to determine if the process needs to be killed, then you could do something like this:
#!/bin/bash
FIFO="tmpfifo"
TIMEOUT=10
mkfifo $FIFO
timeout 100 ./longrun &> $FIFO &
PID=$!
while read line; do
echo "Parsing $line see if $PID needs to be killed";
if [ "$line" = "5" ]; then
kill $PID
fi
done < $FIFO
exit
This will pipe all output into a FIFO and start reading from that fifo. In addition, it keeps the PID of the timeout process, so it can be killed.

BASH: Pause and resume a child script

I want to control a child script somehow. I am making a master script which spawns many children scripts and need to RESUME and PAUSE them on demand.
Child
Do stuff
PAUSE
Cleanup
Parent
sleep 10
RESUME child
Is this possible?
AS PER SUGGESTIONS
Trying to do it with signals while the child runs in the background doesn't seem to work.
script1:
#!/bin/bash
"./script2" &
sleep 1
kill -2 "$!"
sleep 1
script2:
#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...' SIGINT
trap 'echo you hit ctrl-\, stoppng...; exit' SIGQUIT
while [ 1 ]
do
echo "Waiting for signal.."
sleep 60000
echo "~~EXIT1"
done
echo "~~EXIT2"
Running:
> ./script1
One way to control individual process scripts is with signals. If you combine SIGINT (ctrl-c) to resume with SIGQUIT (ctrl-) to kill then the child process looks like this:
#!/bin/sh
trap 'echo you hit ctrl-c, waking up...' SIGINT
trap 'echo you hit ctrl-\, stoppng...; exit' SIGQUIT
while (true)
do
echo "do the work..."
# pause for a very long time...
sleep 600000
done
If you run this script, and hit ctrl-c, the work continues. If you hit ctrl-\, the script stops.
You would want to run this in the background then send kill -2 $pid to resume and kill -3 $pid to stop (or kill -9 would work) where $pid is the child process's process id.
Here is a good bash signals reference: http://www.ibm.com/developerworks/aix/library/au-usingtraps/
-- here is the parent script...
#!/bin/sh
./child.sh &
pid=$!
echo "child running at $pid"
sleep 2
echo "interrupt the child at $pid"
kill -INT $pid # you could also use SIGCONT
sleep 2
echo "kill the child at $pid"
kill -QUIT $pid
One way is to create a named pipe per child:
mkfifo pipe0
Then redirect stdin of the child to read from the pipe:
child < pipe0
to stop the child:
read _
(the odd _ is just there for read to have a place to store the empty line it will read).
to resume the child:
echo > pipe0
A more simple approach would be to save the stdin which gets passed to the child in form a pure file descriptor but I don't know the exact syntax anymore and can't google a good example ATM.

How to capture Ctrl-C and use it to exit an endless loop properly

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"
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.
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:
#!/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"
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"
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.

Resources