Learning Platform
Глоссарий Troubleshooting
Урок 04.04 · 20 мин
Начальный
BashbashrcAliasesHistoryConfiguration

.bashrc и история команд

Каждый раз когда вы открываете терминал, bash читает файл конфигурации и подгружает оттуда настройки: aliases, переменные окружения, функции, history-настройки. Это важный механизм, потому что без понимания вы получите проблемы вида «alias работает в одном терминале, а в другом — нет», «PATH прописал, но не работает», «история теряется между сессиями».

В этом уроке разберём, какие файлы читает bash, чем .bashrc отличается от .bash_profile, как настроить историю, и какие есть хитрые shortcut-ы для работы с историей (Ctrl-R, !!, !$, и так далее).


Login shell vs Interactive shell

Bash может запускаться в разных режимах, и это влияет на то, какие конфиги он читает. Два главных разделения:

Два измерения запуска bash
Login shellЗапущен через логин в систему: ssh, console TTY (Ctrl-Alt-F1), запуск с флагом --login. Это первый shell в сессии
vs
Non-login shellЗапущен как дочерний (новый таб в терминале, явный `bash` в shell, скрипт). Не первый, а вложенный
InteractiveПодключён к терминалу (stdin — это tty). Можно вводить команды руками. Обычный случай — окно терминала
vs
Non-interactivestdin не tty. Запущен из скрипта или pipe. Не выводит prompt, не печатает приветствие

Получается четыре комбинации:

  1. Login + Interactive — вы зашли по SSH или в console. Это первый bash, в нём интерактивно вводите команды.
  2. Non-login + Interactive — открыли новый таб в iTerm2 на macOS. macOS по умолчанию запускает login shell в каждом табе (исторически), на Linux — non-login.
  3. Login + Non-interactive — редко, обычно при ssh с -T или скрипт через bash --login.
  4. Non-login + Non-interactive — скрипт ./script.sh. Самый частый non-interactive случай.

Bash для каждого режима читает свой набор файлов.


Какие файлы читает bash

Какие файлы читает bash в каких режимах
Login shellПервый shell в сессии (SSH, console). Читает 'login конфиг'
потом
~/.bash_profileГлавный пользовательский login-конфиг. Если его нет, ищет ~/.bash_login, потом ~/.profile
Non-login interactiveДочерний интерактивный shell (новый таб, явный `bash`)
потом
~/.bashrcГлавный конфиг для интерактивных сессий. Здесь aliases, prompt, history-настройки. Что вы редактируете 99% времени
Non-interactiveСкрипт ./script.sh. Не читает .bashrc, не читает .bash_profile
кроме
BASH_ENVЕсли установлена переменная BASH_ENV — скрипт читает указанный файл. Редко используется

Это важный момент. Когда вы пишете bash-скрипт ./deploy.sh, он НЕ читает .bashrc и НЕ знает ваши aliases. Поэтому если в скрипте вы хотите ll — у вас ничего не получится, нужно писать ls -la явно.

Главный конфиг для interactive работы — ~/.bashrc. Туда кладут:

  • aliases (alias ll='ls -la')
  • переменные окружения (export PATH=...)
  • prompt customization (PS1=...)
  • history settings
  • автодополнение
  • инициализацию starship/p10k

Файл ~/.bash_profile обычно делает одну вещь — подгружает ~/.bashrc:

# ~/.bash_profile
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

Это нужно, потому что на login-shell .bashrc не подгружается автоматически. На macOS каждый новый таб iTerm2 — это login shell, и без этого хака .bashrc не работал бы.


~/.profile — universal POSIX

~/.profile — это POSIX-стандартный файл. Его читает не только bash, но и dash, sh, ksh. Туда обычно кладут только переменные окружения, в POSIX-синтаксисе (без bash-extensions).

Логика такая: ~/.profile — переменные для ВСЕЙ системы, ~/.bashrc — настройки только bash. Если вы используете zsh, у zsh свой аналог ~/.zshrc.

На Ubuntu обычно цепочка такая:

~/.bash_profile  # если есть, перебивает остальные
    -> ~/.bashrc
    -> ~/.profile  # только если bash_profile нет

На macOS:

~/.bash_profile  -> ~/.bashrc  (в каждом login-shell)

Чтобы не путаться, простое правило: всё кладите в ~/.bashrc. Если работаете в zsh — в ~/.zshrc. А .bash_profile пусть делает только source:

# ~/.bash_profile (универсальная заготовка)
[[ -f ~/.bashrc ]] && source ~/.bashrc

Aliases — короткие имена для команд

Alias — это бирка для команды. Когда shell видит alias в начале строки, заменяет его на полную команду.

$ alias ll='ls -la'
$ ll
total 24
drwxr-xr-x  ...

$ alias                # показать все
alias ll='ls -la'
alias la='ls -A'
alias l='ls -CF'

$ unalias ll           # удалить

Стандартный набор полезных aliases:

# В ~/.bashrc:
alias ll='ls -la --color=auto'
alias la='ls -A --color=auto'
alias l='ls -CF --color=auto'
alias ..='cd ..'
alias ...='cd ../..'
alias gs='git status'
alias gd='git diff'
alias gco='git checkout'
alias gp='git pull'
alias k='kubectl'
alias d='docker'
alias dc='docker compose'

# Безопасность: интерактивный режим для опасных команд
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
git config aliases — Git-side настройка сокращений
WARNING

Aliases работают только в interactive shell. В скриптах они НЕ работают (если не включить shopt -s expand_aliases). Поэтому в скриптах всегда пишите полную команду, а не alias. Когда копируете чужой скрипт и удивляетесь «откуда там ll?» — скорее всего, это функция, не alias.

Aliases для интерактива — отлично. Для скриптов — пишите функции:

# В ~/.bashrc или в скрипте:
mkcd() {
    mkdir -p "$1" && cd "$1"
}

$ mkcd ~/new-project
$ pwd
/home/user/new-project

Подробнее про функции в модуле 17.


Exports — переменные окружения

Переменные в bash бывают двух типов:

  1. Локальные — видны только в текущем shell.
  2. Экспортированные (env) — видны и дочерним процессам.
$ name="Alice"           # локальная
$ bash -c 'echo "$name"' # дочерний bash не видит
                          # (пустая строка)

$ export name="Alice"    # экспортирована
$ bash -c 'echo "$name"' # видит
Alice

В ~/.bashrc обычно используют export для переменных, которые должны быть видны всем программам:

# Добавить в PATH
export PATH="$HOME/.local/bin:$PATH"
export PATH="$HOME/bin:$PATH"

# Установить редактор по умолчанию
export EDITOR=vim
export VISUAL=vim

# Цвета для less, man
export LESS='-R'        # raw output (для цветов)
export LESSHISTFILE=-   # не записывать историю less

# Локаль
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

PATH — самая важная переменная. Она содержит список папок, в которых shell ищет команды. Когда вы пишете ls, shell перебирает все папки из PATH (например, /usr/local/bin, /usr/bin, /bin) и ищет файл ls в одной из них.

$ echo $PATH
/home/user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# Где именно ls?
$ which ls
/usr/bin/ls

$ type ls
ls is aliased to `ls --color=auto'   # потому что alias

type показывает, что именно найдено: alias, function, builtin (встроенная в bash) или внешний файл.


Bash history: основные настройки

Bash сохраняет вашу историю команд в файл (обычно ~/.bash_history). Это файл, который пополняется при каждом завершении сессии (или при history -a).

Настройка через переменные окружения:

# В ~/.bashrc:

# Размер истории: 10000 команд в памяти, 50000 в файле
export HISTSIZE=10000
export HISTFILESIZE=50000

# Не сохранять дубликаты подряд и команды, начинающиеся с пробела
export HISTCONTROL=ignoredups:ignorespace

# Добавлять timestamp к каждой команде
export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "

# Добавлять к файлу истории, а не перезаписывать (если несколько терминалов)
shopt -s histappend

# После каждой команды — записывать в файл (не ждать до выхода)
PROMPT_COMMAND="history -a; ${PROMPT_COMMAND}"

Что значит каждый параметр:

HISTCONTROL опции
ignoredupsНе сохранять команду, если она идентична предыдущей. Спасает от 'ls\\nls\\nls\\nls' в истории
ignorespaceКоманды, начинающиеся с пробела, не попадают в историю. Полезно для команд с паролями: ` mysql -p hardpass`
ignorebothСокращение для ignoredups:ignorespace. То же самое что они оба вместе
erasedupsСильнее: удалит ВСЕ предыдущие копии команды из истории, оставит только последнюю. Опасно: ломает 'cd dir1; cd dir2; cd dir1' (потеряете dir2)

После применения HISTTIMEFORMAT команды в history показывают время:

$ history | tail -5
  996  2026-05-13 14:32:01 cd ~/projects
  997  2026-05-13 14:32:05 git status
  998  2026-05-13 14:32:10 git diff
  999  2026-05-13 14:32:30 git commit -m 'fix'
 1000  2026-05-13 14:33:00 git push

Поиск в истории: Ctrl-R

Самый полезный shortcut в bash — Ctrl-R (reverse incremental search). Нажимаете Ctrl-R, начинаете печатать — bash ищет в истории команду, содержащую это слово.

$ <Ctrl-R>
(reverse-i-search)`': 
# Начинаете печатать "git push"
(reverse-i-search)`git pu': git push origin main
# Enter — выполнить
# Esc — оставить в строке без выполнения
# Ctrl-R ещё раз — следующее совпадение в истории

Это экономит часы. Без Ctrl-R вы стрелкой вверх 50 раз пролистываете историю — с Ctrl-R находите за 3 символа.


История shortcut: !!, !$, !^

Bash имеет «event designators» — короткие способы ссылаться на команды из истории:

Bash history shortcuts
!!Полностью предыдущая команда. Полезно с sudo: `sudo !!` запустит последнюю команду с sudo
!$Последний аргумент предыдущей команды. После `vim long/path/to/file.txt` можно `cd !$` — переходит в long/path/to/file.txt
!^Первый аргумент предыдущей команды. После `cp source.txt dest.txt` `!^` = source.txt
!NN-я команда из истории. `!1000` запустит команду номер 1000 (по выводу history)
!-NN-я команда с конца. `!-3` = третья с конца. Реже используется чем !!
!strПоследняя команда, начинающаяся с str. `!git` запустит последнюю git-команду
^old^newЗапустить предыдущую команду, заменив 'old' на 'new'. После `cd /et/foo` -> `^et^etc` запустит `cd /etc/foo`
!!:gs/old/newПолная замена: запустить предыдущую команду, заменив все 'old' на 'new'. Чуть сложнее ^^, но мощнее

Примеры использования:

$ apt update
E: Could not open lock file /var/lib/apt/lists/lock - permission denied (13)
$ sudo !!
sudo apt update
[sudo] password for user:
...

$ vim /etc/ssh/sshd_config
$ ls -la !$
ls -la /etc/ssh/sshd_config
-rw-r--r-- 1 root root 3253 May 13 14:00 /etc/ssh/sshd_config

# С опечаткой:
$ cd /et/passwd
bash: cd: /et/passwd: No such file or directory
$ ^et^etc
cd /etc/passwd
bash: cd: /etc/passwd: Not a directory  # ну, тоже ошибка, но понятная

Это всё readline-фичи, работают в bash и zsh. В fish — свой синтаксис.


fzf: интерактивный fuzzy-search для истории

Альтернатива Ctrl-R — fzf (fuzzy finder). После установки:

$ sudo apt install -y fzf

# В ~/.bashrc:
$ echo 'source /usr/share/doc/fzf/examples/key-bindings.bash' >> ~/.bashrc
$ source ~/.bashrc

Теперь Ctrl-R открывает интерактивный fuzzy search:

$ <Ctrl-R>
> git pu_                                  ← печатаете
  > git push origin main
    git push --tags
    git pull origin main
    git pull --rebase origin main

Стрелочками выбираете, Enter — запускаете. Гораздо удобнее обычного Ctrl-R, особенно когда история большая (10000+ команд).

fzf — это супер-инструмент для DE, мы подробнее разберём его в модуле 19.


Попробуй сам

Откройте свой ~/.bashrc и посмотрите, что там:

$ cat ~/.bashrc
# Длинный файл с настройками. Найдите HISTSIZE, HISTCONTROL и aliases

Добавьте полезные настройки:

# В конец ~/.bashrc:

# Расширенная история
export HISTSIZE=10000
export HISTFILESIZE=50000
export HISTCONTROL=ignoredups:ignorespace
export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
shopt -s histappend

# Полезные aliases
alias ll='ls -la --color=auto'
alias ..='cd ..'
alias ...='cd ../..'
alias gs='git status'

# PATH для локальных скриптов
export PATH="$HOME/.local/bin:$PATH"

Перезагрузите конфиг:

$ source ~/.bashrc
# или короче:
$ . ~/.bashrc

# Проверьте, что заработало:
$ alias
$ echo $HISTSIZE
10000

Попробуйте history shortcuts:

$ pwd
/home/user
$ echo "!$"
echo "/home/user"
/home/user

$ ls
$ sudo !!
sudo ls
...

$ <Ctrl-R>git<Enter>
# найдёт последнюю git-команду

Проверка знанийKnowledge check
Junior добавил alias `alias deploy='ssh prod-server systemctl restart api'` в свой ~/.bashrc. Локально работает. Но когда добавляет в DAG Airflow BashOperator с command='deploy', — падает с 'deploy: command not found'. Почему?
ОтветAnswer
Airflow BashOperator запускает команды в non-interactive shell. А aliases в bash работают ТОЛЬКО в interactive shell. Конкретно: BashOperator запускает что-то типа `bash -c 'deploy'`, и этот bash не читает ~/.bashrc (читается только в interactive). Даже если бы читал — alias не работает в non-interactive по умолчанию (нужно `shopt -s expand_aliases`). Правильное решение для DAG: использовать полную команду напрямую `ssh prod-server systemctl restart api`, или вынести в shell-функцию, или (лучше всего) вынести в полноценный bash-скрипт `/usr/local/bin/deploy.sh` и вызвать его: `command='/usr/local/bin/deploy.sh'`. Скрипт работает везде, alias — только в одном специфическом контексте.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. В чём разница между ~/.bashrc и ~/.bash_profile?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4