Skip to content

Instantly share code, notes, and snippets.

@tremby
Last active November 16, 2021 21:09
Show Gist options
  • Save tremby/65edb50e94311f15360c9926b8d955da to your computer and use it in GitHub Desktop.
Save tremby/65edb50e94311f15360c9926b8d955da to your computer and use it in GitHub Desktop.
Progress bar for anything
#!/usr/bin/env python3
"""
Get a progress bar for anything.
Feed this script a line containing two numbers on standard input as often or
infrequently as you like.
It will give you a progress bar and, once at least two readings have been taken,
estimated time remaining.
Example for rough progress on a long copy operation, where all files are roughly
equal:
total=$(find src-dir -type f | wc -l); while sleep 5; do echo $(find dest-dir -type f | wc l) $total; done | python3 progress.py
"""
from sys import stdin, stdout
from collections import deque
from datetime import datetime, timedelta
from shutil import get_terminal_size
READINGS_MAX = 10
latest = deque([])
for line in stdin:
# Get status
try:
current, total = [int(x) for x in line.split()]
except ValueError:
stdout.write("Expected two whitespace-separated values: current, total\n")
continue
dt = datetime.now()
progress = current / total
# How long is the total
totalChars = len(str(total))
# Calculate remaining time if possible
remainingTime = None
if len(latest):
refDt, refCurrent, refTotal = latest.popleft() if len(latest) >= READINGS_MAX else latest[0]
todo = total - current
elapsed = dt - refDt
done = current - refCurrent
if done == 0:
remainingTime = "stalled"
else:
remainingTime = todo * elapsed / done
# Round to seconds
remainingTime -= timedelta(microseconds=remainingTime.microseconds)
# Prepare status
line = f"{current:>{totalChars}}/{total}: {int(progress*100):3d}% []"
# Add remaining time if we have it
if remainingTime is not None:
line += f" {remainingTime}"
# Add the progress bar
remainingWidth = get_terminal_size((80, 20))[0] - len(line)
bar = "=" * int(progress * remainingWidth)
line = line.replace("[]", f"[{bar:{remainingWidth}}]")
# Clear line and write to output
stdout.write("%c[2K\r" % 27)
stdout.write(line)
# Flush output
stdout.flush()
# Remember this progress so we can make time predictions
latest.append((dt, current, total))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment