Last Updated: February 25, 2016
· stevebenner

Manage your PATH with a Ruby script, using a single line of Bash

I'm a huge fan of the POSIX interface, but I really find Bash to be gross. I much prefer Ruby! Since I need to modify my shell config files every now and then by messing with the PATH in unpleasant ways, I really would rather take care of it with a language that is elegant and fun to program in.

Since environment propagation between shell environments is one-way only, I thought of a nice solution in which I could use Ruby to perform all the logic of modifying the PATH variable, while still capturing the results in my.bashrc config script. By defining the function setpath() { $(ruby ~/.pathconfig.rb SET); } in my .bashrc, I can configure PATH just how I need to at any given point. By storing the original PATH in an environment var at the same time, I can easily return the PATH to it's original state with a single command. Shiny!

Ruby is really convenient for this becuase you can nicely arrange your path entries in the order you want, then use cool tricks like the | union operator to seamelessly merge with the existing PATH. Plus, things like sorting and performing complex operations on collections are not messy with Ruby; you can easily implement functional, or rule-based directives for deciding your PATH.

Oh, and the only reason I use the funny name VANILLA_PATH is because I just thought it would reduce the likelihood of clashing with other variable names; you never know who is modifying the environment alongside your nifty script.

#!/usr/bin/env ruby
# PATH management via Ruby
# The following two functions accompany this script and
# shoudl be defined in ~/.bashrc, ~/.profile, or ~/.bash_profile:
#   setpath() { $(ruby ~/.pathconfig.rb SET); }
#   resetpath() { $(ruby ~/.pathconfig.rb UNSET); }
abort unless ARGV.first
if ARGV.first == 'SET'

  additions = [
    # Ensure binstubs have priority within a local Bundler project, and reduce use of `bundle exec`

    # NPM - Node.js package manager

  # Sort the path entries using Ruby's comparison operator, to achieve ideal precedence
  newpath = ENV['PATH'].split(':').uniq.sort! {|a, b| b.length <=> a.length }

  # Prepend additions to PATH, and remove duplicates with Set#union
  newpath = (additions | newpath).join ':'

  oldpath = ENV['PATH']
  newpath = ENV['VANILLA_PATH']
  oldpath = nil

puts "export PATH=#{newpath};"
puts "export VANILLA_PATH=#{oldpath}"

P.S. Here is the gist