Run shell commands in parallel with output buffering and resumable checkpoints
Overview · Features · Installation · Usage · Requirements · License
# Before:
# cat hosts.txt | while read host; do ssh "$host" 'apt update && apt upgrade -y'; done
# (sequential, no progress tracking, start over on failure)
# After:
pll -c upgrade.db 'ssh {} "apt update && apt upgrade -y"' < hosts.txt
# (parallel, atomic output, resume from checkpoint on re-run)pll reads lines from stdin, replaces {} placeholders in a command template with each line, and executes the resulting commands in parallel. Output is buffered at the line level by default to prevent interleaving across concurrent jobs. An optional BoltDB-backed checkpoint file tracks which jobs succeeded, allowing interrupted runs to resume without re-executing completed work.
- Configurable parallelism, defaults to number of CPU cores
{}placeholder expansion in both command and working directory templates- Three output buffering modes: none (passthrough), line (atomic lines), job (buffer entire output)
- BoltDB checkpoint persistence for resumable execution, keyed by working directory
- Interactive mode (
-i) with stdin passthrough for sequential execution - Terminal progress bar for tracking job completion (
-p) - Per-job timeout (
-t) to kill jobs that exceed a time limit - Fail-fast mode (
--fail-fast) to stop launching new jobs after the first failure - Three-stage Ctrl+C: stop launching, then interrupt running jobs, then force kill
Download from Releases.
brew install ivoronin/ivoronin/pll# Run a command for each line from stdin
echo -e "host1\nhost2\nhost3" | pll 'ping -c1 {}'
# Read targets from a file
pll 'curl -sS https://{}/.well-known/health' < domains.txt# Limit to 4 concurrent jobs
pll -j4 'scp config.yml {}:/etc/app/' < hosts.txt# Show a progress bar on stderr
pll -j4 -p 'rsync -a {}:/data /backup/{}' < hosts.txt# Sequential execution with stdin connected to each process
pll -i 'ssh -t {} "sudo bash"' < hosts.txt-i forces jobs to 1 and connects stdin to the child process. Mutually exclusive with -j.
# Line-level buffering (default) - atomic line output, no interleaving
pll 'docker logs {}' < containers.txt
# Job-level buffering - buffer entire output per job, flush on completion
pll -b job 'kubectl logs {}' < pods.txt
# No buffering - direct passthrough (default for -i)
pll -b none 'make -C {}' < projects.txt# First run - execute all jobs, record results
pll -c deploy.db 'ansible-playbook -l {} site.yml' < hosts.txt
# Re-run - skip previously successful jobs, retry failures
pll -c deploy.db 'ansible-playbook -l {} site.yml' < hosts.txtEach job is identified by its working directory. Results are stored in a BoltDB file. On re-run, only previously successful jobs are skipped; failed jobs are always retried.
# Kill any job that runs longer than 30 seconds
pll -t 30s 'curl -sS https://{}/.well-known/health' < domains.txt
# 5-minute timeout with checkpoint to retry timed-out jobs on re-run
pll -t 5m -c deploy.db 'ansible-playbook -l {} site.yml' < hosts.txtTimed-out jobs are recorded as failures. Mutually exclusive with -i.
# Stop launching new jobs after the first failure
pll --fail-fast 'make -C {}' < projects.txt
# Combine with timeout - first timeout triggers fail-fast
pll -t 30s --fail-fast -j4 'ssh {} "apt update"' < hosts.txtAlready-running jobs finish naturally. With checkpoints, unlaunched jobs have no entry and will run on re-run.
# Change to a directory per job (supports {} placeholder)
pll -C '/srv/{}' 'git pull' < repos.txt-i, --interactive run jobs sequentially with stdin connected (mutually exclusive with -j, -t)
-j, --jobs number of parallel jobs (default: number of CPU cores)
-p, --progress show progress bar on stderr
-t, --timeout per-job timeout (e.g. 30s, 5m)
--fail-fast stop launching new jobs after first failure
-b, --buffer output buffering mode: none, line, job (default: line)
-c, --checkpoint path to checkpoint file for resumable execution
-C, --chdir change to directory before running command (supports {})
--version print version and exit
shshell available in PATH