Skip to content

Instantly share code, notes, and snippets.

@eguven
Last active June 12, 2024 13:30
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
@affanfarid
Copy link

affanfarid commented Sep 20, 2018

thanks, this helped, if you could format it to a table that would make it a lot better

@akishaha
Copy link

Thanks, it worked for me too.

@simonkeng
Copy link

Me too, thank you!

@Coldsp33d
Copy link

This is really slow, is there something faster?

@evictor
Copy link

evictor commented Jun 14, 2019

@Coldsp33d here's how to do it in parallel; note that you need parallel which you can install with brew install parallel. (Sorry, I couldn't figure out how to do this with xargs. 😬 )

brew list -f1 | parallel "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'"

@evictor
Copy link

evictor commented Jun 14, 2019

Can also do this to sort by size descending, though you won't see intermediate output while parallel goes:

brew list -f1 | parallel "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'" | sort -h -r

@NikhilVerma
Copy link

Just a small update, the sorting command is not correct at least for me

brew list -f1 | parallel "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'" | sort -k2 -hr

Sorts correctly because we are sorting the second column

@eguven
Copy link
Author

eguven commented Nov 5, 2019

I didn't want to require parallel dependency so I've updated the snippet to use xargs, added the sort and table output. Thanks for the suggestions everyone.

@attriroot
Copy link

thank you so much!

@jagdishadusumalli
Copy link

Just a small update, the sorting command is not correct at least for me

brew list -f1 | parallel "brew info {} | egrep '[0-9]* files, ' | sed 's/^.*[0-9]* files, \(.*\)).*$/{} \1/'" | sort -k2 -hr

Sorts correctly because we are sorting the second column

Hi @NikhilVerma i tried your command and even others above but its failing for me
Error: ambiguous option: -f1

Am on macOS catalina 10.15.6 with CLT 11.5.0.0.1.1588476445.

Pls help out

@vasudev-hv
Copy link

Hi @NikhilVerma i tried your command and even others above but its failing for me
Error: ambiguous option: -f1

Am on macOS catalina 10.15.6 with CLT 11.5.0.0.1.1588476445.

Pls help out

@jagdishadusumalli Remove -f1 after brew list.

@jagdishadusumalli
Copy link

Hi @NikhilVerma i tried your command and even others above but its failing for me
Error: ambiguous option: -f1
Am on macOS catalina 10.15.6 with CLT 11.5.0.0.1.1588476445.
Pls help out

@jagdishadusumalli Remove -f1 after brew list.

Thanks Vasudev

@eguven
Copy link
Author

eguven commented Sep 18, 2020

I've removed -f1 from the gist, thanks @vasudev-hv and @jagdishadusumalli

@eguven
Copy link
Author

eguven commented Oct 29, 2020

Warning: Calling brew list to only list formulae is deprecated! Use brew list --formula instead.

Added --formula

@bkeys818
Copy link

bkeys818 commented Nov 1, 2020

Just an updated version for simpletons like me

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

@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