Про оболочку
Оболочка — это не просто ещё одна программа, позволяющая запускать другие программы с клавиатуры. Она, кроме всего прочего, предоставляет среду — окружение, в котором всё происходит.
В рамках созданного окружения оболочка работает с переменными. Мы уже неявно использовали несколько переменных в предыдущей главе, но на самом деле их намного больше.
Окружение доступно не только оболочке, но и любой программе, запускаемой через неё. По сути, окружение — это массив строк вида[имя]=[значение]. Программа на C может получить к нему доступ через функциюgetenv().
Переменные
При старте оболочка инициализирует множество переменных. Просто запустите терминал и введите команду printenv или env. Если вы хотите распечатать какую‑то конкретную переменную, укажите её имя аргументом:
$ printenv HOME
Вы можете создавать свои переменные для разных нужд. Переменные принято писать ПРОПИСНЫМИ буквами, хотя это не обязательно.
Создать переменную несложно: [имя]=[значение]. Между именем и значением не должно быть пробелов:
$ MY_VAR=42
В отличие от многих языков программирования, в bash переменные не имеют типов и по умолчанию рассматриваются как строки. В зависимости от контекста bash может трактовать значение как число. Если в значении есть пробелы — используйте кавычки:
$ TEXT_1="Please lower the entropy"
$ TEXT_2='Demon sorts hot air'
Если требуется использовать кавычку внутри строки, экранируйте её: \".
$ SOME_TEXT="Bash is a \"programming\" language"
Чтобы получить доступ к значению переменной, её нужно «разыменовать» с помощью знака $. Можно писать напрямую или через фигурные скобки {} — второй вариант надёжнее в сложных случаях:
$ echo $SOME_TEXT
Bash is a "programming" language
$ echo ${MY_VAR}
5
Создадим ещё одну переменную:
$ MY_VAR=210/5
$ echo $MY_VAR
210/5
Теперь то же самое через declare:
$ declare -i MY_VAR=210/5
$ echo $MY_VAR
42
Все создаваемые переменные видны только в текущей сессии. Иными словами, после перезапуска терминала они исчезнут. Эти переменные также не будут доступны дочерним процессам, если их не экспортировать (ниже).
Удалить переменную можно командой unset (если переменной нет, echo выведет пустую строку):
$ VAR=42
$ unset VAR
$ echo $VAR
Если вам важно, чтобы переменную нельзя было перезаписать, объявите её константой через readonly:
$ readonly CONSTANT=3
$ CONSTANT=5
bash: CONSTANT: readonly variable
Имейте в виду: объявив переменную readonly, вы уже не сможете поменять её значение и не сможете сделать unset. Поможет только новая сессия.
$ readonly VAR=5
$ unset VAR
bash: unset: VAR: cannot unset: readonly variable
$ exec bash
$ echo $VAR
Кроме простых переменных есть массивы. Доступ к элементам массива — по индексу, начиная с 0 (в zsh — с 1). Выход за пределы вернёт пустую строку:
# in zsh "!" needs to be escaped
$ MY_ARRAY=(1 2 "BANG!" 3)
$ echo ${MY_ARRAY[42]}
$ echo ${MY_ARRAY[2]}
BANG!
Распечатать все элементы сразу:
$ echo ${MY_ARRAY[@]}
1 2 BANG! 3
Добавить элементы:
$ MY_ARRAY+=(4 "five" 6)
$ echo ${MY_ARRAY[@]}
1 2 BANG! 3 4 five 6
Также можно убрать элемент:
$ unset 'MY_ARRAY[2]'
$ echo ${MY_ARRAY[@]}
1 2 3 4 five 6
Получить последний элемент:
$ echo ${MY_ARRAY[@]: -1}
6
Узнать размер массива:
$ echo ${#MY_ARRAY[@]}
6
Индексы массива:
$ echo ${!MY_ARRAY[@]}
0 1 3 4 5 6
Вытащить подмассив (сдвиг и длина):
$ echo ${MY_ARRAY[@]:2:5}
3 4 five 6
Так же можно создать ассоциативный массив (словарь):
$ declare -A m
$ m["favorite color"]="555 nm"
$ echo ${m["favorite color"]}
555 nm
Проверка наличия ключа:
$ [[ -v 'm["favorite color"]' ]]
$ echo $?
0
Можно сохранить в переменную вывод другой команды:
# String
$ RESULT=$(pwd)
$ echo $RESULT
/home/oliver/highway
# Array
$ RESULT=( $(ls) )
$ echo ${RESULT[@]}
chsm data game.sh
Инициализация оболочки
При запуске сессии оболочка инициализирует множество переменных. Некоторые уже встречались: PATH (пути поиска исполняемых файлов), HOME (домашний каталог), USER (имя пользователя) и т. д.
Важно понимать, откуда они берутся. Процесс зависит от оболочки (здесь — bash) и типа запуска — login‑shell или non‑login shell.
Когда система загружается или вы подключаетесь по ssh, запускается login‑shell. Последовательность инициализации:
1. Системные настройки из /etc/profile и скриптов /etc/profile.d/*.
2. Пользовательские настройки из ~/.bash_profile (или ~/.profile, или ~/.bash_login).
Символ~разворачивается в значение переменнойHOME.
Оболочка запущенная в эмуляторе терминала не требует аутентификации, как и запуск скрипта называют non‑login shell. В этом случае читается только ~/.bashrc.
По умолчанию вывод ls монохромный, но в ~/.bashrc команду часто переопределяют через алиас:
alias ls='ls --color=auto'
# Additional
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
Скорее всего, у вас установлено несколько оболочек. Список — в /etc/shells:
$ cat /etc/shells
Оболочка по умолчанию текущего пользователя хранится в переменной SHELL:
$ echo $SHELL
/bin/bash
Login‑shell пользователя указан в /etc/passwd:
$ grep $USER /etc/passwd
oliver:x:1000:1000:oliver:/home/oliver:/bin/zsh
Сменить оболочку по умолчанию можно командой chsh:
$ sudo chsh -s /bin/zsh [username]
Все создаваемые переменные — временные и действуют в рамках сессии. Часто нужно добавить директорию в PATH:
$ PATH=$PATH:$(pwd)
Чтобы изменение применялось всегда, добавьте строку в ~/.bashrc (лучше с export):
$ echo 'export PATH="$PATH:$(pwd)"' >> ~/.bashrc
Локализация
Локализация — это набор правил, по которым система обрабатывает язык, кодировку, форматирует числа и даты, а также выполняет сортировку (например, A a B или A B a). В окружении есть несколько переменных, отвечающих за то, как вы будете видеть текст:
$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
Можно настраивать каждый аспект отдельно или задать общие правила через LC_ALL. Чтобы локаль применялась всегда, пропишите переменные в файлах инициализации оболочки.
Вложенность
Если переменную нужно передать дочерним процессам, используйте export:
$ export MY_VAR
Экспортировать можно и через declare:
$ declare -x -i MY_VAR=20
Ничто не мешает запускать оболочку внутри оболочки. Допустим, ваш интерпретатор по умолчанию — bash, но нужно выполнить csh‑скрипт:
$ csh ./script.sh
Можно запустить вложенный bash:
$ bash
А внутри него — zsh:
$ zsh
В окружении есть переменная SHLVL (начальное значение — 1), в которой хранится текущая глубина вложенности:
# bash (1) -> bash (2) -> zsh (3)
$ echo $SHLVL
3
Выйти из оболочки можно командойexitили сочетаниемCtrl+D(EOF). В приведённом примере команду нужно вызвать дважды, чтобы вернуться к оболочке по умолчанию.
Зачем вообще нужна вложенность?
1) можно переключиться на другую оболочку;
2) можно войти в другую оболочку для тестов;
3) изоляция: вложенная оболочка наследует окружение родителя, но изменения внутри неё не влияют на родительскую.
Конструкции в круглых скобках ( ... ) выполняются в подоболочке (subshell); при этом SHLVL не увеличивается:
$ (cd /etc; ls -la)
Текущая директория при этом не меняется. Переменные, созданные в подоболочке, будут отброшены. Команды можно обернуть и в обратные кавычки (лучше использовать $(...)):
$ echo $(cd /etc; ls -la)
Проверить, что это была действительно вложенная оболочка, можно по PWD:
$ echo $PWD
/home/oliver/highway
$ (cd /etc && echo $PWD)
/etc
$ echo $PWD
/home/oliver/highway
Если вы запускаете bash из другого процесса (например, из CMake‑скрипта), каждая строка будет выполняться в отдельной независимой оболочке!
Команды из файла можно выполнить в текущей оболочке через source (или .):
$ cat data/commands.txt
date
uptime
$ source data/commands.txt
Sun Feb 2 16:58:35 UTC 2025
16:58:35 up 17:32, 1 user, load average: 0.00, 0.01, 0.00
Это удобно для подгрузки конфигурации окружения (переменные, функции и т. п.).
Выполнение команд
В предыдущей главе мы рассмотрели коды возврата. Можно строить несложную логику, опираясь на них, используя && и ||.
$ cd ~
$ mkdir mydir && echo "Directory was successfully created"
Directory was successfully created
$ cd /
$ mkdir mydir || echo "Directory creation has failed"
Directory creation has failed
Во втором примере у текущего пользователя нет прав на создание директорий в /, поэтому mkdir возвращает ненулевой код, и выполняется правая часть после ||.
Для проверок так же можно использовать команды плейс-холдеры true и false:
$ true && echo "It's true!"
It's true!
$ false || echo "It's false!"
It's false!
Используя разделитель, ;, можно выполнить команды последовательно в одной строке:
$ ls > count.txt; wc -l count.txt; rm count.txt
Такое свойство разделителя полезно, когда необходимо выполнить несколько команд с контекстом из другой программы, например прописав post-build команду в Eclipse.
Шаблоны
В bash можно задавать шаблоны (wildcards). Выведем все файлы, которые начинаются с определённого префикса:
$ ls data/file*.txt
data/file_1.txt data/file_3.txt data/file_5.txt data/file_7.txt
data/file_2.txt data/file_4.txt data/file_6.txt
Другой пример. Пусть имена файлов логов имеют формат log_[yy]_[mm]_[dd].txt. Нужны только записи за десятые числа декабря 2024 года, при этом ведущий ноль не используется (1 вместо 01). * не подойдёт — пустая строка тоже удовлетворит шаблону:
$ ls data/log_24_12_1*.txt
data/log_24_12_11.txt data/log_24_12_16.txt
data/log_24_12_12.txt data/log_24_12_1.txt
Можно включить ** для рекурсивного поиска по подкаталогам: shopt -s globstar, после чего:
$ ls **/*.txt
Требуется ровно один символ после 1. Используем ?:
$ ls data/log_24_12_1?.txt
data/log_24_12_11.txt data/log_24_12_12.txt data/log_24_12_16.txt
Фильтр по набору символов:
$ ls data/log_24_12_1[234].txt
data/log_24_12_12.txt
Указать диапазон:
$ ls data/log_24_12_1[2-4].txt
data/log_24_12_12.txt
Исключение значений:
$ ls data/log_24_12_1[!2-4].txt
data/log_24_12_11.txt data/log_24_12_16.txt
Brace‑expansion помогает генерировать последовательности значений:
$ echo data/file_{1,2,3}.txt
data/file_1.txt data/file_2.txt data/file_3.txt
$ echo data/file_{4..7}.txt
data/file_4.txt data/file_5.txt data/file_6.txt data/file_7.txt
$ echo {a..z}{1..9}
$ echo {z..a}{9..1}
$ echo {1..9..2}
1 3 5 7 9
Горячие клавиши
`bash` поддерживает горячие клавиши для редактирования строки:
Ctrl+A— курсор в начало строки;Ctrl+E— курсор в конец строки;Ctrl+W— удалить «слово» слева (включая пробел);Ctrl+U— очистить строку слева от курсора;Ctrl+K— удалить всё справа от курсора.
Фактически содержимое попадает в kill‑ring оболочки. Вставить удалённый фрагмент — Ctrl+Y (yank).
Ещё полезные сочетания:
Ctrl+L— очистка экрана;Ctrl+R— поиск по истории (ниже).
Сочетания, которые понимают и оболочка, и запущенные программы:
Ctrl+C— сигналSIGINT;Ctrl+\— сигналSIGQUIT;Ctrl+D—EOF(End Of File) в терминал.
История
bash записывает историю действий. Допустим, вы бродили по файловой системе с помощью cd:
$ cd highway/data
$ cd ~
Вернуться назад удобно так:
$ cd -
cd сам по себе историю не хранит; это делает оболочка, обновляя переменные PWD и OLDPWD:
$ echo "$OLDPWD -> $PWD"
/home/oliver/highway/data -> /home/oliver
$ cd -
$ echo "$OLDPWD -> $PWD"
/home/oliver -> /home/oliver/highway/data
Узнать текущую директорию можно командой pwd.
Есть переменная CDPATH — список директорий для быстрого перехода. По умолчанию она пуста, поэтому поиск идёт только в текущем каталоге. Но вы можете добавить свои пути:
$ export CDPATH=":~/highway"
$ pwd
/home/oliver
$ cd data
$ pwd
/home/oliver/highway/data
bash запоминает не только перемещения, но и команды. Допустим, вы забыли выполнить длинную команду с sudo:
$ cat /etc/shadow
cat: /etc/shadow: Permission denied
$ sudo !!
sudo cat /etc/shadow
root:*:19962:0:99999:7:::
# ...
Чтобы найти старую команду используйте Ctrl+R. Начните вводить, и bash предложит подходящий вариант. Повторное Ctrl+R листает варианты.
Файл истории:
$ echo $HISTFILE
/home/oliver/.bash_history
История сохраняется в файл при завершении сессии.
Размер истории ограничен: HISTFILESIZE — в файле, HISTSIZE — в оперативной памяти. Обычно это 1000.
Чтобы команда не попала в историю, добавьте пробел в начале строки.
Встроенная команда history упрощает работу:
# Display history
$ history
1 cat /etc/shadow
2 sudo cat /etc/shadow
# Clear history (current session)
$ history -c
# Write history to file
$ history -w
# Read history from file into session
$ history -r
Зная номер команды в истории, можно вызвать её снова:
$ !2
sudo cat /etc/shadow
[sudo] password for oliver:
root:*:19962:0:99999:7:::
Распечатать команду без выполнения:
$ !2:p
sudo cat /etc/shadow
Подставить последнюю команду по префиксу:
$ !sudo
$ sudo cat /etc/shadow
Чтобы не засорять историю, можно исключить некоторые команды:
$ export HISTIGNORE="ls:cd:pwd:exit"
Убрать дубликаты:
$ export HISTCONTROL=ignoredups
Добавить временную метку:
$ export HISTTIMEFORMAT="%F %T "
Внешний вид
В Ubuntu bash по умолчанию показывает строку вида:
<user>@<hostname>:~$
Это задаётся переменной PS1:
$ echo $PS1
\u@\h:\w\$
\u — имя пользователя, \h — имя хоста, \w — текущая директория (знак ~ — сокращение для /home/<user>).
Переменная PS2 — приглашение для продолжения ввода многострочной команды:
$ echo "This is a very \
dquote> long text"
This is a very long text
$ ping \
> google.com
PING google.com (142.250.72.14) 56(84) bytes of data.
$ echo $PS2
%_>
PS3 и PS4 используются в скриптах; о них — позже.
Ещё одна полезная переменная — PROMPT_COMMAND. С её помощью можно выводить что‑то до PS1:
$ PROMPT_COMMAND="cat /proc/loadavg"
0.00 0.00 0.12 1/330 562029
$
Можно выводить, например, имя текущей Git‑ветки (через скрипт).
Готовые решения: Oh My Bash, для zsh — Oh My Zsh.
Цвета
Нет времени объяснять, скопируйте и вставьте:
$ echo -e "\033[31mThis text is red\033[0m"
Текст станет красным. Скорее всего ваш PS1 тоже раскрашен. Проверьте:
$ echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
Для раскраски используются управляющие последовательности (escape‑codes). Они начинаются с ESC в ASCII (\e, 033 или 0x1B).
$ echo -e "\e[31mThis text is red\e[0m"
К escape‑последовательностям относятся не только цвета, но и перевод строки (\n), табуляция (\t) и звуковой сигнал (\a).
[31m — красный, [0m — сброс форматирования.
Количество доступных цветов зависит от терминала. Многие поддерживают 8/16 цветов, GNOME Terminal — 256, а некоторые — 24‑битный TrueColor.
Пример палитры 256 цветов:
for COLOR in {1..255}; do echo -en "\e[38;5;${COLOR}m\u25A0 "; done; echo;

tput и terminfo
tput позволяет управлять терминалом без «сырых» управляющих кодов. Она берёт последовательности из базы terminfo (см. man terminfo), опираясь на значение переменной окружения $TERM, которая описывает возможности текущего терминала.
$ echo ${TERM}
xterm-256color
tput читает записи terminfo и выводит нужные управляющие последовательности в stdout. Проверить, какие возможности доступны, можно так:
infocmp -1 "$TERM" | grep -E 'setaf|setab|sgr0|bold|smul|rmul|cup|civis|cnorm|el|ed'
Для работы с текстом полезны команды: setaf N — цвет текста; setab N — цвет фона (N: 0–7 для базовой палитры, 0–255 при 256-цветной поддержке); bold — жирный; smul/rmul — вкл./выкл. подчёркивания; rev — инверсия; sgr0 — сброс всех атрибутов.
echo "$(tput setaf 1)This text is red$(tput sgr0)"
Также можно управлять курсором и экраном: clear — очистка экрана; cup row col — перемещение курсора (координаты нумеруются с нуля); sc/rc — сохранить/восстановить позицию; civis/cnorm — скрыть/показать курсор; el — очистить до конца строки; ed — очистить до конца экрана.
Можно анимировать процесс загрузки, добавив прогресс-бар:
cols=$(tput cols); bar=$((cols-10))
for i in $(seq 0 100); do
fill=$((i*bar/100))
printf '\r[%-*s] %3d%%' "$bar" "$(head -c "$fill" </dev/zero | tr '\0' '#')" "$i"
sleep 0.02
done
echo
А ещё — использовать этот небольшой «фреймворк» для написания игр в консоли.

Игра из репозитория книги: вы играете за улитку «@» на шоссе и уклоняетесь от встречного трафика «Y». При столкновении теряете одну жизнь; «+» добавляет жизнь. Выход — Ctrl+C, клавиша Q или смерть.
Псевдографика
Современные терминалы могут выводить не только текст, но и псевдографику с помощью Unicode‑символов и ANSI escape‑кодов. Эмуляторы отличаются возможностями: где‑то 256 цветов, где‑то TrueColor.
Есть библиотеки для создания TUI (Terminal UI), так утилита chafa (Character Art File Adapter) конвертирует изображения (JPEG/PNG/SVG) в ANSI/Unicode‑картинки.

Арифметика
В bash есть встроенная утилита let для целочисленных вычислений (сложение, вычитание, умножение, деление, остаток %).
let [expression]
Пример:
$ let "a=5" "b=10" "c=(a*(a+b)-b/a)%10"; echo $c
3
Поддерживаются преинкремент и постинкремент (++ и --):
$ let "a=2" "b=a++"; echo $a $b
3 2
Возведение в степень — **:
$ let "a=2**10"; echo $a
1024
Доступны побитовые операции, как в Си: ~, >>, <<, |, &, ^.
Синтаксис арифметики можно использовать прямо в строках через $(( ... )):
$ echo "2 plus 2 is $((2+2))"
2 plus 2 is 4
Есть внешние утилиты — expr (строки и числа) и bc (вещественные числа). Они создают отдельный процесс при каждом вызове.
$ expr length "string"
6
$ expr substr "Hello there!" 7 5
there
$ echo "scale=3; 22 / 7" | bc
3.142
Можно использовать python как калькулятор:
$ python3 -c "import cmath; z1 = 1+2j; z2 = 3+4j; print(z1 + z2)"
(4+6j)
Или octave:
$ octave-cli --eval "z1 = 1 + 2i; z2 = 3 - 4i; disp(z1 * z2)"
11 + 2i
Мультиплексор терминала
Linux — многозадачная система, но в одном терминале одновременно наблюдать вывод сразу нескольких программ неудобно.
Для этого существуют мультиплексоры терминала. Они позволяют в одном окне держать несколько «панелей». В GNOME есть screen, но чаще используют альтернативу — tmux.
Запуск tmux без аргументов создаёт сессию. От неё можно отключиться и вернуться — процессы сохранятся и будут продолжать работать. Сессии можно давать имя:
$ tmux new -s my_session
В сессии tmux позволяет взаимодействие с эмулятором через «префикс» — Ctrl+B; далее ожижается нажатие ещё одной клавиши, которая сообщит что нужно сделать. Отключиться от сессии: Ctrl+B, потом D.
Перед выходом запустите, например, ping, отключитесь и проверьте список сессий:
$ tmux ls
my_session: 1 windows (created Fri Jan 24 15:53:47 2025)
Подключение обратно — по индексу или имени:
$ tmux a -t 1
# or
$ tmux a -t my_session
Полный список горячих клавиш — Ctrl+B, затем ?.
Закрыть панель: Ctrl+B, X. Разделить горизонтально: Ctrl+B, %. Вертикально: Ctrl+B, ". Изменять размеры — Ctrl+B + Alt + стрелки (или через командный режим: :resize-pane -L 10). Перемещаться между панелями — Ctrl+B + стрелки или по индексу: Ctrl+B, затем 0/1/2…. Между окнами можно переключаться внутри сессии: Ctrl+B, W.
Чтобы не настраивать сессию вручную, можно сделать alias с подготовленными командами:
$ alias term="tmux new-session \; \
send-keys 'mc' C-m \; \
split-window -v \; \
send-keys 'htop' C-m \; \
split-window -h \; \
send-keys 'ls' C-m \;"
$ term

