Posted on
Artificial Intelligence

Bash Automation for Large Data Sets

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Bash Automation for Large Data Sets: From Hours to Minutes

If you’ve ever watched a progress bar crawl while a GUI tool tries to ingest a multi‑gigabyte file, you know the pain. Large data sets strain memory, exhaust CPUs, and expose every inefficiency in your workflow. The good news: with Bash and a handful of battle‑tested CLI tools, you can stream, filter, aggregate, and ship terabytes with surprising speed and reliability—often without writing a single line of Python.

This article shows why Bash is still a top-tier choice for big data plumbing and gives you actionable patterns, commands, and installation steps to go from “stuck” to “shipped.”

Why Bash for big data?

  • Stream-first, low memory: Unix tools process data line-by-line, so you don’t need to load everything into RAM.

  • Composable: Pipes let you stitch specialized tools into robust, fast pipelines.

  • Reproducible and portable: Shell scripts run the same on dev boxes, servers, and containers.

  • Parallel by default: It’s easy to fan out work across CPU cores and merge results.

  • Transparent performance: You can see and measure each stage with standard tools.

Install the toolkit

The examples below use common packages. Install them first. If a package isn’t found, search it by name (e.g., apt search, dnf search, or zypper search) or enable the appropriate repo.

Tools we’ll use:

  • GNU parallel (parallelization)

  • ripgrep (rg) for fast text filtering

  • jq (JSON processing)

  • Miller (mlr) for CSV/TSV processing

  • csvkit (CSV utilities; optional if you prefer Miller)

  • pigz (parallel gzip)

  • pv (progress/throughput viewer)

  • datamash (quick aggregates)

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y parallel ripgrep jq miller csvkit pigz pv datamash

Fedora (dnf):

sudo dnf install -y parallel ripgrep jq miller python3-csvkit pigz pv datamash

RHEL/CentOS (dnf + EPEL):

sudo dnf install -y epel-release
sudo dnf install -y parallel ripgrep jq miller python3-csvkit pigz pv datamash

openSUSE (zypper):

sudo zypper refresh
sudo zypper install -y gnu_parallel ripgrep jq miller python3-csvkit pigz pv datamash

Note: On some distros gnu_parallel is named parallel. Use zypper search parallel if unsure.


1) Stream, don’t load: build pipelines that never keep more than a buffer in RAM

Load-it-all-at-once is the enemy of scale. Prefer streams and simple, fast filters.

  • Work on compressed data directly when possible:
# Stream .gz logs, search for ERROR fast, count occurrences
pigz -dc logs/*.gz | rg -N "ERROR" | wc -l
  • Use locale and memory knobs on sort for speed and scale:
# Faster byte-wise comparison, bounded memory, spill to fast scratch if needed
LC_ALL=C sort -S 50% -T /mnt/fasttmp bigfile.tsv > sorted.tsv
  • Peek before you leap: sample to validate logic on huge files:
# Random sample 10k lines to dry-run a pipeline
shuf -n 10000 big.csv | mlr --csv stats1 -a count,mean -f bytes
  • Show progress/throughput:
pv big.json.gz | pigz -dc | jq -r '.user_id' | wc -l

Why it works: Most Unix tools operate in O(n) streaming passes; they’re fast, predictable, and avoid memory blowups.


2) Scale out locally with GNU parallel

Parallelizing per-file or per-chunk work can cut wall-clock time dramatically.

  • Fan out per-file and merge:
# Extract 5xx API hits per file in parallel, merge and sum
find logs -name '*.json.gz' -print0 |
  parallel -0 -j8 'pigz -dc {} | jq -r "select(.status>=500) | .app"' |
  sort | uniq -c | sort -nr > top_apps.txt
  • Chunk a huge single file by blocks and process in parallel:
# Split into ~100MB chunks on line boundaries, process with --pipe
pigz -dc big.json.gz |
  parallel --pipe --block 100M -j8 'jq -r "select(.country==\"US\") | .user_id"' |
  sort -u > us_users.txt
  • Be safe with filenames (NUL-delimited):
find data -type f -name '*.csv' -print0 | parallel -0 -j$(nproc) 'mlr --csv cut -f id,bytes {} > {.}.min.csv'

Tip: Use --dry-run with parallel to inspect what will execute.


3) Tame JSON and CSV at scale (jq + Miller)

Semi-structured text dominates large-scale telemetry. Use tools purpose-built for line-oriented data.

  • JSON filtering and aggregation:
# Top 10 endpoints by 5xx responses from gzipped NDJSON
pigz -dc api.ndjson.gz |
  jq -r 'select(.status>=500) | .endpoint' |
  sort | uniq -c | sort -nr | head -n 10
  • CSV per-group stats with Miller:
# Sum and average bytes by user_id
pigz -dc traffic.csv.gz |
  mlr --csv stats1 -a count,sum,mean -f bytes -g user_id |
  mlr --csv sort -nr mean_bytes > bytes_by_user.csv
  • Joining large CSVs on a key without loading everything into RAM:
# Left-join metadata onto events by user_id
pigz -dc events.csv.gz |
  mlr --csv join --ul -j user_id -f metadata.csv then cut -o -f user_id,event,plan > enriched.csv
  • Quick aggregates with datamash:
# datamash expects delimited columns; here, comma-separated CSV
datamash -t, -g country sum 3 mean 3 < small.csv

Why it works: jq and Miller are streaming-friendly and optimized for text-based records, avoiding full in-memory parses.


4) Make jobs reliable: idempotent, restartable Bash

Long jobs must survive hiccups. Add guardrails: strict modes, traps, temp dirs, checkpoints, and logs.

  • Script skeleton:
#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit

log() { printf '%s %s\n' "$(date +%FT%T)" "$*" >&2; }

workdir="$(mktemp -d)"
cleanup() { rm -rf "$workdir"; }
trap cleanup EXIT

INPUT="${1:?usage: $0 INPUT_DIR}"
OUT="report.csv"

# Checkpoints
STEP1_DONE=".step1.done"
STEP2_DONE=".step2.done"

if [[ ! -e $STEP1_DONE ]]; then
  log "Step 1: extract 5xx hits"
  find "$INPUT" -name '*.json.gz' -print0 |
    parallel -0 -j"$(nproc)" 'pigz -dc {} | jq -r "select(.status>=500) | .app"' \
    | sort | uniq -c | sort -nr > "$workdir/apps.txt"
  touch "$STEP1_DONE"
fi

if [[ ! -e $STEP2_DONE ]]; then
  log "Step 2: summarize to CSV"
  awk 'BEGIN{OFS=","} {print $2,$1}' "$workdir/apps.txt" > "$OUT"
  touch "$STEP2_DONE"
fi

log "Done. Output: $OUT"
  • Tips:
    • Write deterministic outputs so reruns are safe (idempotent).
    • Use .done files per stage to resume work.
    • Log with timestamps; keep stderr for logs and stdout for data.

5) Measure, then optimize

Guessing wastes time. Instrument your pipelines.

  • Baseline commands:
/usr/bin/time -v bash -c 'pigz -dc big.ndjson.gz | jq -c "select(.ok==true)" | wc -l'
  • Compare alternatives:
# ripgrep vs grep, locale vs default, parallel vs serial
pigz -dc logs.gz | rg -N 'pattern' > /dev/null
pigz -dc logs.gz | LC_ALL=C grep -F 'pattern' > /dev/null
  • Profile bottlenecks:
    • Is it CPU-bound? Try -j$(nproc) or pigz instead of gzip.
    • Is it disk-bound? Stream, avoid temp explosions, use -S on sort and a fast -T tmpdir.
    • Is it network-bound? Compress on the wire: ... | pigz -c | ssh host 'pigz -dc | ...'.

Real-world mini playbook: daily TB of gzipped API logs

Goal: Count 5xx per endpoint, top 100, daily.

# 1) Fan out per file, emit endpoints for 5xx
find /data/logs/2026-07-06 -name '*.json.gz' -print0 |
  parallel -0 -j12 'pigz -dc {} | jq -r "select(.status>=500) | .endpoint"' |
  sort | uniq -c | sort -nr | head -n 100 > top_5xx_endpoints.txt

# 2) Persist CSV
awk 'BEGIN{OFS=","} {print $2,$1}' top_5xx_endpoints.txt > top_5xx_endpoints.csv

Scales well, streams everything, and keeps memory flat.


Common pitfalls (and fixes)

  • “It’s slow.” Measure first (/usr/bin/time -v, pv), then parallelize and tune sort.

  • “Out of space in /tmp.” Use sort -T /mnt/fasttmp and ensure enough scratch.

  • “Weird collation or sort order.” Set LC_ALL=C for byte-wise speed and reproducibility.

  • “Spaces in filenames broke my job.” Use NUL delimiters: -print0 with find and -0 with parallel/xargs.


Conclusion and next step

Bash remains a powerhouse for large data automation because it streams by default, composes cleanly, and scales across cores with minimal code. Your next step:

1) Install the toolkit (parallel, ripgrep, jq, miller, csvkit, pigz, pv, datamash).
2) Convert one slow, memory-heavy task into a streaming pipeline.
3) Add parallel and checkpoints for reliability and speed.
4) Measure, iterate, and document as a script.

Pro tip: keep a “pipeline cookbook” repo of proven one-liners and scripts. Future you—and your compute bill—will thank you.