DevOps Advanced 22 мин • 21.09.2026 г.

Чист .bashrc: alias-и в отделен файл и готови функции

Как разделих разхвърляния си .bashrc на две – настройки и функции в един файл, alias-и в друг (~/.fedya_aliases) – и какво точно да добавите в .bashrc, за да чете втория. Пълен код и обяснение за всеки alias и функция.

Терминал с два отворени файла – .bashrc и .fedya_aliases – и подредени секции с alias-и

С времето моят ~/.bashrc се превърна в смесица от всичко: настройки на history, промпт, функции и над петдесет alias-а, наредени в реда, в който съм ги добавял. Всеки път, когато трябваше да променя една малка команда, ровех из файл, в който нищо не беше на мястото си.

Затова го подредих и го разделих на две:

Файл Какво съдържа
~/.bashrc Настройки на Bash (history, промпт, цветове), външни инструменти (keychain, Starship, NVM) и функции
~/.fedya_aliases Alias-и, подредени в 11 тематични секции, и две помощни функции за преглед и търсене в тях (aliases, fa)

В .bashrc остава един малък блок, който зарежда втория файл. Така alias-ите ми са на едно място, лесно ги архивирам, а ако някой от вас иска да ги вземе като идея, му давам един файл, а не целия си .bashrc.

В статията ще намерите:

  1. Какво точно да добавите в .bashrc, за да чете другия файл.
  2. Пълния код на ~/.fedya_aliases с обяснение на всяка секция.
  3. Пълния код на ~/.bashrc с обяснение на настройките и функциите и как се работи с тях.
  4. Как да приложите всичко при себе си, без да рискувате да останете с неработещ терминал.

Най-важното: как .bashrc чете файла с alias-и

Ето блокът, който стои в самия край на моя .bashrc:

# ЗАРЕЖДАНЕ НА ЛИЧНИТЕ ALIASES
if [ -f ~/.fedya_aliases ]; then
    . ~/.fedya_aliases
fi

Какво прави той:

  • [ -f ~/.fedya_aliases ] проверява дали файлът съществува. Ако го няма, .bashrc просто продължава и не показва грешка.
  • . (същото като source) изпълнява файла в текущата сесия. Само така alias-ите остават налични в терминала. Ако файлът се стартира като отделен скрипт, alias-ите изчезват заедно с него.
  • Блокът е най-накрая, за да има файлът с alias-и последната дума. Ако в .bashrc е дефиниран alias със същото име, по-късната дефиниция печели.

Добавяне с една команда

Ако не искате да отваряте редактор, добавете блока директно:

cat >> ~/.bashrc <<'EOF'

# ЗАРЕЖДАНЕ НА ЛИЧНИТЕ ALIASES
if [ -f ~/.fedya_aliases ]; then
    . ~/.fedya_aliases
fi
EOF

Забележете двойното >> – то добавя в края на файла. Единично > би го презаписало.

Презареждане и проверка

Промените в .bashrc влизат в сила в нов терминал. В отворен терминал ги заредете така:

source ~/.bashrc

Проверете, че alias-ите са налични:

type ll        # ll is aliased to `ls -alF'
alias ll       # същото, в форма, която можете да копирате
aliases        # всички alias-и, оцветени и подредени
aliases docker # само тези, които съдържат "docker"
fa             # интерактивно търсене в alias-и и функции

Внимание при преминаване от стар .bashrc. Ако в отворения терминал още е зареден стар alias със същото име като функция от новия файл (при мен това е aliases), alias-ът има предимство пред функцията. Отворете нов терминал или първо изпълнете unalias aliases.

Още няколко правила, които е полезно да знаете

  • Debian и Ubuntu вече имат такъв механизъм. Стандартният .bashrc там зарежда файла ~/.bash_aliases, ако съществува. Ако не сте изтрили този блок, е достатъчно да кръстите файла .bash_aliases и не се налага да добавяте нищо. Аз избрах собствено име, за да е ясно кой е моят.
  • Alias-ите работят само в интерактивен терминал. В скриптовете Bash не ги разгъва по подразбиране. Ако командата ви трябва и в скрипт, направете я функция или скрипт.
  • Проверка на синтаксиса преди да отворите нов терминал: bash -n ~/.fedya_aliases (не изпълнява нищо, само търси синтактични грешки). Ако всичко е наред, командата не връща изход.
  • Как да заобиколя alias за един път: \ls или command ls изпълняват оригиналната команда. Премахване на alias за текущата сесия: unalias име.

Файл 1: ~/.fedya_aliases

Ето пълното съдържание на файла. Alias-ите са в 11 секции с еднакъв формат: коментар, а под него самата команда.

# =================================================================
# FEDYA ALIASES
# =================================================================
# Всички лични Bash alias-и
#
# Подредени по предназначение:
#   01. System / Navigation
#   02. Files / Search
#   03. Network
#   04. Docker
#   05. Proxmox / Ansible / Backup
#   06. Git / Development
#   07. Node / NPM / Astro
#   08. Terminal / Bash
#   09. Directories / Projects
#   10. SSH / Security
#   11. Notifications
#
# Преглед и търсене:
#   aliases [дума]   (или myaliases) – списък на alias-и; с дума – търсене
#   fa                               – интерактивно търсене (fzf) в alias-и и функции
# =================================================================


# =================================================================
# 01. SYSTEM / NAVIGATION
# =================================================================

# Изчистване на терминала
alias c='clear'

# Една / две директории нагоре
alias ..='cd ..'
alias ...='cd ../..'

# Преглед на PATH — по един елемент на ред
alias path='echo -e ${PATH//:/\\n}'

# Преглед на Bash history
alias h='history'

# Системен update
alias update='sudo apt update && sudo apt upgrade -y'

# Рестарт
alias reboot='sudo shutdown -r now'

# Преглед и търсене на alias-и и функции
#   aliases          – всички alias-и, оцветени и по азбучен ред
#   aliases docker   – alias-и (по име или команда) и функции (по име), съдържащи "docker"
# (функция, защото при alias кавичките в awk/sed стават неудобни)
aliases() {
    local q="${1,,}" name value found=0
    for name in $(printf '%s\n' "${!BASH_ALIASES[@]}" | sort); do
        value=${BASH_ALIASES[$name]}
        value=${value//\\$'\n'/}      # маха "\" + нов ред (многоредови alias-и)
        value=${value//$'\n'/ }       # останалите нови редове -> интервал
        while [[ $value == *"  "* ]]; do value=${value//  / }; done
        if [[ -z $q || ${name,,} == *"$q"* || ${value,,} == *"$q"* ]]; then
            printf '\033[1;36m%-14s\033[0m \033[1;33m%s\033[0m\n' "$name" "$value"
            found=1
        fi
    done
    if [[ -n $q ]]; then
        for name in $(declare -F | awk '{print $3}' | grep -v '^_' | sort); do
            if [[ ${name,,} == *"$q"* ]]; then
                printf '\033[1;35m%-14s\033[0m (функция) виж: type %s\n' "$name" "$name"
                found=1
            fi
        done
        (( found )) || echo "Няма съвпадения за: $1"
    fi
}
alias myaliases='aliases'

# Търсене на alias-и [A] и функции [F] – по име, команда или описание
# Enter извежда дефиницията. Ползване: fa
fa() {
    local -A desc
    local name value sel

    # Описанието е първият коментар над дефиницията
    while IFS=$'\t' read -r name value; do
        desc[$name]=$value
    done < <(awk '
        /^[[:space:]]*$/ { c = ""; inrun = 0; next }
        /^#/ {
            if ($0 ~ /^# ?=/) { c = ""; inrun = 1; next }
            if (!inrun) c = substr($0, 3)
            inrun = 1
            next
        }
        { inrun = 0 }
        /^alias [^=]+=/ {
            n = $2
            sub(/=.*/, "", n)
            if (c != "") print n "\t" c
            next
        }
        /^[A-Za-z_][A-Za-z0-9_]*\(\) *\{/ {
            n = $1
            sub(/\(\).*/, "", n)
            if (c != "") print n "\t" c
            c = ""
            next
        }
    ' ~/.bashrc ~/.fedya_aliases)

    sel=$(
        {
            # Alias-и
            for name in $(compgen -A alias | sort); do
                value=${BASH_ALIASES[$name]}
                value=${value//\\$'\n'/}
                value=${value//$'\n'/ }

                while [[ $value == *"  "* ]]; do
                    value=${value//  / }
                done

                printf '[A] %-12s %s' "$name" "$value"
                [[ -n ${desc[$name]} ]] && printf '   # %s' "${desc[$name]}"
                printf '\n'
            done

            # Functions
            for name in $(compgen -A function | grep -Ev '^(_|nvm|starship)' | sort); do
                printf '[F] %-12s' "$name"
                [[ -n ${desc[$name]} ]] && printf ' # %s' "${desc[$name]}"
                printf '\n'
            done
        } | fzf "$@"
    ) || return

    [[ -z $sel ]] && return

    # Извлича името независимо дали е [A] или [F]
    name=$(printf '%s\n' "$sel" | head -1 | sed -E 's/^\[[AF]\] +([^ ]+).*/\1/')

    echo
    type "$name"
}


# =================================================================
# 02. FILES / LS / SEARCH
# =================================================================

# Цветен ls
alias ls='ls --color=auto'

# Основни варианти на ls
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'

# Оцветен grep
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'

# Disk usage
alias dfh='df -h'
alias duh='du -sh *'

# Цветен преглед на текстови файлове (Изисква: pygmentize)
alias cats='pygmentize -g'

# Дървовидна структура
alias tree='find . -print | sed -e '\''s;[^/]*/;|____;g;s;____|; |;g'\'''

# Следене на лог
alias tf='tail -f'

# Специализиран скрипт
alias las='cat /usr/local/sbin/as'


# =================================================================
# 03. NETWORK
# =================================================================

# Показва публичния IP адрес
alias cip='curl icanhazip.com'

# Ping към Google DNS
alias pingg='ping 8.8.8.8'


# =================================================================
# 04. DOCKER
# =================================================================

# Кратък Docker alias
alias d='docker'

# Docker Compose
alias dcd='docker compose down'
alias dcu='docker compose up -d'

# Работещи контейнери — компактен изглед
alias dps='docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}"'

# Всички Docker images
alias dimg='docker images'

# Следене на container logs
alias dlf='docker logs -f'

# Същото като dlf — запазено за обратна съвместимост
alias dlog='docker logs -f'

# Изтриване на всички спрени containers
alias drm='docker rm $(docker ps -a -q)'

# Изтриване на всички images
alias drmi='docker rmi $(docker images -q)'

# Спиране на всички работещи containers
alias dstop='docker stop $(docker ps -q)'

# ctop — Docker container monitoring
alias ctop='docker run --rm -ti \
    --name=ctop \
    -v /var/run/docker.sock:/var/run/docker.sock \
    quay.io/vektorlab/ctop:latest'


# =================================================================
# 05. PROXMOX / ANSIBLE / BACKUPS
# =================================================================

# SSH към Proxmox host
alias prox='ssh prox'

# Backup на CT 114
alias backups_wp='backup_ct 114'

# Списък на Proxmox backup файловете
alias list_backups='ls -lah /mnt/d/backup_proxmox/'

# Ansible — добавяне на SSH key
alias key='ansible-playbook \
    /home/fedia/ansible/add-key.yaml \
    -i /home/fedia/ansible/servers.ini'


# =================================================================
# 06. GIT / DEVELOPMENT
# =================================================================

# Git помощен файл
alias githelp='cat /home/fedia/github_ssh_setup.txt'

# Git помощно меню
alias gitmenu='/home/fedia/githelp_menu.sh'


# =================================================================
# 07. NODE / NPM / ASTRO
# =================================================================

# Стартиране на development server
alias dev='npm run dev'

# Build (поддържаме и двата варианта за удобство)
alias build='npm run build'
alias bild='npm run build'


# =================================================================
# 08. TERMINAL / BASH
# =================================================================

# Преглед на .bashrc
alias brc='cat ~/.bashrc'

# Показване на command database
alias cmdlist='grep -v "^#" ~/.commands.txt |
    grep -v "^$" |
    column -t -s "||"'

# Редактиране на command database
alias cmdedit='nano ~/.commands.txt'

# Добавяне на команда в database
alias cmdadd='_cmdadd() {
    echo "$1 || $2 || $3" >> ~/.commands.txt &&
    echo "✓ Добавено: $1"
}; _cmdadd'


# =================================================================
# 09. DIRECTORIES / PROJECTS
# =================================================================

# Windows Desktop
alias desktop='cd /mnt/c/Users/fedia/Desktop'

# Защита от грешно изписване на desktop
alias dekstop='cd /mnt/c/Users/fedia/Desktop'

# Nextcloud
alias nex='cd /mnt/c/Users/fedia/Nextcloud'

# Здраве & Красота
alias zdrave='cd /mnt/c/Users/fedia/Desktop/zdravemag'

# Train Brakes
alias vlak='cd /mnt/c/Users/fedia/Desktop/trainbrakes'

# LinuxDev / DevOps
alias devops='cd /mnt/c/Users/fedia/Desktop/linux-devops'

# FS проект
alias fs='cd /mnt/c/Users/fedia/Desktop/fs'


# =================================================================
# 10. SSH / SECURITY
# =================================================================

# SSH без проверка на host key
alias sship='ssh \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -o LogLevel=ERROR'


# =================================================================
# 11. NOTIFICATIONS
# =================================================================

# Известие след приключване на дълга команда
alias alert='notify-send --urgency=low \
    -i "$([ $? = 0 ] && echo terminal || echo error)" \
    "$(history | tail -n1 |
    sed -e '\''s/^\s*[0-9]\+\s*//;
    s/[;&|]\s*alert$//'\'')"'


# =================================================================
# END OF FEDYA ALIASES
# =================================================================

01. System / Navigation

Alias Какво прави
c clear – изчиства терминала
.. и ... Една и две директории нагоре
path Показва $PATH по един елемент на ред
h Кратко за history
update sudo apt update && sudo apt upgrade -y – обновява системата с една дума
reboot sudo shutdown -r now. В WSL не рестартира Windows хоста; дистрибуцията се рестартира от Windows с wsl --terminate или wsl --shutdown
aliases, myaliases Показват всички alias-и, оцветени и по азбучен ред. С дума след тях (aliases docker) търсят
fa Интерактивно търсене с fzf в alias-и и функции – по име, команда или описание (виж раздела „Как да намеря забравен alias или функция“)

aliases и fa са единствените неща в този файл, които не са alias-и, а функции, дефинирани директно. Причината е, че многоредовите alias-и и кавичките в sed и awk бързо стават нечетими вътре в единични кавички, а функцията се чете и поправя лесно.

aliases работи така:

aliases() {
    local q="${1,,}" name value found=0
    for name in $(printf '%s\n' "${!BASH_ALIASES[@]}" | sort); do
        value=${BASH_ALIASES[$name]}
        value=${value//\\$'\n'/}      # маха "\" + нов ред (многоредови alias-и)
        value=${value//$'\n'/ }       # останалите нови редове -> интервал
        while [[ $value == *"  "* ]]; do value=${value//  / }; done
        if [[ -z $q || ${name,,} == *"$q"* || ${value,,} == *"$q"* ]]; then
            printf '\033[1;36m%-14s\033[0m \033[1;33m%s\033[0m\n' "$name" "$value"
            found=1
        fi
    done
    if [[ -n $q ]]; then
        for name in $(declare -F | awk '{print $3}' | grep -v '^_' | sort); do
            if [[ ${name,,} == *"$q"* ]]; then
                printf '\033[1;35m%-14s\033[0m (функция) виж: type %s\n' "$name" "$name"
                found=1
            fi
        done
        (( found )) || echo "Няма съвпадения за: $1"
    fi
}
  • BASH_ALIASES е вграден в Bash масив с всички дефинирани в момента alias-и. Ключът е името, стойността е командата.
  • ${1,,} превръща думата за търсене в малки букви (Bash 4+), а ${name,,} прави същото за имената, така че търсенето не различава главни и малки букви.
  • Alias се показва, ако думата е в името или в командата му. Без дума се показват всички.
  • Функциите се изброяват с declare -F, а grep -v '^_' пропуска вътрешните (тези на bash-completion). Те се търсят само по име и се показват в пурпурно с подсказка type име.
  • Двата реда с ${value//…/…} слепват многоредовите alias-и (като ctop и key) в един ред.
  • printf печата името в циан и командата в жълто.

Пример за употреба:

aliases                # всички alias-и
aliases docker         # само тези с "docker" в името или в командата
aliases | less -R      # прелистване, като -R запазва цветовете
Alias Какво прави
ls ls --color=auto – цветен изход
l, la, ll Компактен списък; всички файлове без . и ..; подробен списък със скритите файлове
grep, fgrep, egrep Същите команди с цветно оцветяване на съвпаденията
dfh df -h – свободно място на дисковете
duh du -sh * – размер на всичко в текущата директория. Не включва скритите файлове (тези с точка)
cats Оцветен преглед на файл (cats файл.py). Изисква pygmentize
tree Дървовидна структура на директорията с find и sed. Заменя истинската команда tree, ако е инсталирана
tf tail -f – следене на лог (tf /var/log/syslog)
las Показва съдържанието на моя собствен скрипт /usr/local/sbin/as

pygmentize идва с пакета python3-pygments (sudo apt install python3-pygments).

03. Network

Alias Какво прави
cip curl icanhazip.com – показва публичния ви IP адрес
pingg ping 8.8.8.8 – бърза проверка дали има интернет. Работи безкрайно, спира с Ctrl+C

04. Docker

Alias Какво прави
d Кратко за docker
dcu / dcd docker compose up -d / docker compose down
dps docker ps в компактна таблица: ID, име, статус, image, портове
dimg docker images
dlf, dlog docker logs -f – следене на лога на контейнер (dlf име). dlog е същото, останало от по-стар вариант
drm Изтрива всички спрени контейнери
drmi Опитва да изтрие всички images
dstop Спира всички работещи контейнери
ctop Стартира ctop (мониторинг на контейнерите) като временен контейнер

Пример:

dcu                # стартира проекта от docker-compose.yml
dps                # виждам какво работи
dlf my_container   # следя лога
dcd                # спирам и махам контейнерите на проекта

⚠️ Внимавайте с drm, drmi и dstop. Те действат върху всичко на машината, а не само върху текущия проект, и не питат за потвърждение. drm не може да изтрие работещи контейнери (Docker го отказва без -f), но drmi ще изтрие всеки image, който не се ползва от контейнер (дори спрян), и после ще ги теглите отново. Ако искате по-безопасен вариант, вградените docker container prune и docker image prune питат за потвърждение.

ctop работи така: --rm изтрива временния контейнер след излизане, -ti дава интерактивен терминал, а -v /var/run/docker.sock:… му позволява да чете състоянието на Docker. Първото стартиране тегли image-а.

05. Proxmox / Ansible / Backups

Alias Какво прави
prox ssh prox – връзка към Proxmox сървъра. Изисква запис Host prox в ~/.ssh/config
backups_wp backup_ct 114 – теглене на най-новия бекъп на контейнер 114 (функцията е описана по-долу)
list_backups Списък на свалените бекъпи в /mnt/d/backup_proxmox/
key Пуска Ansible playbook, който добавя моя SSH ключ на сървърите от servers.ini

06. Git / Development

Alias Какво прави
githelp Показва бележките ми как се настройва SSH достъп до GitHub
gitmenu Пуска помощното ми меню за Git (githelp_menu.sh)

07. Node / NPM / Astro

Alias Какво прави
dev npm run dev – development сървър
build, bild npm run build. Втората форма е защита от моя често срещана печатна грешка

08. Terminal / Bash

Alias Какво прави
brc Показва .bashrc
cmdlist Показва базата ми с команди като подредена таблица
cmdedit Отваря базата в nano
cmdadd Добавя нова команда в базата

Базата е обикновен текстов файл ~/.commands.txt. Всеки ред има три части, разделени с ||:

Описание || тагове || команда

Ето пример (файлът ~/.commands.txt се създава автоматично при първото добавяне):

cmdadd "Свободно място" "disk,df" "df -h"
cmdadd "Следене на лог" "log,tail" "tail -f /var/log/syslog"
cmdlist

Резултатът от cmdlist е подредена таблица с трите колони. Ако командата или описанието са с интервали, ги слагайте в кавички, както е по-горе. Търсенето в базата с fzf е във функцията cmd, която е описана по-долу.

cmdadd е alias, който вътре дефинира функция и веднага я вика. Това е заобикаляне на ограничението, че alias не може да ползва аргументите като $1, $2. По-чисто решение е обикновена функция в .bashrc, но този вариант работи и остава в един файл с останалите.

09. Directories / Projects

Alias Накъде води
desktop, dekstop Windows Desktop. Втората форма е защита от печатна грешка
nex Папката на Nextcloud
zdrave, vlak, devops, fs Мои проекти на Desktop

Всички пътища са към моя Windows потребител (/mnt/c/Users/fedia/…). Когато си го приспособявате, сменете fedia с вашето име. Ако не искате да го повтаряте на всеки ред, задайте го веднъж и го ползвайте във всички:

WINHOME=/mnt/c/Users/ВАШЕТО_ИМЕ
alias desktop='cd $WINHOME/Desktop'
alias nex='cd $WINHOME/Nextcloud'

Единичните кавички са важни: така $WINHOME се разгъва при изпълнение, а не при зареждане.

10. SSH / Security

Alias Какво прави
sship ssh без проверка на host key, без запис в known_hosts и без предупреждения

Ползване: sship user@192.168.1.50.

⚠️ Използвайте sship само за тестови машини, които често преинсталирам и които получават нов host key при всеки път. Проверката на host key е защитата срещу подмяна на сървъра (man-in-the-middle атака), а този alias я изключва. За постоянни сървъри използвайте обикновен ssh.

11. Notifications

Alias Какво прави
alert Показва системно известие, когато дълга команда приключи

Ползва се така: sleep 30; alert. Известието е със заглавието на командата и различна иконка при успех и грешка. Това е стандартният alert от Ubuntu. Изисква notify-send (пакет libnotify-bin) и десктоп среда, така че в WSL често не работи без допълнителна настройка.


Файл 2: ~/.bashrc

След като alias-ите излязоха, .bashrc стана по-кратък и съдържа само настройки, инструменти и функции. Тук вече няма нито един alias – всички са във ~/.fedya_aliases.

# ~/.bashrc — Cleaned & Organized Version

# 1. Интерактивна проверка
case $- in
    *i*) ;;
      *) return;;
esac

# 2. История на командите (History setup)
HISTCONTROL=ignoreboth
HISTFILESIZE=1000000
HISTSIZE=1000000
export HISTTIMEFORMAT="%d-%m-%Y %T "
shopt -s histappend
export PROMPT_COMMAND='history -a'

# 3. Настройки на терминала
shopt -s checkwinsize
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# 4. Prompt (Оцветен промпт)
case "$TERM" in
    xterm-color|*-256color) color_prompt=yes;;
esac

if [ "$color_prompt" = yes ]; then
    PS1="\[\033[38;5;209m\]\u\[\033[38;5;154m\]@\[\033[38;5;46m\]\h \[\033[38;5;226m\]\w \[\033[0m\]\[\033[38;5;15m\]$ \[\033[0m\]"
else
    PS1="\[$(tput setaf 209)\]\u\[$(tput setaf 154)\]@\[$(tput setaf 46)\]\h \[$(tput setaf 226)\]\w \[$(tput sgr0)\]\[$(tput setaf 15)\]$ \[$(tput sgr0)\]"
fi
unset color_prompt force_color_prompt

case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac

# 5. Цветове за dircolors
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
fi

# 6. Външни инструменти (Keychain, Starship, NVM)
eval $(keychain --quiet --eval \
    ~/.ssh/fedia007_rsa \
    ~/.ssh/id_ed25519 \
    ~/.ssh/tablet.id_rsa
)

eval "$(starship init bash)"

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

# 7. Автодопълване (Bash completion)
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi


# ==========================================
# ПОТРЕБИТЕЛСКИ ФУНКЦИИ (FUNCTIONS)
# ==========================================

# Proxmox Backup
backup_ct() {
  local ct_id="$1"
  local server="prox"
  local remote_path="/backup512/dump"
  local local_path="/mnt/d/backup_proxmox"

  if [ -z "$ct_id" ]; then
    echo "❗ Употреба: backup_ct <CT_ID>"
    return 1
  fi

  echo "🔍 Търсене на най-нов архив за контейнер $ct_id..."
  local latest=$(ssh $server "ls -t $remote_path/vzdump-lxc-${ct_id}-*.tar.zst 2>/dev/null | head -1")

  if [ -z "$latest" ]; then
    echo "❌ Архив не е намерен за контейнер $ct_id"
    return 1
  fi

  local filename=$(basename "$latest")
  echo "📦 Копиране на $filename"

  if rsync -avz --progress $server:"$latest" "$local_path/"; then
    echo "✅ Успешно копиране завършено!"
  else
    echo "❌ Грешка при копирането"
    return 1
  fi
}

# Система за управление на Linux команди (fzf)
cmd() {
    local selected=$(cat ~/.commands.txt | grep -v "^#" | grep -v "^$" | \
        fzf --height=80% \
            --layout=reverse \
            --border=rounded \
            --margin=1 \
            --padding=1 \
            --info=inline \
            --prompt="🔍 Търси команда: " \
            --pointer="▶" \
            --marker="✓" \
            --color="fg:#d0d0d0,bg:#1e1e1e,hl:#5f87af" \
            --color="fg+:#ffffff,bg+:#3a3a3a,hl+:#5fd7ff" \
            --color="info:#af87ff,prompt:#5fd7ff,pointer:#ff5f5f" \
            --color="marker:#87ff00,spinner:#ff87d7,header:#87afaf" \
            --preview='echo -e "\033[1;36mОписание:\033[0m" && echo {} | cut -d"|" -f1 | sed "s/^[ \t]*//" && echo -e "\n\033[1;33mТагове:\033[0m" && echo {} | cut -d"|" -f3 | sed "s/^[ \t]*//" && echo -e "\n\033[1;32mКоманда:\033[0m" && echo {} | sed "s/.*|| //"' \
            --preview-window=right:40%:wrap)

    if [ -n "$selected" ]; then
        local command=$(echo "$selected" | sed 's/.*|| //')

        if grep -qi microsoft /proc/version; then
            echo "$command" | clip.exe
        else
            echo "$command" | xclip -sel clip
        fi

        echo -e "\033[1;32m✓ Копирано:\033[0m $command"
    fi
}

# FZF история (Ctrl+H)
find_history_fzf() {
  local selected_command
  selected_command=$(history | fzf --tac --height 40% \
    --layout=reverse --border --ansi \
    --header="📜 История (Ctrl+H) | Най-новите са отгоре" \
    --tiebreak=index \
    --color="header:italic,fg:110,border:93" \
    --preview='echo {}' --preview-window=bottom:3:wrap)

  if [ -n "$selected_command" ]; then
    local clean_cmd=$(echo "$selected_command" | sed -E 's/^[ ]*[0-9]+[ ]+[0-9]{2}-[0-9]{2}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}[ ]+//')
    READLINE_LINE="$clean_cmd"
    READLINE_POINT=${#READLINE_LINE}
  fi
}
bind -x '"\C-h": find_history_fzf'

# Бекъп на директория
bekap() {
    local ext="${1:-}"
    local dest="_backup_$(date +%Y%m%d_%H%M%S)"
    local pattern

    if [ -z "$ext" ] || [ "$ext" = "*" ]; then
        pattern="*"
    else
        pattern="*.${ext}"
    fi

    mkdir -p "$dest"

    find . \
        -type f \
        -name "$pattern" \
        -not -path "./_backup_*/*" \
        -not -path "./.git/*" \
        -exec cp --parents {} "$dest/" \;

    local count
    count=$(find "$dest" -type f | wc -l)
    echo "Бекъп готов: $dest ($count файла, pattern: $pattern)"
}

# Замяна на файл от клипборда
zameni() {
  if [ -z "$1" ]; then
    echo "Грешка: Трябва да зададеш име на файла!" >&2
    echo "Употреба: zameni <име_на_файла>" >&2
    return 1
  fi

  if ! command -v xclip >/dev/null 2>&1; then
    echo "Грешка: Пакетът 'xclip' не е инсталиран (sudo apt install xclip)." >&2
    return 1
  fi

  local content
  content=$(xclip -selection clipboard -o 2>/dev/null)

  if [ -z "$content" ]; then
    echo "Грешка: Клипбордът е празен! Файлът '$1' НЕ е променен." >&2
    return 1
  fi

  printf '%s\n' "$content" > "$1" && echo "Файлът '$1' е успешно презаписан."
}

# Git quick push
gp() {
    if [ -n "$(git status --porcelain)" ]; then
        git add -A
        local files_count
        files_count=$(git diff --cached --name-only | wc -l | tr -d ' ')

        if [ -n "$1" ]; then
            git commit -m "Perf: $1 ($files_count file(s))"
        else
            local timestamp
            timestamp=$(date +"%Y-%m-%d %H:%M")
            git commit -m "Auto-commit: $files_count file(s) at $timestamp"
        fi

        git push origin main
    else
        echo "Няма промени за комит."
    fi
}


# ==========================================
# ЗАРЕЖДАНЕ НА ЛИЧНИТЕ ALIASES
# ==========================================
if [ -f ~/.fedya_aliases ]; then
    . ~/.fedya_aliases
fi

Настройки (секции 1–5)

1. Интерактивна проверка. Ако Bash не е интерактивен (например при scp или скрипт), файлът спира тук. Така неинтерактивните сесии не зареждат промпт, цветове и не печатат нищо излишно.

2. История на командите.

Настройка Ефект
HISTCONTROL=ignoreboth Не записва команди, започващи с интервал, и дублирани подред команди
HISTSIZE / HISTFILESIZE = 1000000 Пази до милион команди в паметта и във файла
HISTTIMEFORMAT="%d-%m-%Y %T " history показва дата и час на всяка команда
shopt -s histappend Файлът с историята се допълва, а не се презаписва при излизане
PROMPT_COMMAND='history -a' Записва всяка команда във файла веднага след изпълнението ѝ

Последната настройка означава, че историята не се губи, ако терминалът се затвори неочаквано, и че нови терминали виждат командите от вече отворените. Отворените терминали не четат файла автоматично; за да получат чуждите команди, трябва да изпълните history -n.

3. Настройки на терминала. checkwinsize обновява размера на прозореца след всяка команда, а lesspipe позволява на less да показва и файлове, които не са чист текст (архиви, PDF). Блокът с debian_chroot е от стандартния Ubuntu .bashrc и показва в промпта, ако работите в chroot.

4. Промпт. PS1 е нарисуван с 256-цветови кодове: потребителят в оранжево, @ в светлозелено, хостът в зелено, пътят в жълто. Вторият блок case слага user@host: папка в заглавието на прозореца. Тъй като в секция 6 зареждам Starship, който поема промпта, този блок реално остава като резервен вариант, ако Starship не е наличен.

5. Цветове за ls. Зареждат се от ~/.dircolors, ако файлът съществува, иначе се ползват стандартните.

Външни инструменти (секция 6)

  • keychain пуска ssh-agent (ако още не работи) и зарежда трите ми SSH ключа, като споделя агента между всички терминали. Паролата на ключа се пита един път, при първия терминал. В WSL агентът умира при wsl --shutdown, включително когато го извиква функцията wslbackup от предишната ми статия, така че паролата ще трябва да се въведе отново.
  • Starship е промптът, който показва git клон, версии на инструменти и статус на последната команда.
  • NVM управлява версиите на Node.js. Обратната наклонена черта в \. е част от стандартния сниппет на NVM и предпазва от евентуален alias на ..
  • Bash completion (секция 7) е автодопълването с Tab.

Ако копирате моя .bashrc, а нямате тези инструменти, при всяко отваряне на терминал ще виждате грешки. Инсталирайте ги или предпазете реда с проверка, например за Starship:

command -v starship >/dev/null && eval "$(starship init bash)"

Същият подход върши работа и за keychain. Ако някой от ключовете липсва на диска, keychain ще се оплаче при всеки терминал, затова оставете само ключовете, които наистина ползвате.

Функции и как се работят с тях

backup_ct – теглене на Proxmox бекъп

backup_ct 114       # най-новият бекъп на контейнер 114
backups_wp          # същото, чрез alias-а за контейнер 114

Функцията се свързва по SSH към хоста prox, търси в /backup512/dump най-новия файл vzdump-lxc-<ID>-*.tar.zst (подрежда ги по време на промяна с ls -t) и го копира с rsync в /mnt/d/backup_proxmox. Примерен изход:

🔍 Търсене на най-нов архив за контейнер 114...
📦 Копиране на vzdump-lxc-114-2026_09_20-03_00_02.tar.zst
...
✅ Успешно копиране завършено!

Изисква Host prox в ~/.ssh/config (стойностите са примерни):

Host prox
    HostName 192.168.1.10
    User root
    IdentityFile ~/.ssh/id_ed25519

и rsync на двете машини. Без ID функцията показва „Употреба“ и спира.

cmd – търсене в базата с команди

cmd

Отваря fzf върху ~/.commands.txt (форматът е описан в секция 08 на alias-ите). Пишете част от описанието, тага или командата и виждате в панел вдясно описанието, таговете и самата команда. С Enter командата се копира в клипборда: в WSL през clip.exe (тогава я поставяте с Ctrl+V във Windows Terminal), а на обикновен Linux през xclip.

Не пишете || вътре в самата команда, защото cmd взема като команда всичко след последното || и ще я отреже. Ако ви трябва „или“, ползвайте ; или функция.

find_history_fzf – търсене в историята с Ctrl+H

Натискате Ctrl+H, пишете част от команда и с Enter избраната команда се появява на командния ред. Не се изпълнява веднага, така че можете да я поправите. Най-новите команди са най-отгоре.

Как работи: bind -x свързва клавишната комбинация с функцията, а функцията записва избраното в READLINE_LINE. Регулярният израз в sed маха номера и датата от началото на реда, като разчита на формата на HISTTIMEFORMAT. Ако смените HISTTIMEFORMAT, поправете и sed-а.

В някои терминали клавишът Backspace изпраща същия код като Ctrl+H, и тогава триенето спира да работи. При мен Windows Terminal ги различава. Ако при вас е проблем, изберете друга свободна комбинация (bind -P показва заетите).

bekap – бърз бекъп на файлове в текущата папка

bekap          # копира всички файлове
bekap md       # само *.md
bekap sh       # само *.sh

Създава папка _backup_ГГГГММДД_ЧЧММСС в текущата директория и копира файловете в нея със запазена структура на подпапките (cp --parents). Пропуска .git и предишните бекъпи (_backup_*). Разширението се подава без точка. Накрая показва колко файла са копирани.

В Node проекти bekap копира и node_modules, което може да е огромно. Добавете още един ред към find, до другите -not -path:

        -not -path "./node_modules/*" \

zameni – замяна на файл със съдържанието на клипборда

zameni script.sh

Презаписва script.sh с това, което е в клипборда. Ползвам я, когато копирам нова версия на файл от чата или от браузъра. Защитите са две: не работи без име на файл и не пипа файла, ако клипбордът е празен. Презаписва без питане и без копие, затова на важни файлове първо пуснете bekap. Изисква xclip (sudo apt install xclip).

gp – git commit и push с една команда

gp                    # автоматично съобщение
gp "поправка на менюто"

Ако има промени, gp изпълнява git add -A, прави commit и праща към origin main. Съобщението е:

  • без аргумент: Auto-commit: N file(s) at 2026-09-21 14:30
  • с аргумент: Perf: поправка на менюто (N file(s))

Ако няма промени, пише „Няма промени за комит“. Две неща, които трябва да знаете: git add -A добавя всичко, включително нови файлове, затова проверявайте .gitignore; и gp винаги праща към main, независимо на кой клон сте.


Как да намеря забравен alias или функция

С времето се събират десетки alias-и и функции и е нормално да забравите как се казва някой от тях. Затова имам няколко начина за търсене, според това какво си спомням:

Знам… Ползвам
част от името или от командата aliases дума
само какво прави fa или grep -i -C1 "дума" ~/.bashrc ~/.fedya_aliases
точното име, но не помня какво прави type име или declare -f име

aliases дума – бързо търсене по име или команда

aliases docker       # alias-и с "docker" в името или в командата
aliases StrictHost   # намира sship, защото търси и в самата команда
aliases zameni       # zameni е функция, затова излиза с "(функция)"

Ако няма съвпадение, пише „Няма съвпадения за: …“. Функциите се търсят само по име, не по съдържание. Това е важно, защото zameni е функция, а не alias, и обикновеното alias | grep zameni няма да я намери.

fa – интерактивно търсене с описание

fa

fa отваря fzf със списък на всички alias-и [A] и функции [F]. Пишете каквото да е и списъкът се стеснява на живо. Всеки ред има описание, затова се търси и по смисъл: „клипборд“ намира zameni, „бекъп“ намира bekap. Примерен изход:

[A] dlf          docker logs -f   # Следене на container logs
[A] sship        ssh -o StrictHostKeyChecking=no ...   # SSH без проверка на host key
[F] zameni       # Замяна на файл от клипборда
[F] bekap        # Бекъп на директория
[F] gp           # Git quick push

Enter затваря списъка и показва цялата дефиниция на избрания ред: за функция – кода ѝ, за alias – в какво се разгъва. Esc затваря без нищо да прави.

Как работи (функцията е във ~/.fedya_aliases, секция 01):

  1. Описанията се четат с awk от двата файла. Описание е първият коментар над дефиницията, затова е добре да пишете кратки, ясни коментари.
  2. Списъкът идва от живата сесия: compgen -A alias за alias-ите и compgen -A function за функциите. Така се виждат и alias-и, дефинирани в друг файл.
  3. Филтърът grep -Ev '^(_|nvm|starship)' скрива вътрешните функции на bash-completion, NVM и Starship. Без него те потопяват вашите.
  4. След Enter type име показва дефиницията. head -1 гарантира, че при няколко избрани реда се ползва първият.

Кратки бележки:

  • Описанието идва от коментара непосредствено над дефиницията, например:

    # Замяна на файл от клипборда      <- това става описанието на zameni
    zameni() {

    Ако искате повече подробности, допишете коментара.

  • Всички аргументи на fa се подават на fzf, например fa --height 60%.

  • Ако в списъка се появят други излишни функции, добавете имената им във филтъра. Например quote, dequote и quote_readline от bash-completion се скриват с (_|nvm|starship|quote|dequote).

  • Пътищата ~/.bashrc и ~/.fedya_aliases са в края на awk реда. Ако файловете ви са другаде, сменете ги.

  • fzf търси приблизително, така че къса дума може да покаже и неочаквани редове. Изисква инсталиран fzf.

Търсене по смисъл без fzf

grep -i -C1 "бекъп" ~/.fedya_aliases ~/.bashrc

-C1 показва по един ред отгоре и отдолу, така че виждате коментара до самата дефиниция. Работи навсякъде, дори без fzf.


Как да приложите всичко при себе си

  1. Направете копие на сегашния .bashrc, за да имате път назад:

    cp ~/.bashrc ~/.bashrc.bak
  2. Създайте ~/.fedya_aliases, поставете кода от тази статия и сменете личните пътища (потребител, проекти, сървъри):

    nano ~/.fedya_aliases
  3. Добавете блока за зареждане в края на .bashrc (командата е в началото на статията).

  4. Инсталирайте зависимостите, които ползвате:

    sudo apt install fzf keychain xclip rsync python3-pygments

    Starship и NVM се инсталират по инструкциите на самите проекти.

  5. Проверете синтаксиса, преди да отворите нов терминал:

    bash -n ~/.bashrc && bash -n ~/.fedya_aliases && echo "OK"
  6. Отворете нов терминал и пуснете aliases, за да видите, че всичко е заредено.

Ако нещо се счупи, bash --norc стартира чист Bash без вашите настройки, откъдето можете да поправите файла или да върнете копието:

cp ~/.bashrc.bak ~/.bashrc

Как да пазя двата файла

Двата файла са малки и лични, затова ги пазя на едно място и правя символични връзки към тях. Например в Git хранилище dotfiles (частно, тъй като файловете съдържат имена на сървъри и пътища):

ln -s ~/dotfiles/.fedya_aliases ~/.fedya_aliases
ln -s ~/dotfiles/.bashrc ~/.bashrc

Така при нова машина или нова WSL дистрибуция е достатъчно да клонирам хранилището и да пусна двете команди.


Alias или функция?

Когато добавяте нова команда, изборът е прост:

Ползвайте Когато
alias (във ~/.fedya_aliases) Командата е просто по-кратко име за друга команда, евентуално с опции. Аргументите се добавят накрая, например dlf име
функция (в ~/.bashrc) Ви трябват аргументи някъде по средата, проверки, условия или няколко стъпки. Примери: backup_ct, bekap, gp

Полезни команди при работа с alias-и: alias име (показва как е дефиниран), type име (показва дали е alias, функция или програма), unalias име (премахва го за сесията). Ако сте забравили как се казва, ползвайте fa.


Заключение

Разделянето на един файл на два ми даде най-вече спокойствие: настройките и функциите са на едно място, alias-ите на друго, и всяко се променя, без да се плаша, че ще счупя другото. Единственото, което свързва двата файла, е блокът от четири реда в края на .bashrc.

Не е нужно да вземате всичко. Прегледайте секциите, вземете само alias-ите, които ще ползвате, и си добавете свои. Най-полезните за мен се оказаха fa за търсене на забравен alias или функция, cmd с базата с команди и Ctrl+H за търсене в историята.