The goto shell utility allows users to navigate to aliased directories and also supports autocompletion.

How it works

Before you can use goto, you need to register your directory aliases. For example:

goto -r dev / home / iridakos / development

then change to that directory, e.g.:

goto dev

Autocompletion in goto

goto comes with a nice autocompletion script—whenever you press the Tab key after the goto command, Bash or Zsh will prompt you with suggestions of the aliases that are available:

$ goto < tab >

bc / etc / bash_completion.d

dev / home / iridakos / development

rubies / home / iridakos / .rvm / rubies

Installing goto

There are several ways to install goto.

Via script

Clone the repository and run the install script as a superuser or root:

git clone https: // github.com / iridakos / goto.git

cd goto

sudo . / install

Manually

Copy the file goto.sh somewhere in your filesystem and add a line in your .zshrc or .bashrc to source it.

For example, if you placed the file in your home folder, all you have to do is add the following line to your .zshrc or .bashrc file:

source ~ / goto.sh

MacOS Homebrew

A formula named goto is available for the Bash shell in MacOS:

brew install goto

Add colored output

echo -e " \$ include /etc/inputrc

set colored-completion-prefix on" >> ~ / .inputrc

Notes:

You need to restart your shell after installation.

You need to have the Bash completion feature enabled for Bash in MacOS (see this issue). You can install it with brew install bash-completion if you don't have it enabled.



Ways to use goto

Change to an aliased directory

To change to an aliased directory, type:

goto < alias >

For example:

goto dev

Register an alias

To register a directory alias, type:

goto -r < alias > < directory >

or

goto --register < alias > < directory >

For example:

goto -r blog / mnt / external / projects / html / blog

or

goto --register blog / mnt / external / projects / html / blog

Notes:

goto expands the directories, so you can easily alias your current directory with the following command and it will automatically be aliased to the whole path: goto -r last_release .

the directories, so you can easily alias your current directory with the following command and it will automatically be aliased to the whole path: Pressing the Tab key after the alias name provides the shell's default directory suggestions.

Unregister an alias

To unregister an alias, use:

goto -u < alias >

or

goto --unregister < alias >

For example:

goto -u last_release

or

goto --unregister last_release

Note: By pressing the Tab key after the command (-u or --unregister), the completion script will prompt you with the list of registered aliases.

List aliases

To get a list of your currently registered aliases, use:

goto -l

or

goto --list

Expand an alias

To expand an alias to its value, use:

goto -x < alias >

or

goto --expand < alias >

For example:

goto -x last_release

or

goto --expand last_release

Clean up aliases

To clean up the aliases from directories that are no longer accessible in your filesystem, use:

goto -c

or

goto --cleanup

Get help

To view the tool's help information, use:

goto -h

or

goto --help

Check the version

To view the tool's version, use:

goto -v

or

goto --version

Push before changing directories

To push the current directory onto the directory stack before changing directories, type:

goto -p < alias >

or

goto --push < alias >

Revert to a pushed directory

To return to a pushed directory, type:

goto -o

or

goto --pop

Note: This command is equivalent to popd but within the goto command.

Troubleshooting

If you see the error command not found: compdef in Zsh, it means you need to load bashcompinit. To do so, append this to your .zshrc file:

autoload bashcompinit

bashcompinit

Get involved

The goto tool is open source under the MIT License terms, and contributions are welcomed. To learn more, visit the Contributing section in goto's GitHub repository.

The goto script

goto()

{

local target

_goto_resolve_db



if [ -z "$1" ]; then

# display usage and exit when no args

_goto_usage

return

fi



subcommand="$1"

shift

case "$subcommand" in

-c|--cleanup)

_goto_cleanup "$@"

;;

-r|--register) # Register an alias

_goto_register_alias "$@"

;;

-u|--unregister) # Unregister an alias

_goto_unregister_alias "$@"

;;

-p|--push) # Push the current directory onto the pushd stack, then goto

_goto_directory_push "$@"

;;

-o|--pop) # Pop the top directory off of the pushd stack, then change that directory

_goto_directory_pop

;;

-l|--list)

_goto_list_aliases

;;

-x|--expand) # Expand an alias

_goto_expand_alias "$@"

;;

-h|--help)

_goto_usage

;;

-v|--version)

_goto_version

;;

*)

_goto_directory "$subcommand"

;;

esac

return $?

}



_goto_resolve_db()

{

GOTO_DB="${GOTO_DB:-$HOME/.goto}"

touch -a "$GOTO_DB"

}



_goto_usage()

{

cat <<\USAGE

usage: goto [<option>] <alias> [<directory>]



default usage:

goto <alias> - changes to the directory registered for the given alias



OPTIONS:

-r, --register: registers an alias

goto -r|--register <alias> <directory>

-u, --unregister: unregisters an alias

goto -u|--unregister <alias>

-p, --push: pushes the current directory onto the stack, then performs goto

goto -p|--push <alias>

-o, --pop: pops the top directory from the stack, then changes to that directory

goto -o|--pop

-l, --list: lists aliases

goto -l|--list

-x, --expand: expands an alias

goto -x|--expand <alias>

-c, --cleanup: cleans up non existent directory aliases

goto -c|--cleanup

-h, --help: prints this help

goto -h|--help

-v, --version: displays the version of the goto script

goto -v|--version

USAGE

}



# Displays version

_goto_version()

{

echo "goto version 1.2.4.1"

}



# Expands directory.

# Helpful for ~, ., .. paths

_goto_expand_directory()

{

builtin cd "$1" 2>/dev/null && pwd

}



# Lists registered aliases.

_goto_list_aliases()

{

local IFS=$' '

if [ -f "$GOTO_DB" ]; then

while read -r name directory; do

printf '\e[1;36m%20s \e[0m%s

' "$name" "$directory"

done < "$GOTO_DB"

else

echo "You haven't configured any directory aliases yet."

fi

}



# Expands a registered alias.

_goto_expand_alias()

{

if [ "$#" -ne "1" ]; then

_goto_error "usage: goto -x|--expand <alias>"

return

fi



local resolved



resolved=$(_goto_find_alias_directory "$1")

if [ -z "$resolved" ]; then

_goto_error "alias '$1' does not exist"

return

fi



echo "$resolved"

}



# Lists duplicate directory aliases

_goto_find_duplicate()

{

local duplicates=



duplicates=$(sed -n 's:[^ ]* '"$1"'$:&:p' "$GOTO_DB" 2>/dev/null)

echo "$duplicates"

}



# Registers and alias.

_goto_register_alias()

{

if [ "$#" -ne "2" ]; then

_goto_error "usage: goto -r|--register <alias> <directory>"

return 1

fi



if ! [[ $1 =~ ^[[:alnum:]]+[a-zA-Z0-9_-]*$ ]]; then

_goto_error "invalid alias - can start with letters or digits followed by letters, digits, hyphens or underscores"

return 1

fi



local resolved

resolved=$(_goto_find_alias_directory "$1")



if [ -n "$resolved" ]; then

_goto_error "alias '$1' exists"

return 1

fi



local directory

directory=$(_goto_expand_directory "$2")

if [ -z "$directory" ]; then

_goto_error "failed to register '$1' to '$2' - can't cd to directory"

return 1

fi



local duplicate

duplicate=$(_goto_find_duplicate "$directory")

if [ -n "$duplicate" ]; then

_goto_warning "duplicate alias(es) found: \

$duplicate"

fi



# Append entry to file.

echo "$1 $directory" >> "$GOTO_DB"

echo "Alias '$1' registered successfully."

}



# Unregisters the given alias.

_goto_unregister_alias()

{

if [ "$#" -ne "1" ]; then

_goto_error "usage: goto -u|--unregister <alias>"

return 1

fi



local resolved

resolved=$(_goto_find_alias_directory "$1")

if [ -z "$resolved" ]; then

_goto_error "alias '$1' does not exist"

return 1

fi



# shellcheck disable=SC2034

local readonly GOTO_DB_TMP="$HOME/.goto_"

# Delete entry from file.

sed "/^$1 /d" "$GOTO_DB" > "$GOTO_DB_TMP" && mv "$GOTO_DB_TMP" "$GOTO_DB"

echo "Alias '$1' unregistered successfully."

}



# Pushes the current directory onto the stack, then goto

_goto_directory_push()

{

if [ "$#" -ne "1" ]; then

_goto_error "usage: goto -p|--push <alias>"

return

fi



{ pushd . || return; } 1>/dev/null 2>&1



_goto_directory "$@"

}



# Pops the top directory from the stack, then goto

_goto_directory_pop()

{

{ popd || return; } 1>/dev/null 2>&1

}



# Unregisters aliases whose directories no longer exist.

_goto_cleanup()

{

if ! [ -f "$GOTO_DB" ]; then

return

fi



while IFS= read -r i && [ -n "$i" ]; do

echo "Cleaning up: $i"

_goto_unregister_alias "$i"

done <<< "$(awk '{al=$1; $1=""; dir=substr($0,2);

system("[ ! -d \"" dir "\" ] && echo " al)}' "$GOTO_DB")"

}



# Changes to the given alias' directory

_goto_directory()

{

local target



target=$(_goto_resolve_alias "$1") || return 1



builtin cd "$target" 2> /dev/null || \

{ _goto_error "Failed to goto '$target'" && return 1; }

}



# Fetches the alias directory.

_goto_find_alias_directory()

{

local resolved



resolved=$(sed -n "s/^$1 \\(.*\\)/\\1/p" "$GOTO_DB" 2>/dev/null)

echo "$resolved"

}



# Displays the given error.

# Used for common error output.

_goto_error()

{

(>&2 echo -e "goto error: $1")

}



# Displays the given warning.

# Used for common warning output.

_goto_warning()

{

(>&2 echo -e "goto warning: $1")

}



# Displays entries with aliases starting as the given one.

_goto_print_similar()

{

local similar



similar=$(sed -n "/^$1[^ ]* .*/p" "$GOTO_DB" 2>/dev/null)

if [ -n "$similar" ]; then

(>&2 echo "Did you mean:")

(>&2 column -t <<< "$similar")

fi

}



# Fetches alias directory, errors if it doesn't exist.

_goto_resolve_alias()

{

local resolved



resolved=$(_goto_find_alias_directory "$1")



if [ -z "$resolved" ]; then

_goto_error "unregistered alias $1"

_goto_print_similar "$1"

return 1

else

echo "${resolved}"

fi

}



# Completes the goto function with the available commands

_complete_goto_commands()

{

local IFS=$' \t

'



# shellcheck disable=SC2207

COMPREPLY=($(compgen -W "-r --register -u --unregister -p --push -o --pop -l --list -x --expand -c --cleanup -v --version" -- "$1"))

}



# Completes the goto function with the available aliases

_complete_goto_aliases()

{

local IFS=$'

' matches

_goto_resolve_db



# shellcheck disable=SC2207

matches=($(sed -n "/^$1/p" "$GOTO_DB" 2>/dev/null))



if [ "${#matches[@]}" -eq "1" ]; then

# remove the filenames attribute from the completion method

compopt +o filenames 2>/dev/null



# if you find only one alias don't append the directory

COMPREPLY=("${matches[0]// *}")

else

for i in "${!matches[@]}"; do

# remove the filenames attribute from the completion method

compopt +o filenames 2>/dev/null



if ! [[ $(uname -s) =~ Darwin* ]]; then

matches[$i]=$(printf '%*s' "-$COLUMNS" "${matches[$i]}")



COMPREPLY+=("$(compgen -W "${matches[$i]}")")

else

COMPREPLY+=("${matches[$i]// */}")

fi

done

fi

}



# Bash programmable completion for the goto function

_complete_goto_bash()

{

local cur="${COMP_WORDS[$COMP_CWORD]}" prev



if [ "$COMP_CWORD" -eq "1" ]; then

# if we are on the first argument

if [[ $cur == -* ]]; then

# and starts like a command, prompt commands

_complete_goto_commands "$cur"

else

# and doesn't start as a command, prompt aliases

_complete_goto_aliases "$cur"

fi

elif [ "$COMP_CWORD" -eq "2" ]; then

# if we are on the second argument

prev="${COMP_WORDS[1]}"



if [[ $prev = "-u" ]] || [[ $prev = "--unregister" ]]; then

# prompt with aliases if user tries to unregister one

_complete_goto_aliases "$cur"

elif [[ $prev = "-x" ]] || [[ $prev = "--expand" ]]; then

# prompt with aliases if user tries to expand one

_complete_goto_aliases "$cur"

elif [[ $prev = "-p" ]] || [[ $prev = "--push" ]]; then

# prompt with aliases only if user tries to push

_complete_goto_aliases "$cur"

fi

elif [ "$COMP_CWORD" -eq "3" ]; then

# if we are on the third argument

prev="${COMP_WORDS[1]}"



if [[ $prev = "-r" ]] || [[ $prev = "--register" ]]; then

# prompt with directories only if user tries to register an alias

local IFS=$' \t

'



# shellcheck disable=SC2207

COMPREPLY=($(compgen -d -- "$cur"))

fi

fi

}



# Zsh programmable completion for the goto function

_complete_goto_zsh()

{

local all_aliases=()

while IFS= read -r line; do

all_aliases+=("$line")

done <<< "$(sed -e 's/ /:/g' ~/.goto 2>/dev/null)"



local state

local -a options=(

'(1)'{-r,--register}'[registers an alias]:register:->register'

'(- 1 2)'{-u,--unregister}'[unregisters an alias]:unregister:->unregister'

'(: -)'{-l,--list}'[lists aliases]'

'(*)'{-c,--cleanup}'[cleans up non existent directory aliases]'

'(1 2)'{-x,--expand}'[expands an alias]:expand:->aliases'

'(1 2)'{-p,--push}'[pushes the current directory onto the stack, then performs goto]:push:->aliases'

'(*)'{-o,--pop}'[pops the top directory from stack, then changes to that directory]'

'(: -)'{-h,--help}'[prints this help]'

'(* -)'{-v,--version}'[displays the version of the goto script]'

)



_arguments -C \

"${options[@]}" \

'1:alias:->aliases' \

'2:dir:_files' \

&& ret=0



case ${state} in

(aliases)

_describe -t aliases 'goto aliases:' all_aliases && ret=0

;;

(unregister)

_describe -t aliases 'unregister alias:' all_aliases && ret=0

;;

esac

return $ret

}



goto_aliases=($(alias | sed -n "s/.*\s\(.*\)='goto'/\1/p"))

goto_aliases+=("goto")



for i in "${goto_aliases[@]}"

do

# Register the goto completions.

if [ -n "${BASH_VERSION}" ]; then

if ! [[ $(uname -s) =~ Darwin* ]]; then

complete -o filenames -F _complete_goto_bash $i

else

complete -F _complete_goto_bash $i

fi

elif [ -n "${ZSH_VERSION}" ]; then

compdef _complete_goto_zsh $i

else

echo "Unsupported shell."

exit 1

fi

done

This was originally published as the README in goto's GitHub repository and is reused with permission.