| name | parallel-processing-patterns |
| description | Parallel and concurrent processing patterns in bash including GNU Parallel, xargs, job pools, and async patterns (2025) |
CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Parallel Processing Patterns in Bash (2025)
Overview
Comprehensive guide to parallel and concurrent execution in bash, covering GNU Parallel, xargs parallelization, job control, worker pools, and modern async patterns for maximum performance.
GNU Parallel (Recommended)
Installation
sudo apt-get install parallel
brew install parallel
wget https://ftp.gnu.org/gnu/parallel/parallel-latest.tar.bz2
tar -xjf parallel-latest.tar.bz2
cd parallel-*
./configure && make && sudo make install
Basic Usage
#!/usr/bin/env bash
set -euo pipefail
parallel gzip ::: *.txt
find . -name "*.jpg" | parallel convert {} -resize 50% resized/{}
parallel -j 8 process_file ::: *.dat
cat urls.txt | parallel -j 10 wget -q
parallel echo ::: A B C ::: 1 2 3
parallel echo ::: A B C :::+ 1 2 3
Input Handling
#!/usr/bin/env bash
set -euo pipefail
parallel -a input.txt process_line
parallel -a file1.txt -a file2.txt 'echo {1} {2}'
cat data.tsv | parallel --colsep '\t' 'echo Name: {1}, Value: {2}'
cat data.csv | parallel --header : --colsep ',' 'echo {name}: {value}'
find . -name "*.txt" -print0 | parallel -0 wc -l
cat huge_file.txt | parallel --pipe -N1000 'wc -l'
Replacement Strings
#!/usr/bin/env bash
set -euo pipefail
parallel echo 'Processing: {}' ::: file1.txt file2.txt
parallel echo '{.}' ::: file.txt file.csv
parallel echo '{/}' ::: /path/to/file.txt
parallel echo '{//}' ::: /path/to/file.txt
parallel echo '{/.}' ::: /path/to/file.txt
parallel echo 'Job {#}: {}' ::: A B C
parallel -j 2 'echo "Slot {%}: {}"' ::: A B C D E
parallel 'convert {} -resize 50% {//}/thumb_{/.}.jpg' ::: *.png
Progress and Logging
#!/usr/bin/env bash
set -euo pipefail
parallel --bar process_item ::: {1..100}
parallel --progress process_item ::: {1..100}
parallel --verbose gzip ::: *.txt
parallel --joblog jobs.log gzip ::: *.txt
parallel --joblog jobs.log --resume gzip ::: *.txt
parallel --results results_dir 'echo {1} + {2}' ::: 1 2 3 ::: 4 5 6
Resource Management
#!/usr/bin/env bash
set -euo pipefail
parallel -j "$(nproc)" process_item ::: {1..1000}
parallel -j '-2' process_item ::: {1..1000}
parallel -j '50%' process_item ::: {1..1000}
parallel --load 80% process_item ::: {1..1000}
parallel --memfree 2G process_item ::: {1..1000}
parallel -j 4 --delay 0.5 wget ::: url1 url2 url3 url4
parallel --timeout 60 long_process ::: {1..100}
parallel --retries 3 flaky_process ::: {1..100}
Distributed Execution
#!/usr/bin/env bash
set -euo pipefail
parallel --sshloginfile servers.txt process_item ::: {1..1000}
parallel --sshloginfile servers.txt --transferfile {} process {} ::: *.dat
parallel --sshloginfile servers.txt --return {.}.result process {} ::: *.dat
parallel --sshloginfile servers.txt --transfer --return {.}.out --cleanup \
'process {} > {.}.out' ::: *.dat
export MY_VAR="value"
parallel --env MY_VAR --sshloginfile servers.txt 'echo $MY_VAR' ::: A B C
Complex Pipelines
#!/usr/bin/env bash
set -euo pipefail
cat huge_file.txt | parallel --pipe -N1000 'sort | uniq -c'
cat data.bin | parallel --pipe --block 10M 'process_chunk'
parallel --keep-order 'sleep $((RANDOM % 3)); echo {}' ::: A B C D E
parallel --group 'for i in 1 2 3; do echo "Job {}: line $i"; done' ::: A B C
parallel --tag 'echo "output from {}"' ::: A B C
parallel --ungroup 'echo "Starting {}"; sleep 1; echo "Done {}"' ::: A B C
xargs Parallelization
Basic Parallel xargs
#!/usr/bin/env bash
set -euo pipefail
find . -name "*.txt" | xargs -P 4 -I {} gzip {}
echo {1..100} | xargs -n 10 -P 4 echo "Batch:"
find . -name "*.txt" -print0 | xargs -0 -P 4 -I {} process {}
cat urls.txt | xargs -P 10 -n 5 wget -q
echo {1..1000} | xargs -P 4 --max-args=50 echo
xargs with Complex Commands
#!/usr/bin/env bash
set -euo pipefail
find . -name "*.jpg" -print0 | \
xargs -0 -P 4 -I {} sh -c 'convert "$1" -resize 50% "thumb_$(basename "$1")"' _ {}
paste file1.txt file2.txt | \
xargs -P 4 -n 2 sh -c 'diff "$1" "$2" > "diff_$(basename "$1" .txt).patch"' _
find . -name "*.log" -print0 | \
xargs -0 -P 4 -n 100 tar -czvf logs_batch.tar.gz
find . -name "*.dat" -print0 | \
xargs -0 -P 4 -I {} sh -c 'process "$1" || echo "Failed: $1" >> failures.log' _ {}
Job Control Patterns
Background Job Management
#!/usr/bin/env bash
set -euo pipefail
declare -a PIDS=()
for item in {1..10}; do
process_item "$item" &
PIDS+=($!)
done
for pid in "${PIDS[@]}"; do
wait "$pid"
done
echo "All jobs complete"
wait -n
echo "At least one job complete"
Job Pool with Semaphore
#!/usr/bin/env bash
set -euo pipefail
MAX_JOBS=4
job_count=0
run_with_limit() {
local cmd=("$@")
while ((job_count >= MAX_JOBS)); do
wait -n 2>/dev/null || true
((job_count--))
done
"${cmd[@]}" &
((job_count++))
}
for item in {1..20}; do
run_with_limit process_item "$item"
done
wait
FIFO-Based Job Pool
#!/usr/bin/env bash
set -euo pipefail
MAX_JOBS=4
JOB_FIFO="/tmp/job_pool_$$"
mkfifo "$JOB_FIFO"
trap 'rm -f "$JOB_FIFO"' EXIT
exec 3<>"$JOB_FIFO"
for ((i=0; i<MAX_JOBS; i++)); do
echo >&3
done
run_with_slot() {
local cmd=("$@")
read -u 3
{
"${cmd[@]}"
echo >&3
} &
}
for item in {1..20}; do
run_with_slot process_item "$item"
done
wait
exec 3>&-
Worker Pool Pattern
#!/usr/bin/env bash
set -euo pipefail
WORK_QUEUE="/tmp/work_queue_$$"
RESULT_QUEUE="/tmp/result_queue_$$"
NUM_WORKERS=4
mkfifo "$WORK_QUEUE" "$RESULT_QUEUE"
trap 'rm -f "$WORK_QUEUE" "$RESULT_QUEUE"' EXIT
worker() {
local id="$1"
while read -r task; do
[[ "$task" == "STOP" ]] && break
local result
result=$(process_task "$task" 2>&1)
echo "RESULT:$id:$task:$result"
done
}
for ((i=0; i<NUM_WORKERS; i++)); do
worker "$i" < "$WORK_QUEUE" > "$RESULT_QUEUE" &
done
collect_results() {
while read -r line; do
[[ "" == ]] &&
>> results.txt
<
} &
COLLECTOR_PID=$!
{
task ;
((i=; i<NUM_WORKERS; i++));
} >
>
Modern Async Patterns
Promise-Like Pattern
#!/usr/bin/env bash
set -euo pipefail
async() {
local result_var="$1"
shift
local cmd=("$@")
local result_file
result_file=$(mktemp)
{
if "${cmd[@]}" > "$result_file" 2>&1; then
echo "0" >> "$result_file.status"
else
echo "$?" >> "$result_file.status"
fi
} &
eval "${result_var}_pid=$!"
eval "${result_var}_file='$result_file'"
}
await() {
local result_var="$1"
local pid_var="${result_var}_pid"
local file_var="${result_var}_file"
status
status=$( )
-f
}
async result1 curl -s
async result2 curl -s
async result3 process_local_data
data1=$(await result1)
data2=$(await result2)
data3=$(await result3)
Event Loop Pattern
#!/usr/bin/env bash
set -euo pipefail
declare -A TASKS
declare -A TASK_RESULTS
TASK_COUNTER=0
schedule() {
local cmd=("$@")
local task_id=$((++TASK_COUNTER))
local output_file="/tmp/task_${task_id}_$$"
"${cmd[@]}" > "$output_file" 2>&1 &
TASKS[$task_id]=$!
TASK_RESULTS[$task_id]="$output_file"
echo "$task_id"
}
is_complete() {
local task_id="$1"
! kill -0 "${TASKS[$task_id]}" 2>/dev/null
}
get_result() {
local task_id="$1"
wait "${TASKS[$task_id]}" 2>/dev/null || true
cat "${TASK_RESULTS[$task_id]}"
rm -f "${TASK_RESULTS[$task_id]}"
}
run_event_loop() {
pending=()
(( > ));
still_pending=()
task_id ;
is_complete ;
result
result=$(get_result )
on_task_complete
still_pending+=()
pending=()
(( > )) && 0.1
}
() {
task_id=
result=
}
Fan-Out/Fan-In Pattern
#!/usr/bin/env bash
set -euo pipefail
fan_out() {
local -n items="$1"
local workers="$2"
local worker_func="$3"
local chunk_size=$(( (${#items[@]} + workers - 1) / workers ))
local pids=()
for ((i=0; i<workers; i++)); do
local start=$((i * chunk_size))
local chunk=("${items[@]:start:chunk_size}")
if ((${#chunk[@]} > 0)); then
$worker_func "${chunk[@]}" &
pids+=($!)
fi
done
echo "${pids[*]}"
}
fan_in() {
local -a pids=($1)
local results=()
for pid in "${pids[@]}"; do
wait "$pid"
}
() {
items=()
item ;
}
data=({1..100})
pids=$(fan_out data 4 process_chunk)
fan_in
Map-Reduce Pattern
#!/usr/bin/env bash
set -euo pipefail
parallel_map() {
local -n input="$1"
local map_func="$2"
local workers="${3:-$(nproc)}"
printf '%s\n' "${input[@]}" | \
parallel -j "$workers" "$map_func"
}
reduce() {
local reduce_func="$1"
local accumulator="$2"
while IFS= read -r value; do
accumulator=$($reduce_func "$accumulator" "$value")
done
echo "$accumulator"
}
square() { echo $(($1 * $1)); }
add() { echo $(($1 + $2)); }
numbers=({1..100})
sum_of_squares=$(
parallel_map numbers square 4 | reduce add 0
)
() {
| | -c
}
() {
-k2 | awk
}
large_text.txt | \
parallel --pipe -N1000 word_count_map | \
word_count_reduce
Performance Optimization
Batch Processing
#!/usr/bin/env bash
set -euo pipefail
optimal_batch_process() {
local items=("$@")
local batch_size=100
local workers=$(nproc)
printf '%s\n' "${items[@]}" | \
parallel --pipe -N"$batch_size" -j"$workers" '
while IFS= read -r item; do
process_item "$item"
done
'
}
dynamic_batch() {
local mem_available
mem_available=$(free -m | awk '/^Mem:/ {print $7}')
local batch_size=$((mem_available / 100))
((batch_size < 10)) && batch_size=10
((batch_size > 1000)) && batch_size=1000
parallel --pipe -N"$batch_size" process_batch
}
I/O Optimization
#!/usr/bin/env bash
set -euo pipefail
setup_fast_temp() {
local tmpdir="/dev/shm/parallel_$$"
mkdir -p "$tmpdir"
trap 'rm -rf "$tmpdir"' EXIT
echo "$tmpdir"
}
buffered_parallel() {
local input="$1"
local tmpdir
tmpdir=$(setup_fast_temp)
split -l 1000 "$input" "$tmpdir/chunk_"
parallel process_chunk {} ::: "$tmpdir"/chunk_*
cat "$tmpdir"/result_* > output.txt
}
no_disk_parallel() {
command | parallel --pipe process
}
CPU Affinity
#!/usr/bin/env bash
set -euo pipefail
cpu_pinned_parallel() {
local num_cpus
num_cpus=$(nproc)
for ((cpu=0; cpu<num_cpus; cpu++)); do
taskset -c "$cpu" process_worker "$cpu" &
done
wait
}
numa_parallel() {
local num_nodes
num_nodes=$(numactl --hardware | grep "available:" | awk '{print $2}')
for ((node=0; node<num_nodes; node++)); do
numactl --cpunodebind="$node" --membind="$node" \
process_chunk "$node" &
done
wait
}
Error Handling
Graceful Failure Handling
#!/usr/bin/env bash
set -euo pipefail
declare -A FAILURES
parallel_with_retry() {
local max_retries=3
local items=("$@")
for item in "${items[@]}"; do
local retries=0
local success=false
while ((retries < max_retries)) && ! $success; do
if process_item "$item"; then
success=true
else
((retries++))
echo "Retry $retries for $item" >&2
sleep $((retries * 2))
fi
done
if ! $success; then
FAILURES["$item"]="Failed after $max_retries retries"
fi
done &
wait
}
report_failures() {
if ((${#FAILURES[@]} > ));
>&2
item ;
>&2
1
}
Cancellation Support
#!/usr/bin/env bash
set -euo pipefail
CANCELLED=false
declare -a WORKER_PIDS=()
cancel_all() {
CANCELLED=true
for pid in "${WORKER_PIDS[@]}"; do
kill "$pid" 2>/dev/null || true
done
}
trap cancel_all SIGINT SIGTERM
cancellable_worker() {
local id="$1"
while ! $CANCELLED; do
if work=$(get_next_work); then
process_work "$work"
else
sleep 0.1
fi
done
}
for ((i=0; i<NUM_WORKERS; i++)); do
cancellable_worker "$i" &
WORKER_PIDS+=($!)
done
wait || true
Resources
Master parallel processing for efficient multi-core utilization and faster script execution.