Skip to content

Instantly share code, notes, and snippets.

@SteveBenner
Last active February 2, 2025 11:01
Show Gist options
  • Save SteveBenner/11254428 to your computer and use it in GitHub Desktop.
Save SteveBenner/11254428 to your computer and use it in GitHub Desktop.
Homebrew uninstall script
#!/usr/bin/env ruby
#
# CLI tool for locating and removing a Homebrew installation
# It replaces the official uninstaller, which is insufficient and often breaks
# If files were removed, the script returns 0; otherwise it returns 1
#
# http://brew.sh/
#
# Copyright (C) 2025 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Stephen Benner
# https://github.com/SteveBenner
#
# Contributors:
# - @AaronKulick
# - @lloeki
# - @lewismc
#
# NOTE: This script has ONLY been tested and verified for the following operating systems:
# - macOS 10.9
# - macOS 15.1
#
# TODO/ROADMAP
# - v1.3: test deletion code, make sure it can ALWAYS remove
# - v1.4: find and remove daemons installed by brew
require 'optparse'
require 'fileutils'
require 'pathname'
require 'open3'
$stdout.sync = true
# Default options
options = {
:quiet => false,
:verbose => true,
:dry_run => false,
:force => false,
:find_path => false
}
$opts = options # Provide access to the options within methods
optparser = OptionParser.new do |opts|
opts.banner = <<~STR
The unofficial Homebrew uninstaller that actually works!
Example usage (run the script with one of the following flags after the filename, such as `ruby unbrew.rb -v`):
STR
opts.on('-q', '--quiet', 'Quiet mode: suppress output.') do |setting|
options[:quiet] = setting
options[:verbose] = false
end
opts.on('-v', '--verbose', 'Verbose mode: print all operations') { |setting| options[:verbose] = setting }
opts.on('-d', '--dry', 'Dry run: print results, but perform no actual operations') do |setting|
options[:dry_run] = setting
end
opts.on('-f', '--force', 'Force removal of files, bypassing all prompts (USE WITH CAUTION!)') do |setting|
options[:force] = setting
end
opts.on('-p', '--find-path', 'Output Homebrew location if found, then exit') do |setting|
options[:find_path] = setting
options[:quiet] = true
end
opts.on_tail('-h', '--help', '--usage', 'Display this message') { puts opts; exit false }
opts.on_tail('--version', 'Display script version') { puts opts.version; exit false }
end
optparser.version = '1.2'
optparser.summary_width = 16
optparser.parse!
# Files installed into the Homebrew repository
BREW_LOCAL_FILES = %w[
.git
Cellar
Library/brew.rb
Library/Homebrew
Library/Aliases
Library/Formula
Library/Contributions
Library/LinkedKegs
]
# Files that Homebrew installs into other system locations
BREW_SYSTEM_FILES = %W[
#{ENV['HOME']}/Library/Caches/Homebrew
#{ENV['HOME']}/Library/Logs/Homebrew
/Library/Caches/Homebrew
]
$files = []
# Runs the given command in a sub-shell, expecting the output to be the
# path of a Homebrew installation. If a block is provided, it passes the shell output to
# the block for processing, using the block's return value as the new path.
# Known Homebrew files are then scanned for and added to the file list. The directory
# is tested for a Homebrew installation, and the git index is added if a
# valid repository is found. The function stops running once a Homebrew installation is
# found but accumulates untracked Homebrew files with each invocation.
#
# @param [String] cmd The shell command to execute.
# @yield [path] Passes the output from the shell command to the block.
# @yieldparam [String] path The path returned by the shell command.
# @yieldreturn [String] The processed brew prefix path.
# @return [void]
#
def locate_brew_path(cmd)
return if $brew_prefix # Stop testing if we find a valid Homebrew installation
unless $opts[:quiet]
msg = "Searching for Homebrew installation using '#{cmd}'..."
msg << " This MAY take a while, and will require your password." if cmd.include? 'locate'
msg << " This WILL take a while." if cmd.include? 'find'
puts msg
end
# Update the locate database manually if necessary (if it doesn't exist or is more than 5 minutes out of date)
if cmd.include? 'locate' # Suppress errors
locate_db = '/var/db/locate.database'
if File.exist?(locate_db) && (Time.now - File.mtime(locate_db) > 60 * 5)
system 'sudo /usr/libexec/locate.updatedb 2>/dev/null'
elsif !File.exist?(locate_db)
system 'sudo /usr/libexec/locate.updatedb 2>/dev/null'
end
end
# Run given shell command ALONG with any code passed-in via a block (returns on error/empty result)
begin
path = `#{cmd}`.chomp
return if path.empty?
path = yield(path) if block_given? # Pass the command output to your own fancy code block
rescue Errno::ENOENT
return
end
# Warn user if multiple installation directories are located
puts path
if path.split("\n").count > 1
puts 'WARNING: Multiple Homebrew locations found on your system!'
puts 'It is HIGHLY recommended to rerun this script until all installations are removed.'
end
begin
Dir.chdir(path) do
# Search for known Homebrew files and folders, regardless of git presence
$files += BREW_LOCAL_FILES.select { |file| File.exist? file }.map {|file| File.expand_path file }
$files += Dir.glob('**/{man,bin}/**/brew*')
# Test for Homebrew git repository (using open3 to suppress git error output)
repo_name, status = Open3.capture2 'git remote -v'
# Presence of the Homebrew repo indicates we are in the proper directory
if repo_name =~ /homebrew.git|Homebrew/
$brew_prefix = path
else
return
end
end
rescue StandardError # On normal errors, continue the program
return
end
end
# Attempt to locate Homebrew installation using a command and optional code block
# for processing the command results. Locating a valid path halts searching.
locate_brew_path 'brew --prefix'
locate_brew_path('which brew') { |output| File.expand_path('../..', output) }
locate_brew_path('command -v brew') { |output| File.expand_path('../..', output) }
# Fallback 1: Update the local `locate` database and search files using `locate`
cmd = options[:verbose] ? 'locate bin/brew' : 'locate bin/brew 2>/dev/null'
locate_brew_path cmd do |output| # ... And here
# Limit results to the first located path
Pathname.new(output.split("\n").find { |path| File.basename(path) == 'brew' }).parent.parent.to_s
end
# Fallback 2: Use `find` to search files (very slow!)
cmd = options[:verbose] ? 'find / -type f -name brew' : 'find / -type f -name brew 2>/dev/null'
locate_brew_path cmd do |output| # Suppress errors
# Limit results to the first located path
filtered_path = output.split("\n")
.grep(%r{/bin/brew\z})
.reject { |p| p.start_with?("/System") } # Don't look here (for a good reason)
.first
Pathname.new(filtered_path).parent.parent.to_s
end
# Found Homebrew installation
if $brew_prefix
if options[:find_path]
puts $brew_prefix
exit
end
unless options[:quiet]
puts "Homebrew found at: #{$brew_prefix}"
# Record kegs and taps for later output
brewed = `#{$brew_prefix}/bin/brew list`
tapped = `#{$brew_prefix}/bin/brew tap`
end
# Collect files indexed by git
begin
Dir.chdir($brew_prefix) do
# Update file list (use popen3 so we can suppress git error output)
Open3.popen3('git checkout master') { |stdin, stdout, stderr| stderr.close }
$files += `git ls-files`.split.map {|file| File.expand_path file }
end
rescue StandardError => e
puts e # Report any errors, but continue the script and collect any last files
end
else
puts 'No Homebrew location was found.' unless options[:quiet]
end
# Collect any files Homebrew may have installed throughout our system
puts 'Detecting vestigial system files...' unless options[:quiet]
$files += BREW_SYSTEM_FILES.select { |file| File.exist? file }
if $files.empty?
abort 'Failed to locate any Homebrew files!' if $files.empty?
else # We found files to remove!
# DESTROY! DESTROY! DESTROY!
unless options[:force]
puts "Delete #{$files.count} files? [y/n]"
abort unless gets.chomp =~ /y/i
end
rm =
if options[:dry_run]
lambda { |entry| puts "deleting #{entry}" unless options[:quiet] }
else
lambda { |entry| FileUtils.rm_rf(entry, :verbose => options[:verbose]) }
end
puts 'Deleting files...' unless options[:quiet]
$files.each &rm
# Print a list of formulae and kegs that were removed as part of the uninstallation process
unless brewed.to_s.empty?
puts
puts 'The following previously installed formulae were removed:'
puts brewed
end
unless tapped.to_s.empty?
puts
puts 'The following previously tapped kegs were removed:'
puts tapped
end
exit
end
@neversleepz
Copy link

@GrimaceVladimir You'll need to give execute permissions to the script. eg chmod a+x unbrew.rb
@Steve great script. worked well. Liked the verbose and dry run modes.

@SteveBenner
Copy link
Author

Hi guys

So I added a new feature (well, two, technically) in which the script will output a list of all formulas and kegs you had installed/tapped, after removing them. I thought it was a pretty clever addition, seeing as the script really does its best to wipe your system of all things Homebrew, possibly leaving you wondering what he hell you had installed previously! I'm in the habit of doing things like brew list > ~/Downloads/brews.txt in case of uninstalling package managers and such, but now people don’t need to worry about that. I tested it out, and this feature seems to work just fine with everything.

The other thing I added was just versioning, and an option to print the script version... I recently discovered that this is a builtin ability of OptionParser, which is cool, and fit with my desire to develop it further. I started maintaining a change log and tags already, and it had me thinking... I’ll probably hit up the maintainers at some point and see if they are interested in making a better official uninstaller, as what we have on the Homebrew wiki is pretty sad...

@SteveBenner
Copy link
Author

@GrimaceVladimir I edited my initial comment with more clear instructions for everyone, hope that helps. Also, I haven’t used MacPorts but from what I hear, it’s much more difficult to work with in general.

@schiff
Copy link

schiff commented Aug 11, 2014

Works like a charm. Thank you so much for your time. Removing Homebrew can definitely be a pain.

@bijancamp
Copy link

Thanks for helping a playa out. I found this from your AskDifferent post. It helped me debug a problem with one of my packages!

@ataipale
Copy link

Hi Steve,

I ran your script under sudo permissions and had the same problem as @cauerego .Thoughts on the best way to fix it other than permissions alterations?

@sdegutis
Copy link

lol @SteveBenner why not just forego your whole Instructions: section and just note that it can be run with sudo ruby unbrew.rb?

@SteveBenner
Copy link
Author

@ataipale Unfortunately, because the issues you and @cauerego experienced are rooted (haha!) in _permissions_, one of the most convoluted and complicated aspects of Unix, often there is no easy solution except attaining a deeper understanding of how they work, which is why I pointed towards this highly informative stackexchange thread as the ultimate answer to him.

I regrettably note that my response to @cauerego became a wall of text that probably came off as rambling… The fact is, this is a very complex and murky topic, which can be seen by how hotly the opinions in the thread are debated. But there you will also find far more than I can provide you, so really check it out. It’s very easy to misunderstand what’s going on otherwise; @cauerego, for example, mentions he set permissions with chown, but that command changes the file owner, a completely different operation.

As a reminder, this script only does ONE thing affecting your system, and that is remove Homebrew. Even when run with sudo, it’s not altering any permissions whatsoever.

@gigaimage
Copy link

I followed the instructions. Ran the script. It worked for me - Mac 10.10.2 Y

It ID 3493 files to be removed... I hope they were Homebrew install files only! :)

Thanks for a clear and one shot solution to overly debated/questioned prior solution

Update: Reinstall worked without any problem..

@giogadi
Copy link

giogadi commented Mar 18, 2015

Thanks for writing this.

When I ran this, however, it didn't actually remove any of the files installed by Homebrew. That is, it removed anything directly Homebrew related (Cellar, the config files) but left everything else that I installed using homebrew (bin, include, share, ...). Is this on purpose?

@jproberts14
Copy link

Thanks for creating this script Steve. The script worked great on my Mac Pro but when I tried to run it on my MacBook Pro, it's hanging right after it starts searching for the homebrew files. These are the 4 lines that I see before it hangs:

Searching for homebrew installation using 'brew --prefix'...
Searching for homebrew installation using 'which brew'...
Searching for homebrew installation using 'command -v brew'...
Searching for homebrew installation using 'brew --prefix'...

After a while, I got a message that said Delete 27 files? I said yes and it proceeded to delete those files. When I check out my /usr/local folder, some of the files are still there. When I compare the folder to the one on the Mac Pro, the files remaining are:

.gitignore
.yardopts
brew (in the bin folder)
codeofconduct.md
contributing.md
rubocop.yml (in Library folder - the Library also has the folders 3.2.6, 4.2, 4.3 which aren't on the Mac Pro)
License.txt
Readme.md
Homebrew folder under the Share folder (has 41 items in it) (the Homebrew folder is there on the MacPro but it's empty)
There is also a brew.1 file in the Man/Man1 folder as well
supporters.md

When I ran the script on the Mac Pro, it said it had found over 3,000 files.

Should I try to run the script again or is there another solution you can think of?

Thanks Steve!

Edit: I forgot to mention that I tried running it without sudo first, then ran it with sudo the second time.

@jproberts14
Copy link

Additional information regarding my uninstall issue. I remembered that the only difference in the installation between the mac pro and the macbook pro was xCode. On the mac pro, I had it installed via the command line interface. On the macbook pro, I went through the app store. Not sure if that has any relevance or not though.

@bsl787
Copy link

bsl787 commented Apr 1, 2015

Hello. I am very new. I had to re-install my Yosemite OS (iMac) because of a corrupted user profile. Homebrew insists on installing (trying) software to the corrupted user profile. I think I need to remove Homebrew. Looking at the directories in /usr/local/Cellar/, I notice some that appear to be ones I'd like to keep (python, octave, postgresql). If I run your script, do I lose these directories and re-install? How do I know which are the ones that Homebrew installed itself? For reference, permissions are root, group = wheel. Thank you.

BSL

@SteveBenner
Copy link
Author

@giogadi The directories you've mentioned, (bin/, include/, share/), among a few others, are created merely as a Unix convention (read more here) and they are used by many different programs. As it happens, Homebrew does not actually install anything in these folders—it is completely self-contained, relying on symbolic links from the original files to these locations, a very clean solution. When you use the uninstaller script, the symlinks should be removed along with Homebrew’s files. Anything remaining must have been installed by the user at some point, because the /usr/local/ directory itself is empty by default (and consequently safe to remove entirely!).

@jproberts14 I’m not sure how much help I can be to your situation, without more specific constraints to work with. Your Homebrew installation is definitely buggered—I would recommend deleting the entire contents of /usr/local/ and starting fresh. The fact your installation couldn’t be located using the first 3 commands indicates Homebrew wasn’t installed correctly in the first place, or that your shell environment is improperly configured. Any any rate, the ‘hanging’ is my last-ditch attempt to scrounge up Homebrew files in cases just like this… It’s far from spotless so again I recommend starting from scratch.

Bear in mind the script comes with several helpful options designed specifically with troubleshooting in mind. They are all inter-compatible. For instance, if you want to write a list of all the files potentially deleted by the script to a text file, you could run unbrew -d -f > results.txt which combines the ‘dry-run’ option (also known as no-op) with forced mode. _This is very cool._

As per convention, the script options and usage can be viewed by running it with -h or --help

@Roboji
Copy link

Roboji commented Aug 13, 2015

Hi Steve,
I experience the same problem as stated before by famousky.

$ sudo ./unbrew.rb
Searching for homewbrew installation using 'brew --prefix'...
./unbrew.rb:80:in ``': No such file or directory - brew (Errno::ENOENT)
from ./unbrew.rb:80:inlocate_brew_path' from ./unbrew.rb:106:in

'

I definitely have homebrew installed, so what am/can I do now??
Thanks!

@gethubd
Copy link

gethubd commented Aug 26, 2015

Hi Steve - thanks for the script. Just FYI I tried this on mac os x 10.6.8 and had the same problem as jproberts14. My guess is that the homebrew installs to different locations on 10.6 and the script doesn't see all of the files (however I am a complete novice so it's entirely just a guess). Your script didn't actually 'hang' as stated by jproberts14 - I was watching the activity in the activity monitor and it said ruby was very busy (with varying levels of cpu and disk activity) presumably looking for files. This took about 5 minutes before coming up with the "Delete 27 files ?" response. Most of the files it found were in the /local/ dir though it missed many others also in there.
I manually deleted all the remaining files in usr/local/ except for a few which were much older than the homebrew install.
Any tips on finding any other files to delete ( and I suspect there a a couple of thousand lurking around somewhere as between your script and manual deletion I think we only removed about 100-200), would be appreciated.
Thanks again for sharing.

@claudia1204
Copy link

Hi Steve,
I experienced the same problem.

sudo ./unbrew.rb

result:
Searching for homewbrew installation using 'brew --prefix'...
./unbrew.rb:105:in ``': No such file or directory - brew (Errno::ENOENT)
from ./unbrew.rb:105:inlocate_brew_path' from ./unbrew.rb:131:in

'

when I installed homebrew i quitted by control+C because of network problem. I do not know what should I do now....
Thanks!

@Roboji
use rm -rf /usr/local/Cellar /usr/local/.git I resolved problems, thanks~

@marktmiller
Copy link

I tried Homebrew's uninstall command, which just messed up my installation. I couldn't use any of the packages afterward, nor could I reinstall them. They need to fix that! Your script cleaned out what needed to be cleaned, and made it so I could reinstall Homebrew from scratch. Thanks so much for posting this!

@lloeki
Copy link

lloeki commented Apr 11, 2016

Watch out, this script doesn't stop potentially running services beforehand, something to the effect of:

for d in {/Users/`whoami`,}/Library/Launch{Agents,Daemons}/homebrew.mxcl.*.plist; do launchctl unload $d; done

@phuonghphan
Copy link

Thank you very much. It works very well.

@lewismc
Copy link

lewismc commented Sep 20, 2016

@SteveBenner, if you have homebrew or homebrew-core already checked out to /usr/local then the script will fail, incorrectly thinking that you have Homebrew installed as oppose to just checked out locally into /usr/local.
After pruning both homebrew and homebrew-core from /usr/local and using your script it all went well thanks.

@cmfrtblynmb728
Copy link

@SteveBenner I ran the script but it is stuck at the message "Searching for homewbrew installation using 'brew --prefix'..." for over an hour. Thanks!

Searching for homewbrew installation using 'brew --prefix'...
Searching for homewbrew installation using 'which brew'...
Searching for homewbrew installation using 'command -v brew'...
Searching for homewbrew installation using 'brew --prefix'...

This is all I see

@kevanchristmas
Copy link

Thanks for the effort and continued comments/support - worked flawlessly for me

@techartist
Copy link

techartist commented Nov 26, 2016

@SteveBenner I am having the same issues as @cmfrtblynmb728. Any suggestions? No options were given on the command line I just ran ./unbrew.rb.

@anthonyrollett
Copy link

Likewise, running on 10.10, needed to clear it out, seems to have worked exactly as intended. Much appreciated.

@anthonyrollett
Copy link

anthonyrollett commented Dec 11, 2016

I should have said that I found I had to clear brew out because I still use gfortran from HPC Mac OS X (http://hpc.sourceforge.net) and I needed to reinstall gcc and gfortran (versions 5.1). May not be the best approach these days, so happy to see further comments. Thanks again.

@binchuri
Copy link

Hello,
I was trying to use your script to uninstall Homebrew from Mac OSX Mojave (10.14.5) as it was unable to find the latest Xcode and Command Line Tools. However, it seems that the script is not actually deleting the files, they remain even after I run it. I am attaching the terminal output for your reference:

Apple$ ./unbrew.rb 
Searching for homewbrew installation using 'brew --prefix'...
Searching for homewbrew installation using 'which brew'...
Searching for homewbrew installation using 'command -v brew'...
Searching for homewbrew installation using 'brew --prefix'...
Delete 16 files? y
Deleting files...
rm -rf share/man/man1/brew-cask.1
rm -rf share/man/man1/brew.1
rm -rf bin/brew
rm -rf Homebrew/bin/brew
rm -rf share/man/man1/brew-cask.1
rm -rf share/man/man1/brew.1
rm -rf bin/brew
rm -rf Homebrew/bin/brew
rm -rf share/man/man1/brew-cask.1
rm -rf share/man/man1/brew.1
rm -rf bin/brew
rm -rf Homebrew/bin/brew
rm -rf usr/local/share/man/man1/brew-cask.1
rm -rf usr/local/share/man/man1/brew.1
rm -rf usr/local/bin/brew
rm -rf usr/local/Homebrew/bin/brew
Apples-MacBook-Air:unbrew Apple$ cd 
Apples-MacBook-Air:~ Apple$ cd /usr/l
lib/     libexec/ local/   
Apples-MacBook-Air:~ Apple$ cd /usr/l
lib/     libexec/ local/   
Apples-MacBook-Air:~ Apple$ cd /usr/local/
Apples-MacBook-Air:local Apple$ ls
Caskroom	etc		lib		sbin		var
Homebrew	gfortran	opt		share
bin		include		remotedesktop	texlive

Can you please advise what I should do? I also used unbrew.rb with sudo.

@ShadSterling
Copy link

I have vestigial homebrew files, but homebrew hasn't been installed for years, so I'm looking for an uninstaller that will clean up the files without having to reinstall homebrew. Unfortunately, like the official uninstaller, this doesn't work unless homebrew is installed.

@SteveBenner
Copy link
Author

SteveBenner commented Mar 18, 2022

2022 UPDATE:

I have added a list of fixes and improvements based on the last few years of feedback. Time to update the script! Thanks to the community for all the feedback and help testing this.

@lewismc, @lloeki these are great ideas and will be added as features

@SteveBenner
Copy link
Author

@Roboji, @claudia1204, @cmfrtblynmb728, @techartist, @binchuri

The output for all of your cases is similar, in that I can see a couple different possible bugs causing the same kind of issue for all of you.

There are several fixes I am going to implement, they are documented at the top of the script. It will take some time to test, but I’m confident I will soon know why it broke.

It would be helpful for those experiencing hangups using unbrew to kill it with CTL-C or similar, and report any errors that show up.

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