I do most of my automation and string manipulation scripting using Ruby. I know it’s not the most popular language these days, but it’s the one I know the best (thanks to my days of hacking on TextMate bundles) and it’s usually the fastest way for me to solve a problem. I have reams of snippets saved (and easily accessible with Snibbets) and thought I’d share a few that are useful for everyday scripting on macOS.
Scrub
Scrub uses the encode function to switch a string to UTF-16 discarding invalid characters (gremlins), then back to UTF-8. Applied as a String method, you can then use input.scrub
to get clean text with all gremlins removed.
class ::String
def scrub
encode('utf-16', invalid: :replace).encode('utf-8')
end
def scrub!
replace scrub
end
end
Check for macOS
This can be modified or turned into a case statement to determine operating system. I often only need it to check if I can run Mac-only tools like pbcopy
or not.
`uname`.strip == "Darwin"
Get macOS version
Just in case your script needs to behave differently across OS versions:
def macos_version
begin
`sw_vers`.strip.each_line do |line|
if line.strip =~ /ProductVersion:\s+([\d.]+)$/
return Regexp.last_match(1).to_f
end
end
return 0
rescue
warn 'Failed to retrieve macOS version'
Process.exit 1
end
end
Rounding numbers
class ::Numeric
# Round to nearest 5
def round5
((self * 0.2).round / 0.2)
end
# Round to nearest 10
def round10
((self * 0.1).round / 0.1)
end
end
Human-readable file size
The #to_human
method will convert 293592035
to “280MB”.
class Numeric
def to_human
units = %w{B KB MB GB TB}
e = (Math.log(self)/Math.log(1024)).floor
s = "%.1f" % (to_f / 1024**e)
s.sub(/\.?0*$/, units[e])
end
end
print File.size(File.expand_path(ARGV[0])).to_human
URL encoding
I used to use the CGI library for url encoding, but it doesn’t properly percent-encode spaces as %20
, but rather as +
, which breaks a lot of applications. The solution (as opposed to doing a manual search and replace) is to use the ERB library instead:
require 'erb'
ERB::Util.url_encode('string to encode')
Get a single keypress
For dialogs where you want to act as soon as a key is pressed without requiring pressing Return:
def get_keypress
system "stty raw -echo"
STDIN.getc
ensure
system "stty -raw echo"
end
key = get_keypress.chr
Get the longest string in an array of strings
Of limited utility but I use it all the time when doing search algorithms:
class ::Array
def longest_element
group_by(&:size).max.last[0]
# Leave off the [0] to get an array containing all of the longest elements when there's a tie
end
end
p ['short', 'longest', 'longer'].longest_element
#=> "longest"
Symbolize hash keys
If you’re creating nested hash objects that end up with a mix of string and symbol keys, the easiest thing to do is symbolize all keys. #to_sym
won’t do anything if the key is already a symbol.
class ::Hash
# Turn all keys into symbols, including nested hashes
def symbolize_keys
each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
end
end
# >> h = {'one' => 2, 'two' => { 'three' => 4 } }
# >> h.symbolize_keys
# {
# :one => 2,
# :two => {
# :three => 4
# }
# }
I hope that’s useful for aspiring Ruby scripters. If there’s interest, I can post (tons) more, but I also hope to get better about creating Gists that could be more easily searchable as a reference. The next step for Snibbets?
Bonus tip: Clipboard Preview
There’s a shell function I use frequently, easily replicable in any shell. It just shows you the contents of your clipboard (assuming text) without requiring a paste:
pbpaste | cat
In Fish that looks like:
# Defined in /Users/ttscoff/.config/fish/functions/cbp.fish @ line 1
function cbp --description 'ClipBoard Preview'
pbpaste | cat
end
Like I said, useful in any shell as a way to ensure what you think is on your clipboard actually is. Just type cbp
and get a preview before pasting anywhere.