Bash function that changes directory

Joshua Cheek picture Joshua Cheek · May 16, 2012 · Viewed 7.2k times · Source

I have a common use case that I'd like to write a function for: I often want to cd to some directory relative to some file.

My current workflow looks like this:

$ gem which rspec/core | xargs echo -n | pbcopy
$ cd *paste and delete end until direcory looks right*

note: gem which rspec/core prints something like "/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0/lib/rspec/core.rb"

I'd like it to look like this:

$ gem which rspec/core | 2dir 3

Which will cd me into "/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0" (passing the argument "3" tells it to remove "lib/rspec/core.rb" from the end)

This is the best I've gotten so far:

2dir() {
  read dir
  for i in $(seq 1 $1)
    do
      dir="${dir%/*}"
  done
  cd "$dir"
}

But the cd changes the function's directory, not mine. I've tried swapping it with an alias, but can't figure out how to make anonymous functions or pass the argument.

Answer

Jonathan Leffler picture Jonathan Leffler · May 16, 2012

I'd use:

2dir()
{
    name=${2:?'Usage: 2dir count path'}
    count=$1
    while [[ $count -gt 0 ]]; do name=$(dirname "$name"); ((count--)); done
    cd "$name"
}

and use it as:

2dir 3 $(gem which rspec/core)

This works where your pipeline can't. The cd in the pipe process affects that (sub-)shell, but cannot affect the current directory of the parent process. This function can be made to work.

And you can use your dir="${dir%/*}" in place of my dirname if you prefer, except that you'll end up in your home directory instead of the current directory (or root directory, depending on whether you gave a relative or absolute path name) if you specify 10 when there are only 5 components.