0Pricing
DevOps Bootcamp · Lesson

Orchestrating Workloads with GNU parallel

Distribute large input sets across cores with GNU parallel, job slots, and result ordering.

Orchestrating Workloads with GNU parallel is a free DevOps Bootcamp lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is GNU parallel and Why Use It?

GNU parallel is a shell tool that lets you run jobs in parallel on one or multiple machines. Instead of processing a large list of items one by one in a for loop, parallel spreads that work across all available CPU cores simultaneously.

  • Speed: A task that takes 8 minutes sequentially can finish in ~1 minute on an 8-core machine.
  • Simplicity: It accepts input from stdin, files, or argument lists — no manual process management.
  • Safety: Output from different jobs is kept separate; results are never interleaved.

Install it with sudo apt install parallel (Debian/Ubuntu) or brew install parallel (macOS). Verify with parallel --version.

Your First parallel Command

The simplest form of parallel reads items from stdin and runs a command for each one. The placeholder {} represents the current input item.

The example below compresses five log files concurrently using gzip. Without parallel, each file would be compressed one after the other. With it, up to N files (where N = number of CPU cores) are compressed at the same time.

#!/usr/bin/env bash
# Create sample files first
for i in 1 2 3 4 5; do
  dd if=/dev/urandom bs=1M count=2 of="log_${i}.txt" 2>/dev/null
done

# Compress all of them in parallel
ls log_*.txt | parallel gzip {}

echo "Done. Compressed files:"
ls log_*.txt.gz

Controlling Job Slots with -j

By default, parallel runs one job per CPU core. You can override this with the -j (or --jobs) flag.

  • -j 4 — run exactly 4 jobs at once
  • -j 0 — run as many jobs as there are inputs (use carefully!)
  • -j 200% — run twice as many jobs as there are CPU cores (useful for I/O-bound work)
  • -j 50% — use only half the available cores

For CPU-bound tasks, -j $(nproc) is often optimal. For network or disk I/O tasks, you can safely exceed the core count because jobs spend most of their time waiting.

#!/usr/bin/env bash
# Show how many cores are available
echo "CPU cores: $(nproc)"

# Run 8 sleep jobs but limit to 3 at a time
# -j 3 means at most 3 jobs run simultaneously
seq 1 8 | parallel -j 3 'echo "Starting job {}"; sleep 1; echo "Done job {}"'

echo "All jobs finished."

Reading Input from Files and Arguments

parallel is flexible about where it reads its input list. You are not limited to piping from stdin.

  • From a file: parallel -a urls.txt wget {}
  • Inline argument list: parallel echo ::: apple banana cherry
  • Multiple argument sources (cartesian product): parallel echo {1}-{2} ::: a b c ::: 1 2 — produces a-1, a-2, b-1, b-2, c-1, c-2
  • From stdin explicitly: cat list.txt | parallel -j4 process {}

The ::: separator tells parallel to use the following values as an argument source rather than reading from stdin.

#!/usr/bin/env bash
# Inline list with :::
parallel echo 'Hello from {}' ::: Alice Bob Carol Dave

echo '---'

# Cartesian product: combine two lists
# Generates: dev-v1, dev-v2, prod-v1, prod-v2
parallel echo 'Deploy {1} to env {2}' ::: v1 v2 ::: dev prod

Placeholders: Manipulating Input Tokens

parallel provides several placeholder substitutions that let you extract parts of the input string automatically — very useful when inputs are file paths.

  • {} — the full input item
  • {.} — input without its file extension (report.csvreport)
  • {/} — basename only (strips the directory path)
  • {//} — directory path only
  • {/.} — basename without extension

These eliminate the need for basename / dirname calls inside the job command, making pipelines cleaner and faster.

#!/usr/bin/env bash
# Demonstrate placeholder substitutions
parallel --dry-run 'convert {} -resize 800x600 {.}_thumb.jpg' \
  ::: /photos/vacation/beach.png /photos/work/team.png

# {.}  strips extension: /photos/vacation/beach
# Result command shown (--dry-run does NOT execute):
#  convert /photos/vacation/beach.png -resize 800x600 /photos/vacation/beach_thumb.jpg
#  convert /photos/work/team.png      -resize 800x600 /photos/work/team_thumb.jpg
echo 'No files were changed (dry run)'

Keeping Output Ordered with --keep-order

When jobs finish at different times, their stdout output appears in whichever order they complete. This can make logs hard to read and downstream parsing unreliable.

Two flags control output ordering:

  • --keep-order (-k) — prints each job's output in the same order as the input, even if a later job finishes first. The output is buffered until earlier jobs complete.
  • --line-buffer — a middle ground: outputs complete lines as they arrive, without waiting for job completion, but never interleaves half-written lines.

Use -k when the downstream consumer expects results in input order (e.g., building a sorted report). Omit it when order does not matter and you want to see results as soon as possible.

#!/usr/bin/env bash
# Without -k: output order is unpredictable
echo '--- Without --keep-order ---'
seq 5 1 1 | parallel 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'

echo

# With -k: output always appears as 5, 4, 3, 2, 1
echo '--- With --keep-order (-k) ---'
seq 5 1 1 | parallel -k 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'

Grouping Output to Avoid Interleaving

Even with ordered output, if a job prints multiple lines, those lines can interleave with lines from another job running at the same time. parallel solves this automatically by buffering each job's complete stdout and stderr, then printing them as a single atomic block once the job finishes.

This behaviour is on by default. You can disable it with --ungroup if you need live streaming output (e.g., long-running jobs with progress bars), but then interleaving becomes possible again.

  • Default: output is grouped per job — safe for parsing.
  • --ungroup: output streams live — good for interactive monitoring.
  • --line-buffer: compromise — lines are never split, but jobs can interleave at line boundaries.

Passing Arguments Inside Shell Functions

Sometimes the work you want to parallelise is more than a single command — it is a multi-step shell function. You can pass a function to parallel using export -f combined with env_parallel, or by calling bash -c directly.

The safest portable approach for complex jobs is the bash -c '...' pattern. The {} placeholder is passed as $1 when you end with _ {}.

#!/usr/bin/env bash
# Define a multi-step processing function
process_item() {
  local item="$1"
  echo "[START] $item"
  # Simulate two steps
  sleep 0.2
  local result=$(echo "$item" | tr '[:lower:]' '[:upper:]')
  echo "[END]   $item -> $result"
}

export -f process_item

# Run the function in parallel for each input
echo 'alpha beta gamma delta epsilon' | tr ' ' '\n' \
  | parallel -j 3 process_item {}

Throttling and Retry with --delay and --retries

When hitting external services (APIs, remote servers, databases) in parallel, you often need rate limiting and fault tolerance.

  • --delay N — wait N seconds between starting each new job (fractional values like 0.5 are allowed). Prevents flooding a service.
  • --retries N — if a job exits with a non-zero status, retry it up to N times before giving up. Each retry counts as a new job slot.
  • --timeout N — kill a job if it runs longer than N seconds. Combined with --retries this handles hanging jobs gracefully.

Example: downloading 50 URLs with at most 4 concurrent connections, a 0.5 s ramp-up delay between starts, and 3 retries on failure.

#!/usr/bin/env bash
# Simulate downloading URLs with throttling and retries
# (using echo instead of curl so this is self-contained)

download_url() {
  local url="$1"
  # Randomly fail ~30% of the time to demo --retries
  if (( RANDOM % 10 < 3 )); then
    echo "FAIL: $url" >&2
    return 1
  fi
  echo "OK:   $url downloaded"
}

export -f download_url

printf 'https://example.com/file%d\n' $(seq 1 10) \
  | parallel -j 4 --delay 0.2 --retries 3 download_url {}

echo 'All downloads attempted.'

Distributing Work Across Remote Hosts with --sshloginfile

parallel can transparently distribute jobs to remote machines over SSH, making it a lightweight cluster compute tool without any special cluster software.

  • --sshlogin user@host — run jobs on a specific remote host.
  • --sshloginfile machines.txt — read a list of hosts from a file (one per line). Use : as a special entry to also use the local machine.
  • --transfer — copy the input file to the remote host before processing.
  • --return {} — copy the result file back after the job finishes.
  • --cleanup — delete transferred files from the remote host after retrieval.

The remote host must have parallel installed and SSH key-based authentication configured (no password prompts).

Progress Reporting and Logging

For long-running workloads it is essential to monitor progress and diagnose failures after the fact.

  • --progress — prints a live summary line showing how many jobs are running, completed, and remaining.
  • --eta — estimates time to completion based on average job duration so far.
  • --joblog results.log — writes a tab-separated log file with one row per completed job, including exit code, runtime, and the command run. Invaluable for auditing failures.
  • --resume --joblog results.log — skip jobs that already appear (with exit code 0) in the log file. If a batch run is interrupted, you can resume it without re-doing successful work.

The --joblog + --resume combination is one of the most powerful features of GNU parallel for robust production pipelines.

#!/usr/bin/env bash
LOGFILE="/tmp/parallel_demo_$$.log"

# Run jobs and record results to a log
seq 1 12 | parallel \
  --jobs 4 \
  --progress \
  --joblog "$LOGFILE" \
  'sleep 0.1; echo "Processed item {}"'

echo
echo '=== Job Log (first 5 entries) ==='
head -6 "$LOGFILE"

# Show only failed jobs (exit value != 0)
echo '=== Failed jobs ==='
awk 'NR>1 && $7 != 0 { print $0 }' "$LOGFILE" || echo '(none)'

rm -f "$LOGFILE"

Knowledge Check: Job Slot Flags

Test your understanding of how parallel controls concurrency.

Lesson Recap: Orchestrating Workloads with GNU parallel

You have covered the core toolkit for distributing large input sets across CPU cores with GNU parallel. Here is what to take away:

  • Basic usage: pipe a list into parallel command {}{} is replaced by each input item.
  • Job slots (-j): control concurrency precisely — use core count for CPU-bound work, higher percentages for I/O-bound work.
  • Placeholders ({.}, {/}, {//}, {/.}) cleanly extract path components without extra commands.
  • Output control: -k preserves input order; default grouping prevents interleaved lines; --ungroup gives live streaming.
  • Resilience: --retries, --timeout, and --delay make parallel pipelines robust against flaky jobs and rate limits.
  • Auditability: --joblog records every job's outcome; --resume lets you pick up where you left off after an interruption.
  • Scale-out: --sshloginfile distributes jobs to remote machines over SSH with zero cluster overhead.

Mastering these options turns parallel into a production-grade workload orchestrator built right into your shell.

Frequently asked questions

Is the “Orchestrating Workloads with GNU parallel” lesson free?

Yes — the full text of “Orchestrating Workloads with GNU parallel” is free to read here on the web, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.

What will I learn in “Orchestrating Workloads with GNU parallel”?

Distribute large input sets across cores with GNU parallel, job slots, and result ordering. You practise DevOps Bootcamp with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DevOps Bootcamp?

No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Orchestrating Workloads with GNU parallel” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DevOps Bootcamp lesson?

Yes. Every DevOps Bootcamp lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Profiling Scripts and Avoiding Useless Subshells
  2. Parallelism with xargs -P and Background Jobs
  3. Orchestrating Workloads with GNU parallel
  4. Streaming Pipelines and Named Pipes for Throughput
← Back to DevOps Bootcamp