Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active June 21, 2026 17:36
Show Gist options
  • Select an option

  • Save ernstki/dab42b66b4397d77caf64aad8cdcafc7 to your computer and use it in GitHub Desktop.

Select an option

Save ernstki/dab42b66b4397d77caf64aad8cdcafc7 to your computer and use it in GitHub Desktop.
A `du` for Flatpak

flatpak-du: like du for Flatpaks

This utility is intended to produce more consistent and useful output than flatpak list, while papering over some of the more irritating quirks of flatpak list --show-details, such as:

  • showing completely different columns depending on whether standard out is a terminal or not
  • inability to control column headers; shown when stdout is a terminal, hidden otherwise
  • hiding or showing column #13 (installation), or displaying completely different information in that column depending on a combination of other options like --user and --system
  • human-readable byte size figures that separate the unit with a non-breaking space, and are therefore not sortable with standard Unix utilities like sort -rh
  • (and no-built in sort functionality either)

Installation

GIST=https://gist.github.com/ernstki/dab42b66b4397d77caf64aad8cdcafc7/raw/flatpak-du

# create ~/bin if it doesn't alrady exist, or put in /usr/local/bin
mkdir -p ~/bin

( curl -L $GIST || wget -O- $GIST ) > ~/bin/flatpak-du
chmod a+x ~/bin/flatpak-du

If ~/bin didn't exist beforehand, a logout may be required so that it gets added to your search path.

Usage

Now try:

$ flatpak-du -5 --bars | column -ts$'\t'
+ flatpak list --show-details
name                       application                  version            size          type
elementary platform        io.elementary.Platform                          [██▊   1.1G]  system runtime
GNOME Application Plat[…]  org.gnome.Platform                              [██▌   1.0G]  user runtime
elementary platform        io.elementary.Platform                          [██  806.1M]  system runtime
Freedesktop SDK            org.freedesktop.Platform     freed[…]-25.08.12  [█▋  656.8M]  user runtime
Freedesktop SDK            org.freedesktop.Platform[…]  25.0.7             [█▎  539.2M]  system runtime

For best appearance, pipe through column -ts$'\t'. For Bourne-like shells, you can create a wrapper function like this

function flatdu() { flatpak-du --bars "$@" | column -ts$'\t'; }

in your login scripts, which will always give you the pretty-printed tabular output.

Call the script by its original name if you want to use it in a pipe or sort it some other way. Bear in mind that human-readable figures are currently the default; turn them off with -H or --no-human-readable.

To-do

  • to columnize or not to columnize; for now, just rely on column as described above
  • report apps which depend on a runtime, and report which runtimes an app depends on
  • maybe default to non-human readable figures if piped/redirected (if AWK has a reliable way of telling; this is tricky)

Author and license

Kevin Ernst <ernstki -at- mail.uc.edu>. Zero-Clause BSD.

#!/usr/bin/gawk -E
##
## flatpak-du - print disk utilization of system/user Flatpak apps & runtimes
##
## Author: Kevin Ernst (ernstki@mail.uc.edu)
## Date: 21 June 2026
## Assisted-by: Kagi Quick 06-2026 → selection sort algorithm; 'dehumanize'
## and 'bargraph' functions
## License: Zero-Clause BSD, but attribution is appreciated
## Homepage: https://gist.github.com/ernstki/dab42b66b4397d77caf64aad8cdcafc7
##
BEGIN {
FS = "\t"
OFS = "\t"
headers = 1
human = 1
bars = 0
quiet = 0
for (i = 1; i < ARGC; i++) {
#print "ARGV[" i "]=" ARGV[i] > "/dev/stderr"
if (ARGV[i] ~ /^--?h(elp)?$/) help = 1
else if (ARGV[i] ~ /^--?v(erbose)?$/) { verbose = 1; quiet = 0 }
else if (ARGV[i] ~ /^--?q(uiet)?$/) { quiet = 1; verbose = 0 }
else if (ARGV[i] ~ /^--?u(ser)?$/) user = " --user"
else if (ARGV[i] ~ /^--?s(ystem)?$/) sys = " --system"
else if (ARGV[i] ~ /^--r(un)?t(imes?)?$/) runtime = " --runtime"
else if (ARGV[i] ~ /^--?a(ll)$/) all = " --all"
else if (ARGV[i] ~ /^-(b|-bar.*)$/) bars = 1
else if (ARGV[i] ~ /^-(nh|H|-no-human.*)$/) human = 0
else if (ARGV[i] ~ /^-(nc|C|-no-col.*)$/) headers = 0
else if (ARGV[i] ~ /^-n?[[:digit:]]+$/)
{
topn = ARGV[i]
gsub(/^-n?/, "", topn)
topn = topn + 0
}
else if (ARGV[i] == "-n")
{
if (ARGV[i+1] && ARGV[i+1] ~ /[[:digit:]]+/) {
topn = ARGV[i+1] + 0
delete ARGV[i]; delete ARGV[i+1]; i++
} else {
print "ERROR: -n was missing an (integer) argument." \
> "/dev/stderr"
exitcode = 1; exit
}
}
else {
print "ERROR: Unknown option '" ARGV[i] "'." > "/dev/stderr"
exitcode = 1; exit
}
delete ARGV[i]
}
if (help) {
printhelp()
exit
}
# stop trying to read stdin
ARGC = 2; ARGV[1] = "/dev/null"
# we'll use columns 1,3,4,12,13
flatlist = "flatpak list --show-details" all runtime user sys
if (!quiet) print "\x1b[1;35m+ " flatlist "\x1b[0m" > "/dev/stderr"
# the manual page makes it seem as if NR is unconditionally set by
# `getline`, but it isn't here; NF is, though
nr = 0
while ((flatlist | getline) > 0) {
nr++
flatpaks[nr]["name"] = $1
flatpaks[nr]["appid"] = $3
flatpaks[nr]["version"] = $4
flatpaks[nr]["size"] = dehumanize($12)
# fix this bad behavior: the 13th column will not be displayed if you
# specified `--user` or `--system` and it's not shown at all if output
# is stdout (!)
split($13, types, /,/)
if (runtime)
# only runtimes are shown
flatpaks[nr]["type"] = types[1] " runtime"
else if (user && !sys) {
if (types[1] == "runtime")
flatpaks[nr]["type"] = "user runtime"
else
flatpaks[nr]["type"] = "user app"
}
else if (sys && !user)
if (types[1] == "runtime")
flatpaks[nr]["type"] = "system runtime"
else
flatpaks[nr]["type"] = "system app"
else {
if (types[2] == "runtime")
flatpaks[nr]["type"] = types[1] " runtime"
else
flatpaks[nr]["type"] = types[1] " app"
}
}
close(flatlist)
# selection sort b/c it's easy; largest byte size first, then alphabetical
for (i = 1; i <= nr; i++) {
best = i
for (j = i + 1; j <= nr; j++) {
if (!flatpaks[j]["size"] && !flatpaks[best]["size"]) {
# compare alphabetically by name instead
if (flatpaks[j]["name"] < flatpaks[best]["name"])
best = j
}
else if (flatpaks[j]["size"] > flatpaks[best]["size"])
best = j
}
for (k in flatpaks[i]) tmp[k] = flatpaks[i][k]
for (k in flatpaks[i]) flatpaks[i][k] = flatpaks[best][k]
for (k in flatpaks[i]) flatpaks[best][k] = tmp[k]
}
# limit to top n by byte size here, after sorting
if (!topn) topn = nr
if (bars)
for (i = 1; i <= topn; i++) totalbytes += flatpaks[i]["size"]
if (headers)
print "name\tapplication\tversion\tsize\ttype"
for (i = 1; i <= topn; i++) {
bytes = flatpaks[i]["size"]
size = human ? humanize(bytes) : sprintf("%.f", bytes)
if (bars)
size = "[" bargraph(bytes, totalbytes, size) "]"
print flatpaks[i]["name"], \
flatpaks[i]["appid"], \
flatpaks[i]["version"], \
size, \
flatpaks[i]["type"]
} # top n by byte size, largest first
}
END {
exit exitcode
}
function printhelp() {
print "\n" \
" flatpak-du - print disk utilization for flatpak apps and runtimes\n" \
"\n" \
" usage: \n" \
" flatpak-du [-h|--help] [-q|--quiet] [-v|--verbose]\n" \
" [-u] [-s] [-r] [-a] [-b|--bars|--bargraph|--bar-graph]\n" \
" [-H|--no-human-readable] [-C|--no-column-headers]\n" \
" [-LINES|-nLINES]\n" \
"\n" \
" where: \n" \
" -h, --help shows this help\n" \
" -q, --quiet suppresses some informational output\n" \
" -v, --verbose shows more details while processing\n" \
" -u, --user shows user-installed packages/runtimes only\n" \
" -s, --system shows system-wide packages/runtimes only\n" \
" -r, --runtime shows only runtimes rather than apps + runtimes\n" \
" -a, --all shows hidden/debug runtimes, too\n" \
" -b, --bar[…] shows Unicode bar graphs for byte sizes\n" \
" -H, --no-human[…] shows sizes in bytes rather than IEC units\n" \
" -C, --no-col[…] suppresses column headers\n" \
" -LINES, -nLINES shows only top LINES lines, sorted by size\n" \
"\n" \
" The default is to show apps + runtimes, both user-installed and\n" \
" system-wide, sorted largest-first, displaying human-readable sizes\n" \
" and bar graphs, with no limit on the number of records.\n" \
"\n" \
" tips: \n" \
" For best appearance, pipe through 'column'. For Bourne-like shells,\n" \
" try adding this wrapper function\n" \
"\n" \
" function flatdu() { flatpak-du --bars \"$@\" | column -ts$'\\t'; }\n" \
"\n" \
" to your login scripts.\n" \
"\n" \
" homepage:\n" \
" https://gist.github.com/ernstki/dab42b66b4397d77caf64aad8cdcafc7\n"
}
function dehumanize(s) {
# convert "1.0 GB" into bytes again, since `flatpak` has no way of doing
# that; all the better because someone thought it was a good idea to have a
# non-breaking space between the number and the unit in the 'size' column
# ¯\_(ツ)_/¯
bytesize = s + 0 # casting to number removes suffix
if (s ~ /GB/) return bytesize * 1024^3
if (s ~ /MB/) return bytesize * 1024^2
if (s ~ /KB/) return bytesize * 1024
return val
}
function humanize(b) {
if (b % 1024 == b) return b
else if (b % 1024^2 == b) return sprintf("%0.1fK", b/1024)
else if (b % 1024^3 == b) return sprintf("%0.1fM", b/1024^2)
else if (b % 1024^4 == b) return sprintf("%0.1fG", b/1024^3)
else return sprintf("%0.1fT", b/1024^4)
}
# create a 10-character wide unicode bar graph with text overlaid
function bargraph(val, max_val, text_label, width) {
if (!text_label) text_label = val
if (!width) width = 10
text_padded = sprintf("%*.*s", width, width, text_label)
if (max_val == 0) return text_padded
ratio = val / max_val
if (ratio > 1) ratio = 1
total_eighths = int(ratio * 80 + 0.5)
full_blocks = int(total_eighths / 8)
remainder = total_eighths % 8
bar = ""
for (b = 1; b <= width; b++) {
c = substr(text_padded, b, 1)
if (b <= full_blocks) {
# fully-filled segment: invert the text character
bar = bar "\033[7m" c "\033[27m"
} else if (b == full_blocks + 1 && remainder > 0) {
# Partially filled segment
if (c == " ") {
# if no text here, show the high-res fractional block
if (remainder == 7) bar = bar "▉"
else if (remainder == 6) bar = bar "▊"
else if (remainder == 5) bar = bar "▋"
else if (remainder == 4) bar = bar "▌"
else if (remainder == 3) bar = bar "▍"
else if (remainder == 2) bar = bar "▎"
else if (remainder == 1) bar = bar "▏"
} else {
# if text, invert it if the block is at least half full
if (remainder >= 4) {
bar = bar "\033[7m" c "\033[27m"
} else {
bar = bar c
}
}
} else {
# empty segment: just print the text character normally
bar = bar c
}
}
return bar
}
# vim: expandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment