Bash function that changes directory Bash function that changes directory bash bash

Bash function that changes directory


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.


Here's a variant of @Jonathan Leffler's suggestion to streamline usage a little -- it makes the count argument optional, and avoids the need for $( ) around the command:

2dir() {# If first arg is a number, use it as a trim count; otherwise assume 2if [[ "$1" =~ ^[0-9]+$ ]]; then    count="$1"    shiftelse    count=2fiif [[ $# -lt 1 ]]; then  # Make sure a command was specified    echo "Usage: 2dir [count] command [commandargs ...]" >&2    return 1finame="$("$@")"  # Execute the remaining args as a command to get the target directorywhile [[ $count -gt 0 ]]; do name=$(dirname "$name"); ((count--)); donecd "$name"}

Example uses:

2dir 3 gem which rspec/core2dir gem which rspec/core


The command gem which rspec/core | 2dir 3is known as a "pipeline" in shell parlance. Each command in the pipeline is executed as a separate process. If one of the commands in the pipeline is a shell function, it may be executed by the current (interactive) shell process. But it is not guaranteed, and in your case this is not happening.

To fix your problem you just need to make sure that the function is evaluated in the interactive shell. You just need to fix the function and then use it differently. Here is the updated function:

2dir() {  declare -ir snip="$1"  declare dir="$2"  for i in $(seq 1 "$snip"); do      dir="${dir%/*}"  done  cd "$dir"}

You use it like this:

$ 2dir 3 "$(gem which rspec/core)"