Sequences of numbers can be created using seq in bash.
seq 2 4
2
3
4
The & will send scripts to the background, and jobs can list what’s currently running. This will put three sleep commands in the background and list the number of jobs running.
sleep 1 & sleep 1 & sleep 1 & jobs -r | wc -l | grep -Eow '[0-9]+'
3
In the words of chatgpt:
The
jobs -rcommand in bash lists running jobs that are continued in the background
-E: Uses extended regex syntax instead of basic regex syntax.
-o: Only prints the matched portion of the line, rather than the whole line.
-w: Only matches whole words. For example, if you use-wto search for “cat” in a file, it won’t match “concatenate”.
The following will launch 4 commands in the background (&), using a subshell (the parens), each echo-ing <number> done when it is done. To make a shell script wait for background jobs to finish, use the wait command. This will take ~2s to finish as each command sleeps for 2 seconds.
for i in $(seq 1 4); do
  (sleep 2 && echo "$i done") &
  echo waiting
done;
wait # takes ~2s
waiting
waiting
waiting
waiting
4 done
2 done
1 done
3 done
The following will read each line received from stdin into the variable i and echo its number.
seq 4 | while read i; do echo line $i; done
line 1
line 2
line 3
line 4
Another way of acomplishing this is using process substitution
. This allows the output of a command to be referred like its read from a file, by wrapping the command in <( ), like this
while read i; do echo line $i; done < <(seq 4)
line 1
line 2
line 3
line 4
We can combine the above into a while loop that will wait if there are max_bg_jobs or more background jobs running, before launching another one. wait -n will wait for a single background job to finish. (For wait -n to work on macos ventura you need an upgraded bash, e.g. by brew install bash to install 5.2 as of may 2023)
The following will keep a maximum number of 2 jobs going at once. A final wait ensures we wait for the last background job (or jobs, depending on how many you run and max_bg_jobs) to finish before the program exits.
#!/usr/bin/env bash
max_bg_jobs=2
while read l; do
    P="$(jobs -r | wc -l | grep -Eow '[0-9]+')"
    if [[ $P -ge $max_bg_jobs ]]; then
        wait -n # wait for single job
    fi
    (echo "$(date +'%H:%M:%S') starting $l" \
        && sleep 2 \
        && echo "$(date +'%H:%M:%S') done $l") &
done < <(seq 5)
wait # wait for remaining jobs
13:01:33 starting 1
13:01:33 starting 2
13:01:35 done 1
13:01:35 done 2
13:01:35 starting 3
13:01:35 starting 4
13:01:37 done 3
13:01:37 done 4
13:01:37 starting 5
13:01:39 done 5
Change the echo-s that are sent into the background to e.g. curl, and you have yourself a machinery that can limit the number of concurrent requests to a number of your choice.