mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
599 lines
12 KiB
Markdown
599 lines
12 KiB
Markdown
|
[#]: collector: (lujun9972)
|
|||
|
[#]: translator: ( )
|
|||
|
[#]: reviewer: ( )
|
|||
|
[#]: publisher: ( )
|
|||
|
[#]: url: ( )
|
|||
|
[#]: subject: (Introduction to the Linux goto shell utility)
|
|||
|
[#]: via: (https://opensource.com/article/20/1/directories-autocomplete-linux)
|
|||
|
[#]: author: (Lazarus Lazaridis https://opensource.com/users/iridakos)
|
|||
|
|
|||
|
Introduction to the Linux goto shell utility
|
|||
|
======
|
|||
|
Learn how to use goto to alias and navigate to directories with
|
|||
|
autocomplete in Linux.
|
|||
|
![Files in a folder][1]
|
|||
|
|
|||
|
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`
|
|||
|
```
|
|||
|
|
|||
|
![goto demo][2]
|
|||
|
|
|||
|
## 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\nset 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][3]).
|
|||
|
* 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: [code]`goto -r last_release .`
|
|||
|
```
|
|||
|
* 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][4] terms, and contributions are welcomed. To learn more, visit the [Contributing][5] 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\n' "$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: \\\n$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\n'
|
|||
|
|
|||
|
# 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=$'\n' 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]}")")
|
|||
|
els
|