Skip to content

Instantly share code, notes, and snippets.

@eguven
Last active August 19, 2024 10:14
Show Gist options
  • Save eguven/23d8c9fc78856bd20f65f8bcf03e691b to your computer and use it in GitHub Desktop.
Save eguven/23d8c9fc78856bd20f65f8bcf03e691b to your computer and use it in GitHub Desktop.
List all packages installed using Homebrew and their sizes
# this original one uses values returned from 'brew info'
brew list --formula | xargs -n1 -P8 -I {} \
sh -c "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'" | \
sort -h -r -k2 - | column -t
# faster alternative using 'du'
du -sch $(brew --cellar)/*/* | sed "s|$(brew --cellar)/\([^/]*\)/.*|\1|" | sort -k1h
@georgio
Copy link

georgio commented Feb 18, 2021

This is identical to what @bkeys818 posted except it's compatible with the fish shell

brew install parallel; parallel --citation   #Skip this step if parallel is already installed
brew list --formula | parallel "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*(/{}) \1/'" | sort -k2 -hr

@vogler
Copy link

vogler commented Nov 7, 2023

Alternative: brew install ncdu && ncdu /opt/homebrew/Cellar

@manan-gup
Copy link

Works on fish shell without parallel:

brew list --formula | xargs -P8 -I {} sh -c "brew info {} | grep -E '\([0-9]+ files'" \
| awk -F '[/(),]' '{print $6 ", " $8 ", " $9}' | sort -t "," -rh -k3

and gives output in a comma-separated format which can be simply changed in the awk command:

exiftool, 603 files,  25.3MB
micro, 7 files,  11.4MB
tealdeer, 12 files,  5.4MB
bat, 14 files,  4.6MB
eza, 14 files,  1.1MB

@adk-anw
Copy link

adk-anw commented Jan 15, 2024

Worked for me too! Thanks!

@redspot
Copy link

redspot commented May 16, 2024

TLDR; the du-wildcard version is the simplest and fastest.

du -sch $(brew --cellar)/*/* | sort -k1h

Summary of pipelines and timings

alias description (summary of pipeline) runtime (sec)
parallel brew list | parallel "brew info {}" 60.6667 avg
linear brew info $(brew list) 25.333 avg
du-formula brew list | xargs brew --cellar | xargs du -sch 3.4
du-version brew_list_with_versions | xargs du -sch 2.5
du-wildcard du -sch $(brew --cellar)/*/* 0.4

Three run average, for parallel and linear

parallel linear
t1 61 25
t2 61 25
t3 60 26
avg 60.6667 25.333

Let's go over the existing solutions from above.
First, lets wrap our brew info ... | sed ... pipeline into a function.
Also note, that all lsited functions here are bash and zsh compatible.

# simplify this:
#   egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'
# into this:
#   sed -nE -e '/[0-9]+ files, /{s,.*/Cellar/,,;s/ \(.* files, ([^)]+)\)/ \1/p}'
# and, allow multiple args for 'brew info'
brew_formula_size() {
    brew info "${@?}" \
    | sed -nE -e '
    /[0-9]+ files, /{
        s,.*/Cellar/,,
        s/ \(.* files, ([^)]+)\)/ \1/p
    }'
}

And, then, lets wrap the sizing into a parallel pipeline function

brew_sizes() {
    if [ $# -gt 0 ]; then
        printf '%s\n' "$@"
    else
        brew list --formula
    fi \
    | parallel "bash -c 'brew_formula_size {}'" \
    | sort -k2h
}

# Example usages using gnu parallel:
brew_sizes zlib util-linux | column -t
brew_sizes | tail | column -t

However, parallel fs sizing operations won't improve performance. Here's the timing for the parallel version.

$ time { brew_sizes | tail | column -t; }
Warning: Formula pev was renamed to readpe.
icu4c/74.2               85.0MB   *
berkeley-db@5/5.3.28_1   87.7MB   
ghostscript/10.03.0      170.7MB  *
boost/1.85.0             226.9MB  *
go/1.22.3                259.5MB  *
gcc/14.1.0               359.7MB  *
binutils/2.42            490.5MB  
mingw-w64@11.0.1/11.0.1  1GB      
mingw-w64/11.0.1_1       1GB      *
llvm/18.1.5              2.7GB    

real    0m59.004s
user    5m12.727s
sys     1m30.422s

Since brew info accepts multiple formulas, we can do a linear, non-parallel version.

# Example usages, linear non-parallel:
brew_formula_size zlib util-linux | column -t
brew_formula_size $(brew list --formula) | sort -k2h | tail | column -t
$ time { brew_formula_size $(brew list --formula) | sort -k2h | tail | column -t; }
Warning: Formula pev was renamed to readpe.
icu4c/74.2               85.0MB   *
berkeley-db@5/5.3.28_1   87.7MB   
ghostscript/10.03.0      170.7MB  *
boost/1.85.0             226.9MB  *
go/1.22.3                259.5MB  *
gcc/14.1.0               359.7MB  *
binutils/2.42            490.5MB  
mingw-w64@11.0.1/11.0.1  1GB      
mingw-w64/11.0.1_1       1GB      *
llvm/18.1.5              2.7GB    

real    0m25.452s
user    0m11.543s
sys     0m6.668s

You could just pipe everything to du and get the sizes per formula, but not the sizes per formula per version.

# one-liners, using 'du', but does not show sizes per installed version
printf '%s\n' zlib util-linux | xargs brew --cellar | xargs du -sch | sort -k1h
brew list --formula | xargs brew --cellar | xargs du -sch | sort -k1h | tail
$ time { brew list --formula | xargs brew --cellar | xargs du -sch | sort -k1h | tail; }
Warning: Formula pev was renamed to readpe.
123M    /home/linuxbrew/.linuxbrew/Cellar/ruby
172M    /home/linuxbrew/.linuxbrew/Cellar/ghostscript
263M    /home/linuxbrew/.linuxbrew/Cellar/boost
295M    /home/linuxbrew/.linuxbrew/Cellar/go
363M    /home/linuxbrew/.linuxbrew/Cellar/gcc
502M    /home/linuxbrew/.linuxbrew/Cellar/binutils
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64@11.0.1
2.8G    /home/linuxbrew/.linuxbrew/Cellar/llvm
8.1G    total

real    0m3.378s
user    0m1.879s
sys     0m0.999s

brew list --formula --versions does produce all the versions for each formula.

$ brew list --formula --versions zlib util-linux
util-linux 2.40.1 2.39.3
zlib 1.3.1

You could then fold the formulas together with the versions, like this:

# array slicing, like ${arr[@]:start} and ${arr[@]:start:length}
# works in bash and zsh,
# and 'start' starts at 0, for both bash and zsh
#
# for input 'foo 1.2 3.4' into 'read -a form',
# ${form[@]:0:1} == 'foo'
# ${form[@]:1} == (1.2 3.4)  # sub-array
#
# $ printf '%s\n' arg1 arg2
# arg1
# arg2
# ^-- printf will repeat the format pattern for each arg
# "${array[@]}" will turn into multiple args, if non-empty
#
brew_list_with_versions() {
    brew list --formula --versions "$@" \
    | while read -a form; do
        printf "$(brew --cellar)/${form[@]:0:1}/%s\n" "${form[@]:1}"
    done
}

$ brew_list_with_versions zlib util-linux
/home/linuxbrew/.linuxbrew/Cellar/util-linux/2.40.1
/home/linuxbrew/.linuxbrew/Cellar/util-linux/2.39.3
/home/linuxbrew/.linuxbrew/Cellar/zlib/1.3.1

# one-liners, using 'du', show sizes per formula per installed version
brew_list_with_versions zlib util-linux | xargs du -sch | sort -k1h
brew_list_with_versions | xargs du -sch | sort -k1h | tail
# NOTE: this was run after the above timed pipeline, which showed 'real 0m3.378s'
$ time { brew_list_with_versions | xargs du -sch | sort -k1h | tail; }
123M    /home/linuxbrew/.linuxbrew/Cellar/ruby/3.3.1
172M    /home/linuxbrew/.linuxbrew/Cellar/ghostscript/10.03.0
263M    /home/linuxbrew/.linuxbrew/Cellar/boost/1.85.0
295M    /home/linuxbrew/.linuxbrew/Cellar/go/1.22.3
363M    /home/linuxbrew/.linuxbrew/Cellar/gcc/14.1.0
502M    /home/linuxbrew/.linuxbrew/Cellar/binutils/2.42
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64/11.0.1_1
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64@11.0.1/11.0.1
2.8G    /home/linuxbrew/.linuxbrew/Cellar/llvm/18.1.5
8.1G    total

real    0m2.465s
user    0m1.071s
sys     0m1.410s

You could just use 'du' on Cellar/some_formula/some.version like this:

du -sch $(brew --cellar)/*/* | sort -k1h | tail
du -sch $(brew --cellar)/{zlib,util-linux}/* | sort -k1h
# pipeline version for specific formulas,
# 'paste -sd,' joins input lines with comma
echo zlib util-linux | bash -c "du -sch $(brew --cellar)/{$(xargs -n1|paste -sd,)}/*"
echo zlib util-linux | xargs -n1 | paste -sd, | bash -c "du -sch $(brew --cellar)/{$(cat)}/*"
du_wildcard() {
    typeset forms=$(tty --quiet || xargs --no-run-if-empty -n1 | paste -sd,)
    if [ x"${forms}" = x ]; then
        du -sch $(brew --cellar)/*/*
    else
        echo "du -sch $(brew --cellar)/{${forms}}/*" | $SHELL
        # bash -c "du -sch $(brew --cellar)/{${forms}}/*"
    fi
}
echo zlib util-linux | du_wildcard
# NOTE: this was run after the above timed pipeline, which showed 'real 0m2.465s'
$ time { du -sch $(brew --cellar)/*/* | sort -k1h | tail; }
123M    /home/linuxbrew/.linuxbrew/Cellar/ruby/3.3.1
172M    /home/linuxbrew/.linuxbrew/Cellar/ghostscript/10.03.0
263M    /home/linuxbrew/.linuxbrew/Cellar/boost/1.85.0
295M    /home/linuxbrew/.linuxbrew/Cellar/go/1.22.3
363M    /home/linuxbrew/.linuxbrew/Cellar/gcc/14.1.0
502M    /home/linuxbrew/.linuxbrew/Cellar/binutils/2.42
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64/11.0.1_1
1.1G    /home/linuxbrew/.linuxbrew/Cellar/mingw-w64@11.0.1/11.0.1
2.8G    /home/linuxbrew/.linuxbrew/Cellar/llvm/18.1.5
8.1G    total

real    0m0.359s
user    0m0.067s
sys     0m0.308s

@eguven
Copy link
Author

eguven commented May 17, 2024

Thanks @redspot I've added an alternative using du -sch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment