Both your example and the accepted answer are overly complicated, why do you not only use timeout since that is exactly its use case? The timeout command even has an inbuilt option (-k) to send SIGKILL after sending the initial signal to terminate the command (SIGTERM by default) if the command is still running after sending the initial signal (see man timeout).
If the script doesn't necessarily require to wait and resume control flow after waiting it's simply a matter of
timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]
If it does, however, that's just as easy by saving the timeout PIDs instead:
pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]
E.g.
$ cat t.sh
#!/bin/bash
echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"
.
$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script
Answer from Adrian Frühwirth on Stack OverflowBoth your example and the accepted answer are overly complicated, why do you not only use timeout since that is exactly its use case? The timeout command even has an inbuilt option (-k) to send SIGKILL after sending the initial signal to terminate the command (SIGTERM by default) if the command is still running after sending the initial signal (see man timeout).
If the script doesn't necessarily require to wait and resume control flow after waiting it's simply a matter of
timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]
If it does, however, that's just as easy by saving the timeout PIDs instead:
pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]
E.g.
$ cat t.sh
#!/bin/bash
echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"
.
$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script
Write the PIDs to files and start the apps like this:
pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo
pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!
wait $pid
kill $killerPid
That would create another process that sleeps for the timeout and kills the process if it hasn't completed so far.
If the process completes faster, the PID file is deleted and the killer process is terminated.
killChildrenOf is a script that fetches all processes and kills all children of a certain PID. See the answers of this question for different ways to implement this functionality: Best way to kill all child processes
If you want to step outside of BASH, you could write PIDs and timeouts into a directory and watch that directory. Every minute or so, read the entries and check which processes are still around and whether they have timed out.
EDIT If you want to know whether the process has died successfully, you can use kill -0 $pid
EDIT2 Or you can try process groups. kevinarpe said: To get PGID for a PID(146322):
ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'
In my case: 145974. Then PGID can be used with a special option of kill to terminate all processes in a group: kill -- -145974
bash - Difference between wait and sleep - Stack Overflow
background process - Bash wait for all subprocesses of script - Unix & Linux Stack Exchange
bash - Significance of `[...] & wait $!` - Unix & Linux Stack Exchange
How can I make WAIT to run properly in this case?
Does the Bash wait command work in sh and zsh?
How do I add a timeout to the Bash wait command?
What is the difference between wait and sleep in Bash?
Videos
Use the sleep command.
Example:
sleep .5 # Waits 0.5 second.
sleep 5 # Waits 5 seconds.
sleep 5s # Waits 5 seconds.
sleep 5m # Waits 5 minutes.
sleep 5h # Waits 5 hours.
sleep 5d # Waits 5 days.
One can also employ decimals when specifying a time unit; e.g. sleep 1.5s
For those looking for the Bash equivalent of Windows Powershell/CMD's pause command.
In Bash use read with option -p specifying a prompt like:
read -p "Press enter to continue"
wait waits for a process to finish; sleep sleeps for a certain amount of seconds.
wait is a BASH built-in command. From man bash:
wait [n ...]
Wait for each specified process and return its termination sta-
tus. Each n may be a process ID or a job specification; if a
job spec is given, all processes in that job's pipeline are
waited for. If n is not given, all currently active child pro-
cesses are waited for, and the return status is zero. If n
specifies a non-existent process or job, the return status is
127. Otherwise, the return status is the exit status of the
last process or job waited for.
sleep is not a shell built-in command. It is a utility that delays for a specified amount of time.
The sleep command may support waiting in various units of time. GNU coreutils 8.4 man sleep says:
SYNOPSIS
sleep NUMBER[SUFFIX]...
DESCRIPTION
Pause for NUMBER seconds. SUFFIX may be ‘s’ for seconds (the default),
‘m’ for minutes, ‘h’ for hours or ‘d’ for days. Unlike most implemen-
tations that require NUMBER be an integer, here NUMBER may be an arbi-
trary floating point number. Given two or more arguments, pause for
the amount of time specified by the sum of their values.
Yes, it's enough to use a single wait with no arguments at the end to wait for all background jobs to terminate.
Note that background jobs started in a subshell would need to be waited for in the same subshell that they were started in. You have no instance of this in the code that you show.
Note also that the question that you link to asks about checking the exit status of the background jobs. This would require wait to be run once for each background job (with the PID of that job as an argument).
In my opinion, even though a single wait without parameters should be sufficient, this is not a good practice (relay on a default behaviour). I would collect the pid returned from each call, and wait for these pids explicitly.
I'm trying to wait for a couple of processes to finish before executing other commands, including apt update coming next in the queue, as follows:
pop-upgrade release upgrade & # Pop!_OS tool checking for own OS updates _PID=$(pidof pop-upgrade) wait $_PID apt update && apt full-upgrade -y
Here is the actual code: https://codeshare.io/Pd1xwM
This occasionally returns this output from apt:
E: Could not get lock /var/lib/apt/lists/lock. It is held by process 1485 (packagekitd) E: Unable to lock directory /var/lib/apt/lists/
To circumvent this, I sandwiched another wait:
pop-upgrade release upgrade & # Pop!_OS tool checking for own OS updates _PID=$(pidof pop-upgrade) wait $_PID _PID=$(pidof packagekitd) wait $_PID apt update && apt full-upgrade -y
But now wait throws this error message preventing the system update:
wait: pid 1485 (meaning `packagekitd`) is not a child of this shell
The thing is I'm running these commands from a script as sudo. But don't know how to deal with parents and children thingy.
How can I make wait to do its job(s) properly? Or, maybe, how can I know which process(es) prevent apt work as it should?
Thank you!