The idea
Autocomplete is AWESOME. Ultimately it is just a function that suggests values. I typically put hard-coded values in first, then update with dynamic values once it is working!
Bash
Define a function (typically a private function with a leading underscore), then register which "command" should use this autocomplete.
_hello_complete() {
COMPREPLY=( $(compgen -W "world everyone" -- "${COMP_WORDS[COMP_CWORD]}") )
}
complete -F _hello_complete hello
COMPREPLY is the array Bash displays as suggestions. compgen -W filters the words in its list using the current word: the argument currently being typed. COMP_CWORD tells us which item in COMP_WORDS that is, so completion also works when the command has more than one argument.
You can test this by adding the above to your ~/.bashrc file, then type hello <Tab>.
Zsh
Zsh has a different approach.
_hello() {
_describe 'suggestions' '(world everyone)'
}
compdef _hello hello
_describe displays the supplied choices. suggestions is only a label for the completion group; it can be changed to anything meaningful.
You can test this by adding the above to your ~/.zshrc file, then type hello <Tab>.
Keywords
zsh provides helpers for common cases: _files completes file names, _directories completes directories, and _describe completes a list of named choices. Use compdef to connect a helper function to a command.
Dynamic suggestions
For dynamic suggestions, replace the static list with command output—just ensure it is safely quoted and fast enough to run on every Tab press.
Real world examples
"Notes" folder
notes() {
local dir="$HOME/notes"
if (( $# == 0 )); then
"$EDITOR" "$dir"
else
local files=()
for file in "$@"; do
files+=("$dir/$file")
done
"$EDITOR" "${files[@]}"
fi
}
_notes_completion() {
_files -W "$HOME/notes"
}
compdef _notes_completion notes
The command opens the whole folder with no arguments, or selected notes when file names are supplied. _files -W makes Zsh suggest files from that folder.
Switching Git branches
Many developers use a short helper for switching branches. This Zsh version suggests the repository's local branches dynamically:
gco() {
git switch "$@"
}
_gco() {
local -a branches
branches=("${(@f)$(git branch --format='%(refname:short)' --no-merged | awk '$1 != "main" && $1 != "develop"')}")
_describe 'branches' branches
}
compdef _gco gco
In ${(@f)$(command)}, $(command) captures the command's output, f splits it at newlines, and @ preserves each line as a separate array item. The quotes keep branch names grouped. This is roughly zsh's equivalent of bash's mapfile or readarray.
Now gco <Tab> completes to show unmerged branch names (but excludes main and develop). Add more conditions to the awk expression to exclude other branches. 🚀