Skip to content

Instantly share code, notes, and snippets.

@JackDrogon
Last active November 4, 2023 13:43
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save JackDrogon/53678a54a326b9aaf4102180eeb58cab to your computer and use it in GitHub Desktop.
Save JackDrogon/53678a54a326b9aaf4102180eeb58cab to your computer and use it in GitHub Desktop.
Mac OS X Office thinner: All files are link by hard link, so you needn't to worry about apple code signature. All same files are backuped to Trash dir
#!/usr/bin/env ruby
# -*- coding:utf-8 -*-
# TODO: Check same file with soft link
# TODO: Trim same file by apfs clone
require 'set'
require 'find'
require 'digest'
require 'fileutils'
TRASH = "#{ENV["HOME"]}/.Trash/Office/"
WORD_PATH = '/Applications/Microsoft Word.app'
EXCEL_PATH = '/Applications/Microsoft Excel.app'
ONENOTE_PATH = '/Applications/Microsoft OneNote.app'
OUTLOOK_PATH = '/Applications/Microsoft Outlook.app'
POWERPOINT_PATH = '/Applications/Microsoft PowerPoint.app'
PATHS = [WORD_PATH, EXCEL_PATH, ONENOTE_PATH, OUTLOOK_PATH, POWERPOINT_PATH]
@target_path = nil
@trim_paths = []
def find_all_files_without_prefix(dir)
set = Set.new
Find.find(dir) do |filename|
unless File.directory? filename or File.symlink? filename
set << filename[dir.length, filename.length] if filename != dir
end
end
return set
end
def find_same_files(dir1, dir2)
set1 = find_all_files_without_prefix(dir1)
set2 = find_all_files_without_prefix(dir2)
set = set1 & set2
same_files = set.select do |filename|
File.lstat(dir1+filename).ino != File.lstat(dir2+filename).ino \
and FileUtils.compare_file(dir1+filename, dir2+filename)
end
return same_files
end
def backup_file(filename)
dest_filename = TRASH + filename
FileUtils.mkdir_p File.dirname(dest_filename)
puts "Move #{filename} to #{dest_filename}"
FileUtils.mv filename, dest_filename
end
def trim_all_same_files(target_dir, dir)
same_files = find_same_files(target_dir, dir)
same_files.each do |filename|
backup_file dir+filename
FileUtils.ln(target_dir+filename, dir+filename)
end
end
def main
if Process.euid != 0
puts "Need root privilege, Please run: sudo ruby #{__FILE__}"
exit 1
end
PATHS.each do |pathname|
if Dir.exist?(pathname)
if @target_path
@trim_paths << pathname
else
@target_path = pathname
end
end
end
unless @target_path
puts "Don't exist path in #{PATHS}"
exit 1
end
puts "Trim #{@trim_paths} with #{@target_path}"
@trim_paths.each do |pathname|
puts "#{@target_path}, #{pathname}"
trim_all_same_files(@target_path, pathname)
end
puts "Office thinning completed!"
puts "Backup files in #{TRASH}, you view or delete the files later by Finder Trash."
end
if __FILE__ == $0
main
end
@JackDrogon
Copy link
Author

Just run sudo ruby office-thinner.rb. All backup files are in ~/.Trash/Office

@JackDrogon
Copy link
Author

JackDrogon commented Jan 9, 2017

All files are link by hard link, so you needn't to worry about apple code signature. The script checks same file by content and file's inode number(to prevent repeated link).

@fr0der1c
Copy link

I run into an error when using this script:

/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `block in find': No such file or directory (Errno::ENOENT)
	from /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `collect!'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `find'
	from office-thinner.rb:20:in `find_all_files_with_no_prefix'
	from office-thinner.rb:30:in `find_same_files'
	from office-thinner.rb:47:in `trims_all_same_files'
	from office-thinner.rb:61:in `block in <main>'
	from office-thinner.rb:59:in `each'
	from office-thinner.rb:59:in `<main>'

Any idea?

@JackDrogon
Copy link
Author

JackDrogon commented Jan 12, 2019

I run into an error when using this script:

/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `block in find': No such file or directory (Errno::ENOENT)
	from /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `collect!'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/find.rb:43:in `find'
	from office-thinner.rb:20:in `find_all_files_with_no_prefix'
	from office-thinner.rb:30:in `find_same_files'
	from office-thinner.rb:47:in `trims_all_same_files'
	from office-thinner.rb:61:in `block in <main>'
	from office-thinner.rb:59:in `each'
	from office-thinner.rb:59:in `<main>'

Any idea?

Sorry, I did not receive this feedback.
Maybe that you did'nt have some paths of ["/Applications/Microsoft Word.app", "/Applications/Microsoft Excel.app", "/Applications/Microsoft OneNote.app", "/Applications/Microsoft Outlook.app", "/Applications/Microsoft PowerPoint.app"].
I changed the script to fix the issue. You can try the newest script.

@jagdishadusumalli
Copy link

hi @JackDrogon
I ran the script and it executed as expected but I see in Finder the app sizes still show up as earlier.
Is it because of hard linking? If yes how do I verify trimming has been done and how much is the resultant size of apps?

@JackDrogon
Copy link
Author

hi @JackDrogon
I ran the script and it executed as expected but I see in Finder the app sizes still show up as earlier.
Is it because of hard linking? If yes how do I verify trimming has been done and how much is the resultant size of apps?

Yep, because of hard link. You can see by running command df -hT.

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