01 Основи
Преди да копираш
Структура, инструменти и философия — защо повечето .zshrc конфиги са бавни и object.
📁 Структура
Добрата практика: раздели конфига на файлове. ~/.zshrc само source-ва останалите.
~/.zsh/aliases.zsh, ~/.zsh/functions.zsh, ~/.zsh/exports.zsh.
Редактираш само релевантния файл, без да разравяш 500 реда.
~/.zshrc — skeleton структура
# ── Performance: measure startup time # zsh -i -c exit (виж колко ms отнема) # ── Path — само веднъж, правилно export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin" export PATH="$HOME/.local/bin:$PATH" # ── Editor export EDITOR='nvim' export VISUAL='nvim' # ── History — увеличи лимита export HISTSIZE=100000 export SAVEHIST=100000 export HISTFILE="$HOME/.zsh_history" setopt HIST_IGNORE_DUPS setopt HIST_IGNORE_SPACE # команди с интервал отпред не се пазят setopt SHARE_HISTORY # споделена history между терминали setopt EXTENDED_HISTORY # пази timestamp # ── Load modules source "$HOME/.zsh/exports.zsh" source "$HOME/.zsh/aliases.zsh" source "$HOME/.zsh/functions.zsh" source "$HOME/.zsh/prompt.zsh" # ── Completions (lazy load за скорост) autoload -Uz compinit if [[ -n "${ZDOTDIR}/.zcompdump(#qN.mh+24)" ]]; then compinit else compinit -C # -C = skip security check = бързо fi
setopt HIST_IGNORE_SPACE е golden rule — ако напишеш sudo rm -rf ... с интервал отпред, командата не се записва в history. Удобно за пароли и sensitive команди.
02 Aliases
Alias-и, дето имат смисъл
Не ls="ls -la". Alias-и, с които спестяваш реално писане всеки ден.
~/.zsh/aliases.zsh
# ══ NAVIGATION ══════════════════════════════════ alias .. ='cd ..' alias ... ='cd ../..' alias ....='cd ../../..' alias ~ ='cd ~' alias dl ='cd ~/Downloads' alias dt ='cd ~/Desktop' alias dev ='cd ~/Development' # ══ LS — замени с eza/exa ако имаш ══════════════ if command -v eza &>/dev/null; then alias ls ='eza --icons --group-directories-first' alias ll ='eza -la --icons --git --group-directories-first' alias lt ='eza --tree --level=2 --icons' alias lta='eza --tree --level=3 --icons --git-ignore' else alias ls ='ls -G' alias ll ='ls -lahG' fi # ══ GIT ═════════════════════════════════════════ alias g ='git' alias gs ='git status -sb' # компактен status alias ga ='git add -p' # patch mode — add само конкретни hunks alias gc ='git commit -v' # показва diff в commit editor-а alias gca='git commit --amend --no-edit' alias gp ='git push' alias gpl='git pull --rebase' alias gl ='git log --oneline --decorate --graph --all' alias gd ='git diff --stat' alias gds='git diff --staged' alias grh='git reset HEAD~1' # undo последния commit, запази промените alias gst='git stash' alias gsp='git stash pop' alias gco='git checkout' alias gb ='git branch -vv' # branches с upstream info # ══ NETWORK ══════════════════════════════════════ alias myip ='curl -s https://api.ipify.org && echo' alias localip='ipconfig getifaddr en0' alias flush ='sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder' alias ports ='sudo lsof -i -n -P | grep LISTEN' alias wget ='wget --no-check-certificate' # за dev # ══ MACOS SPECIFIC ═══════════════════════════════ alias o ='open .' alias show='defaults write com.apple.finder AppleShowAllFiles YES && killall Finder' alias hide='defaults write com.apple.finder AppleShowAllFiles NO && killall Finder' alias dsclean='find . -name ".DS_Store" -delete' alias sleepnow='pmset sleepnow' alias trash='osascript -e "tell application \"Finder\" to empty trash"' # ══ SHORTCUTS ════════════════════════════════════ alias zr ='source ~/.zshrc' # reload config alias ze ='$EDITOR ~/.zshrc' # редактирай config alias h ='history | tail -50' alias hg ='history | grep' # hg "docker" alias c ='clear' alias q ='exit' alias path='echo $PATH | tr ":" "\n" | nl' # numbered list на PATH # ══ SAFETY NET ═══════════════════════════════════ alias rm ='rm -i' # пита преди изтриване alias mv ='mv -i' alias cp ='cp -i' # ══ DOCKER ═══════════════════════════════════════ alias dk ='docker' alias dkc ='docker compose' alias dkps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"' alias dkrm='docker rm $(docker ps -aq)' # изтрий всички спрени alias dki ='docker images' alias dkx ='docker exec -it' # dkx container_name bash
ga = git add -p е game-changer. Вместо да add-ваш целия файл, избираш конкретни hunks интерактивно. Commit историята става атомарна и четима.
03 Functions
Functions — истинската сила
Alias-ите имат ограничения — не приемат аргументи. Functions нямат.
mkcd — Създай директория и влез в нея
utility
function mkcd() { mkdir -p "$1" && cd "$1" }
extract — Разархивирай всичко
utility
function extract() { if [ -f $1 ]; then case $1 in *.tar.bz2) tar xjf $1 ;; *.tar.gz) tar xzf $1 ;; *.tar.xz) tar xJf $1 ;; *.bz2) bunzip2 $1 ;; *.gz) gunzip $1 ;; *.tar) tar xf $1 ;; *.zip) unzip $1 ;; *.7z) 7z x $1 ;; *.rar) unrar x $1 ;; *) echo "'$1' - не знам как да разархивирам" ;; esac else echo "'$1' не е файл" fi }
Ползваш: extract archive.tar.gz — без да помниш кой флаг е за кой формат.
serve — HTTP сървър в текущата директория
network
function serve() { local port="${1:-8000}" local ip=$(ipconfig getifaddr en0 2>/dev/null || echo "localhost") echo "Serving at: http://${ip}:${port}" python3 -m http.server $port }
serve — порт 8000. serve 3000 — порт 3000. Показва local IP за достъп от телефон.
gcl — Изтрий merged branches
git
function gcl() { local main_branch="${1:-main}" git branch --merged $main_branch \ | grep -v "^\*\|$main_branch\|develop\|master" \ | xargs -n 1 git branch -d echo "Merged branches cleaned." }
gcl или gcl main. Изтрива всички локални branch-ове, merged в main — освен самия main/master/develop.
killport — Kill процеса на порт
system
function killport() { if [ -z "$1" ]; then echo "Usage: killport" ; return 1 fi local pid=$(lsof -ti :$1) if [ -n "$pid" ]; then echo "Killing PID $pid on port $1" kill -9 $pid else echo "Nothing on port $1" fi }
killport 3000 — find & kill. Край на "port already in use" разровяния.
sshcopy — Копирай SSH ключ на сървър
network
function sshcopy() { local key="${2:-$HOME/.ssh/id_ed25519.pub}" cat "$key" | ssh "$1" \ 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys' echo "Key copied to $1" }
sshcopy user@server — по-добър ssh-copy-id, работи дори без инсталиран пакет.
weather — Времето в терминала
network
function weather() { local city="${1:-Sofia}" curl -s "wttr.in/${city}?format=3" } function weatherfull() { local city="${1:-Sofia}" curl -s "wttr.in/${city}" }
weather → "Sofia: ⛅ +8°C" на един ред. weatherfull Varna → пълна 3-дневна прогноза в ASCII.
fcd — Fuzzy cd с fzf
utility
# Изисква: brew install fzf fd function fcd() { local dir dir=$(fd --type d --hidden --follow --exclude .git \ "${1:-.}" 2>/dev/null \ | fzf --preview 'eza --tree --level=2 --icons {}' \ --height 60% --border) \ && cd "$dir" } # Ctrl+F → fuzzy cd навсякъде bindkey -s '^f' 'fcd\n'
Ctrl+F отваря fuzzy finder за директории с preview. Намираш и влизаш в nested директория за 2 секунди.
04 Prompt
Prompt без oh-my-zsh
Информативен, бърз prompt — git branch, exit code, virtualenv. Без framework overhead.
✦ alex
❯
~/dev/project
❯
⎇ main ✚2
❯
❯
user · директория · git branch + промени · prompt символ
~/.zsh/prompt.zsh
# ── Git info function function _git_info() { local branch dirty branch=$(git symbolic-ref --short HEAD 2>/dev/null) || return if [[ -n $(git status --porcelain 2>/dev/null) ]]; then dirty=' ✚' fi echo " ⎇ ${branch}${dirty}" } # ── Virtualenv info function _venv_info() { [[ -n $VIRTUAL_ENV ]] && echo " ($(basename $VIRTUAL_ENV))" } # ── Colors autoload -U colors && colors setopt PROMPT_SUBST # ── Prompt lines NEWLINE=$'\n' PROMPT=' %F{magenta}╭─[%f%F{cyan}%n%f%F{magenta}]%f %F{blue}%~%f%F{yellow}$(_git_info)%f%F{green}$(_venv_info)%f %F{magenta}╰─%f%F{white}❯%f ' # ── Right prompt: timestamp + exit code RPROMPT='%(?..%F{red}✘ %?%f )%F{239}%T%f' # ── Simpler alt version (powerline style) # PROMPT='%K{magenta}%F{white} %n %f%k%K{blue}%F{magenta}❯%f%F{white} %~ %f%k%F{blue}❯%f%F{yellow}$(_git_info)%f %F{white}❯%f '
RPROMPT показва exit code само когато е грешка (✘ 127) и timestamp вдясно. Виждаш веднага дали командата е минала или се е счупила — без да четеш output.
05 Перформанс
Старт под 100ms
Типичните zsh проблеми и как се оправят без да жертваш функционалност.
🕐 Измери
time zsh -i -c exit — виж колко ms отнема стартирането. Над 300ms = имаш проблем. Цел: под 80ms.
| Проблем | Причина | Решение |
|---|---|---|
| compinit всеки път | Rebuild на completion cache при всеки старт | Проверявай дали cache е стар с -mh+24 (само 1x на 24h) |
| nvm load time | nvm source е бавен (~300ms) | Lazy load: заредени само когато пишеш node/npm |
| Много plugins | oh-my-zsh зарежда 50+ файла | Използвай само нужните; zsh-autosuggestions е достатъчен |
| git prompt бавен | git status при всеки prompt render | Добави timeout или disable в голям repo |
| brew shellenv | eval "$(brew shellenv)" е бавен | Hardcode пътищата вместо eval |
lazy load за nvm — спести ~300ms
# Вместо: source ~/.nvm/nvm.sh (бавно!) # Lazy load — зарежда nvm само при нужда export NVM_DIR="$HOME/.nvm" function _load_nvm() { [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && source "$NVM_DIR/bash_completion" } # Intercept node/npm/nvm — зарежда само при извикване for cmd in node npm npx nvm; do eval "function $cmd() { unfunction $cmd; _load_nvm; $cmd \"\$@\"; }" done
Startup time пада от ~400ms на ~60ms само с тази промяна. nvm се зарежда при първото node или npm извикване — без разлика в ползването.
06 Пълен конфиг
Всичко заедно
Copy-paste ready — един файл с всичко. Адаптирай пътищата за твоя setup.
⚠ Преди да замениш
Backup: cp ~/.zshrc ~/.zshrc.bak — после може да върнеш с cp ~/.zshrc.bak ~/.zshrc.
~/.zshrc — пълен конфиг ~280 реда
# ════════════════════════════════════════════════════ # .zshrc — Pro Setup # ════════════════════════════════════════════════════ # ── PATH export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$HOME/.local/bin:$PATH" # ── Core exports export EDITOR='nvim' export VISUAL='nvim' export LANG='en_US.UTF-8' export LC_ALL='en_US.UTF-8' export TERM='xterm-256color' # ── History export HISTSIZE=100000 export SAVEHIST=100000 export HISTFILE="$HOME/.zsh_history" setopt HIST_IGNORE_DUPS HIST_IGNORE_SPACE SHARE_HISTORY EXTENDED_HISTORY # ── Zsh options setopt AUTO_CD # cd без cd setopt CORRECT # предложи корекция при typo setopt GLOB_DOTS # включи dotfiles в glob setopt NO_BEEP # без звук при грешка # ── Completions (cached) autoload -Uz compinit if [[ -n "${ZDOTDIR:-$HOME}/.zcompdump(#qN.mh+24)" ]]; then compinit; else compinit -C fi zstyle ':completion:*' menu select zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' # case insensitive # ── Plugins (manual — без oh-my-zsh) if [ -f /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh ]; then source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=238' bindkey '^]' autosuggest-accept # Ctrl+] приема suggestion fi if [ -f /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh fi # ── Key bindings bindkey '^[[A' history-search-backward # ↑ търси в history bindkey '^[[B' history-search-forward bindkey '^A' beginning-of-line bindkey '^E' end-of-line bindkey '^[[H' beginning-of-line # Home bindkey '^[[F' end-of-line # End # ── FZF integration [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh export FZF_DEFAULT_OPTS='--height 40% --border --reverse --color=fg:#8892a4,bg:#080b12,hl:#ff2d78' export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" # ── Aliases alias .. ='cd ..' alias ... ='cd ../..' alias dl ='cd ~/Downloads' alias dev ='cd ~/Development' alias ls ='eza --icons --group-directories-first 2>/dev/null || ls -G' alias ll ='eza -la --icons --git 2>/dev/null || ls -lahG' alias g ='git' alias gs ='git status -sb' alias ga ='git add -p' alias gc ='git commit -v' alias gca ='git commit --amend --no-edit' alias gp ='git push' alias gpl ='git pull --rebase' alias gl ='git log --oneline --decorate --graph --all' alias gd ='git diff --stat' alias grh ='git reset HEAD~1' alias gst ='git stash' alias gsp ='git stash pop' alias gb ='git branch -vv' alias myip='curl -s https://api.ipify.org && echo' alias flush='sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder' alias ports='sudo lsof -i -n -P | grep LISTEN' alias o ='open .' alias show='defaults write com.apple.finder AppleShowAllFiles YES && killall Finder' alias hide='defaults write com.apple.finder AppleShowAllFiles NO && killall Finder' alias dsclean='find . -name ".DS_Store" -delete' alias zr ='source ~/.zshrc' alias ze ='$EDITOR ~/.zshrc' alias h ='history | tail -50' alias hg ='history | grep' alias c ='clear' alias path='echo $PATH | tr ":" "\n" | nl' alias rm ='rm -i' alias dk ='docker' alias dkps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"' # ── Functions function mkcd() { mkdir -p "$1" && cd "$1"; } function serve() { python3 -m http.server "${1:-8000}"; } function weather() { curl -s "wttr.in/${1:-Sofia}?format=3"; } function killport() { local pid=$(lsof -ti :$1) [ -n "$pid" ] && kill -9 $pid && echo "Killed PID $pid" || echo "Nothing on :$1" } function extract() { [ -f "$1" ] || { echo "File not found: $1"; return 1; } case "$1" in *.tar.gz|*.tgz) tar xzf "$1" ;; *.tar.bz2) tar xjf "$1" ;; *.tar.xz) tar xJf "$1" ;; *.zip) unzip "$1" ;; *.gz) gunzip "$1" ;; *.7z) 7z x "$1" ;; *) echo "Cannot extract: $1" ;; esac } function gcl() { git branch --merged "${1:-main}" \ | grep -v "^\*\|main\|master\|develop" \ | xargs -n 1 git branch -d } # ── NVM Lazy Load export NVM_DIR="$HOME/.nvm" function _load_nvm() { [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" } for cmd in node npm npx nvm; do eval "function $cmd() { unfunction $cmd; _load_nvm; $cmd \"\$@\"; }" done # ── Prompt autoload -U colors && colors setopt PROMPT_SUBST function _git_info() { local b=$(git symbolic-ref --short HEAD 2>/dev/null) || return local d=$([[ -n $(git status --porcelain 2>/dev/null) ]] && echo ' ✚') echo " ⎇ ${b}${d}" } PROMPT=' %F{magenta}╭─[%f%F{cyan}%n%f%F{magenta}]%f %F{blue}%~%f%F{yellow}$(_git_info)%f %F{magenta}╰─%f%F{white}❯%f ' RPROMPT='%(?..%F{red}✘%?%f )%F{239}%T%f' # ── END ──────────────────────────────────────────
Инсталирай prerequisites: brew install eza fzf fd zsh-autosuggestions zsh-syntax-highlighting
После: source ~/.zshrc или отвори нов терминал.