Based on the answer from @KamilCuk (https://stackoverflow.com/a/64393146/5430476), finally, I got this version:
#!/bin/bash
count=0
maxcount=5
for ((i=0; i<10; ++i)); do
{ sleep 0.$i; echo "Job $i done"; } &
count=$((count + 1))
if ((count >= maxcount)); then
wait
count=0
fi
done
# Wait for remaining background processes
wait
Maybe life would get easier if I installed the GNU parallel
like the comments suggested, but what I want to do is to finish a simple backup job in a container. I don't want to install extra commands other than bash as much as possible.
So I wrapped this into a function, like:
#!/bin/bash
job() {
local i=$1 # Job index
shift
local extra_args=("$@")
echo "Job $i started with args: ${extra_args[*]}"
sleep $((RANDOM % 5)) # some works
echo "Job $i finished"
}
parallel_spawn_with_limits() {
local max_limit_per_loop=$1; shift
local job=$1; shift
local count=0
for ((i=0; i<10; ++i)); do
{ "$job" "$i" "$@" & } # Run job in background with arguments
count=$((count + 1))
if ((count >= max_limit_per_loop)); then
wait
count=0
fi
done
wait # Ensure remaining jobs finish
}
Then call like this:
# example usage
parallel_spawn_with_limits 3 job "extra_arg1" "extra_arg2"
[1] 13199
[2] 13200
[3] 13201
Job 1 started with args: extra_arg1 extra_arg2
Job 2 started with args: extra_arg1 extra_arg2
Job 0 started with args: extra_arg1 extra_arg2
Job 0 finished
[1] Done "$job" "$i" "$@"
Job 2 finished
Job 1 finished
[2]- Done "$job" "$i" "$@"
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 13479
[2] 13480
Job 3 started with args: extra_arg1 extra_arg2
[3] 13481
Job 4 started with args: extra_arg1 extra_arg2
Job 5 started with args: extra_arg1 extra_arg2
Job 4 finished
Job 5 finished
Job 3 finished
[1] Done "$job" "$i" "$@"
[2]- Done "$job" "$i" "$@"
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 14004
[2] 14005
[3] 14006
Job 6 started with args: extra_arg1 extra_arg2
Job 7 started with args: extra_arg1 extra_arg2
Job 8 started with args: extra_arg1 extra_arg2
Job 7 finished
Job 6 finished
[1] Done "$job" "$i" "$@"
[2]- Done "$job" "$i" "$@"
Job 8 finished
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 14544
Job 9 started with args: extra_arg1 extra_arg2
Job 9 finished
[1]+ Done "$job" "$i" "$@"
Depending on your needs, you may need to add a trap function or abstract the 10
in the for loop into a new variable.