Skip to content
dots
Esc
↑↓navigate↵open⌘Jpreview
On this page

bash and zsh

Secondary shells that share fish's aliases, starship prompt, atuin history, and cd-style helper functions.

bash and zsh are set up so a shell other than fish feels the same: the same aliases, the same starship prompt, atuin history, and ports of the fish helpers that have to change the shell’s own directory. zsh also gets ports of the fish key bindings.

Source (repo) Target
dot_bashrc ~/.bashrc
dot_zshrc ~/.zshrc
dot_config/zsh/keybinds.zsh ~/.config/zsh/keybinds.zsh
dot_config/shell/aliases.sh.tmpl ~/.config/shell/aliases.sh (template)
dot_config/bash/functions/*.bash ~/.config/bash/functions/
dot_config/zsh/functions/*.zsh ~/.config/zsh/functions/

aliases.sh.tmpl is the only template. It renders the alias list from .chezmoidata/aliases.yaml (chezmoi data). The other files apply as-is on every role.

Installation

bash is part of the base system. zsh itself is not listed in .chezmoidata/packages.yaml, but zsh-syntax-highlighting, starship, atuin, eza, and zoxide are, for both pacman and Homebrew. suvadu (the suv history tool) is declared under packages.shared.cargo, but none of the apply scripts install cargo packages, so install it manually if you want it.

~/.bashrc

Interactive shells only ([[ $- != *i* ]] && return). In order:

  1. PATH. Prepends ~/.local/bin if missing, sources ~/.cargo/env if present, and appends ~/.maestro/bin if it exists.
  2. Go. GOPATH=~/.local/share/go, GOBIN=$GOPATH/bin, and GOBIN prepended to PATH. mise provides GOROOT.
  3. Android SDK. ANDROID_HOME and ANDROID_SDK_ROOT set to ~/.local/share/android. platform-tools, cmdline-tools/latest/bin, and emulator are appended if they exist.
  4. Aliases. Sources ~/.config/shell/aliases.sh.
  5. Functions. Sources every ~/.config/bash/functions/*.bash.
  6. Flyline. If ~/.local/lib/libflyline.so exists, loads it with enable -f … flyline for richer line editing.
  7. Sources ~/.config/envman/load.sh if it is non-empty.
  8. Prompt. eval "$(starship init bash)" if starship is installed.
  9. atuin. Sources ~/.atuin/bin/env, then eval "$(atuin init bash)".

bash has no custom key bindings. atuin’s init binds ctrl-r and up.

~/.zshrc

  1. History. HISTFILE=~/.zsh_history, HISTSIZE=10000, SAVEHIST=10000, setopt SHARE_HISTORY HIST_IGNORE_DUPS.
  2. Completion. autoload -Uz compinit && compinit.
  3. The same PATH, Go, and Android blocks as .bashrc.
  4. Sources ~/.config/shell/aliases.sh.
  5. Sources every ~/.config/zsh/functions/*.zsh.
  6. Sources ~/.config/zsh/keybinds.zsh.
  7. eval "$(suv init zsh)" if suvadu is installed.
  8. Sources ~/.config/envman/load.sh if it is non-empty.
  9. eval "$(starship init zsh)" if starship is installed.
  10. Syntax highlighting. Sources the first zsh-syntax-highlighting.zsh found in /usr/share/zsh/plugins/zsh-syntax-highlighting/, /opt/homebrew/share/zsh-syntax-highlighting/, or /usr/local/share/zsh-syntax-highlighting/. The comment says this must stay last, because it wraps every zle widget defined before it.
  11. atuin. Sources ~/.atuin/bin/env and runs eval "$(atuin init zsh)". These lines come after the syntax-highlighting block, so despite the comment, syntax highlighting is not actually last.

zsh key bindings

keybinds.zsh ports fish_user_key_bindings.fish to zle. Unlike fish, zsh uses the emacs keymap: bindkey -e pins it so that $EDITOR=nvim does not switch zsh into vi mode. unsetopt flow_control frees ctrl-q and ctrl-s from the terminal.

A generic widget _zle-run is registered as run-<cmd> for purple, clip-last, gho, pj, omp, yy, and lazygit. It clears the pending display (zle -I), runs the command with the tty on stdin, then zle reset-prompt. Widgets run in the shell process, so directory changes made by gho and yy persist.

Key Action
ctrl-e yy (yazi, cd to its exit dir)
ctrl-f yy
ctrl-b purple (an external command; this repo does not provide it)
ctrl-y clip-last
ctrl-o gho (repo or vault picker, cd)
ctrl-p pj (task runner)
ctrl-a omp
ctrl-g lazygit
ctrl-q exit
ctrl-c At the prompt: kill the buffer, clear the screen and kitty scrollback (ESC[2J ESC[3J), reset the prompt

ctrl-c is the tty interrupt character, so bindkey '^C' would never fire. The binding is a TRAPINT function instead. At the prompt it runs zle .kill-buffer, prints the clear sequence, and runs zle .reset-prompt. While a command is running, SIGINT keeps its normal behavior (returns 128+SIGINT).

The fish-only binds ctrl-w (atuin AI), down (pj), and ? have no zsh equivalent. ctrl-h/j/k/l belong to kitty (kitty) and are not bound here.

Shared aliases: aliases.sh

~/.config/shell/aliases.sh is rendered from .chezmoidata/aliases.yaml. Each YAML section becomes a comment, and each item becomes alias <name>='<cmd>'. The template then appends shell-specific extras that are not in the YAML:

# Clipboard
if command -v wl-copy >/dev/null 2>&1; then
    alias cpy='wl-copy'
    alias pst='wl-paste'
elif command -v pbcopy >/dev/null 2>&1; then
    alias cpy='pbcopy'
    alias pst='pbpaste'
fi

q() {
    if [ -n "$YAZI_ID" ] && command -v ya >/dev/null 2>&1; then
        ya emit quit 2>/dev/null
    fi
    exit
}

alias celar='clear'
alias claer='clear'
if command -v eza >/dev/null 2>&1; then
    alias ls='eza --icons'
fi
  • Clipboard detection. cpy/pst map to Wayland’s wl-copy/wl-paste when available, else macOS pbcopy/pbpaste.
  • q. Exits the shell. If the shell was spawned from yazi’s own q binding ($YAZI_ID set), it runs ya emit quit first so the parent yazi closes too instead of bouncing back.
  • Typo guards and eza. celar/claer → clear, and ls → eza --icons when eza is installed. The file is sourced only by interactive shells, so scripts keep the real ls.

The full alias table, including the differences from fish, is on the Aliases page.

Per-shell functions

Each directory holds one function per file. bash and zsh source every file at startup; the bash and zsh versions have identical bodies.

Function Behavior
c [dir] clear with no argument, cd "$1" with one
cd [dir] No argument: $HOME. -: previous dir. Otherwise builtin cd, falling back to zoxide query -- "$1". Records the result with zoxide add "$PWD" and preserves cd’s exit status
clip-last Re-runs the previous history entry (fc -ln -1 -1, skipping itself if the line editor already recorded clip-last), pipes the output to wl-copy or pbcopy, and sends a notify-send -a shell notification if available
ghg <repo> ghg-clone "$@", cd into the printed path, clear && onefetch
ghn <name> command ghn "$@", cd into the printed path
gho command gho, cd into the printed path, clear, and onefetch if it is a git repo
yy [args] yazi --cwd-file=<tmp>, then cd to the directory written there if it changed

The ghg helper script is named ghg-clone because an unrelated cargo binary already uses the name ghg. The Scripts page explains why these helpers are shell functions and not scripts.

Was this page helpful?