Продолжаю цикл «Linux под капотом». В этот раз разберём Bash. Это как раз тот случай, когда инструментом пользуются почти каждый день, но внутри у него для многих всё выглядит как чёрный ящик. Открыл терминал, написал команду, что-то отработало. А почему именно так, уже вопрос со звёздочкой.
Если по-честному, Bash: это не просто «строка для ввода команд». Это командная оболочка. Она читает то, что вы написали, разбирает текст, подставляет переменные, ищет нужную программу, настраивает ввод и вывод, запускает процесс и возвращает код завершения. Иногда ещё и портит настроение, если забыть кавычки. Но тут уже виноват не Bash, а жизнь.
Ниже разберу, что происходит после нажатия Enter, как работают переменные, кавычки, пайпы и почему одни команды запускаются как файлы, а другие живут прямо внутри оболочки.
Что такое Bash на практике
Bash: это shell, то есть командный интерпретатор. Он получает строку текста и решает, что с ней делать. В Linux есть и другие оболочки, например sh, dash, zsh, fish. Но именно Bash много лет остаётся базовым и привычным вариантом для серверов, VPS, учёбы и скриптов.
Когда вы входите в систему по SSH или открываете терминал, у вас запускается shell. Он показывает приглашение командной строки и ждёт ввод. Дальше всё выглядит просто, но внутри идёт вполне конкретная последовательность шагов.
Что происходит после Enter
Допустим, вы вводите простую команду:
ls -la /var/www
Bash не бросается сразу выполнять её как есть. Сначала он разбирает строку. Отделяет имя команды от аргументов. Смотрит, нет ли переменных, подстановок, кавычек, перенаправлений и спецсимволов. Потом решает, что такое ls. Это встроенная команда, алиас, функция или внешний исполняемый файл.
Если это внешний файл, Bash ищет его в каталогах из переменной PATH. Обычно это что-то вроде /usr/local/bin, /usr/bin, /bin и так далее. Проверить можно так:
echo "$PATH" type ls which ls command -v ls
На практике полезнее всего type и command -v. Команда which часто стоит в системе, но она не всегда показывает полную картину, особенно если у вас алиасы или shell-функции.
Встроенные команды и внешние программы
Вот здесь уже начинается интересное. Не каждая команда в терминале: это отдельный файл на диске. Например, cd не может быть обычной внешней программой в привычном смысле. Если бы она запускалась в дочернем процессе, каталог менялся бы только у этого процесса, а не у вашей текущей сессии. Поэтому cd живёт внутри Bash.
Проверить разницу можно так:
type cd type echo type pwd type grep
Обычно cd, echo, pwd, test, export и ещё ряд команд встроены в оболочку. А вот grep, ls, cat, find чаще всего: это отдельные бинарники.
Как Bash работает с переменными
Обычные переменные в Bash создаются очень просто:
name="Alexey" echo "$name"
Пробелы вокруг знака = ставить нельзя. Это частая ошибка новичков. Bash в таком случае подумает, что вы пытаетесь запустить команду с именем name.
Есть важная разница между обычной shell-переменной и переменной окружения. Пока вы просто написали name="Alexey", она видна только внутри текущего shell. Если хотите передать её дочерним процессам, нужен export:
name="Alexey" export name env | grep '^name='
Именно так приложения получают PATH, HOME, USER, LANG и другие переменные окружения.
Почему кавычки в Bash: это не мелочь
Если в Bash что-то ломается странно и неприятно, очень часто рядом лежат забытые кавычки. Оболочка умеет делать подстановки и разделять слова. Без кавычек она может неожиданно разбить строку по пробелам или раскрыть спецсимволы.
Сравните:
file="/var/www/my site/index.html" cat $file cat "$file"
В первом случае Bash разобьёт значение переменной на несколько частей, потому что там есть пробел. Во втором передаст путь как один аргумент, и всё будет хорошо.
Базовое правило простое. Если работаете со строками, путями, именами файлов и результатами подстановки, почти всегда используйте двойные кавычки. Одинарные нужны, когда надо передать текст вообще без интерпретации. Например, без раскрытия переменных.
name="Alexey" echo "$name" echo '$name'
В первой строке будет подставлено значение переменной. Во второй вы увидите буквальный текст $name.
Подстановка команд и арифметика
Bash умеет подставлять результат другой команды прямо в строку. Старый вариант через обратные кавычки ещё встречается, но по-хорошему лучше использовать форму $(...). Она читается проще и легче вкладывается одна в другую.
today=$(date +%F) echo "$today" files_count=$(find /etc -maxdepth 1 | wc -l) echo "$files_count"
Для простых вычислений есть арифметическая подстановка:
a=10 b=5 sum=$((a + b)) echo "$sum"
Для целых чисел этого хватает с головой. Если нужна математика посерьёзнее, чаще уже зовут bc, Python или что-то ещё по задаче.
stdin, stdout и stderr без магии
Каждый процесс в Linux обычно работает как минимум с тремя потоками. Это стандартный ввод, стандартный вывод и стандартный поток ошибок. Их файловые дескрипторы: 0, 1 и 2.
Когда вы пишете команду в терминале, она читает данные из stdin и пишет результат в stdout. Ошибки идут в stderr. На глаз всё это выглядит одинаково, но для shell разница принципиальная.
Простой пример перенаправлений:
ls /etc > ok.txt ls /no-such-dir > ok.txt 2> error.txt ls /no-such-dir > all.txt 2>&1
Первая команда пишет обычный вывод в файл. Вторая отдельно сохраняет ошибки. Третья отправляет и обычный вывод, и ошибки в один файл. Вот тут Bash уже перестаёт быть просто «окном в терминал» и становится диспетчером потоков данных.
Как работают пайпы
Пайп через символ | берёт stdout одной команды и передаёт его в stdin следующей. Это одна из самых сильных идей Unix. Каждая программа делает маленькую задачу, а Bash позволяет собрать их в цепочку.
ps aux | grep nginx journalctl -u nginx | tail -n 50 df -h | grep '/$'
На практике это работает очень удобно. Одна команда собирает данные, вторая фильтрует, третья показывает нужный кусок. Именно из таких цепочек и собирается большая часть нормальной админской рутины.
Но тут есть нюанс. По умолчанию пайп смотрит на код завершения последней команды в цепочке. Из-за этого можно не заметить ошибку в начале конвейера. Для скриптов часто включают такую настройку:
set -o pipefail
Тогда shell увидит сбой внутри цепочки, а не только в её хвосте. Очень полезная штука, особенно когда автоматизация уже крутится без вашего присмотра.
Код завершения и почему он важнее красивого вывода
После выполнения команды Bash сохраняет код завершения в переменную $?. Ноль означает успех. Любое другое число обычно означает ошибку или особое состояние.
mkdir /root/test-dir echo $? mkdir /root/test-dir echo $?
Если команда отработала успешно, увидите 0. Если нет, будет другой код. Для скриптов это вообще основа логики. Не надо ориентироваться только на текст в терминале. Bash и другие программы умеют разговаривать кодами, и это намного надёжнее.
Скрипт Bash: это просто текстовый файл, но есть детали
Минимальный Bash-скрипт выглядит так:
#!/usr/bin/env bash echo "Привет из Bash"
Первая строка называется shebang. Она говорит системе, каким интерпретатором запускать файл. Потом файлу нужны права на выполнение:
chmod +x script.sh ./script.sh
Если запустить скрипт как bash script.sh, права на выполнение не обязательны, потому что вы явно вызываете интерпретатор. Но для нормальной работы и переносимости лучше делать всё по-человечески.
Для скриптов, которые должны падать аккуратно и не скрывать ошибки, часто ставят в начало такой набор:
#!/usr/bin/env bash set -euo pipefail
-e завершает скрипт при ошибке команды, -u ругается на несуществующие переменные, pipefail ловит сбои в пайпах. Это не волшебная таблетка, но от многих тихих поломок спасает отлично.
Где Bash обычно ломает ожидания
Чаще всего проблемы приходят из трёх мест. Первое: забытые кавычки. Второе: путаница между shell-переменными и окружением. Третье: уверенность, что команда в пайпе точно отработала, хотя там уже всё горит, просто красиво и без дыма.
Ещё одна частая история: запуск скрипта через sh script.sh, хотя внутри bash-конструкции. Например, массивы, [[ ... ]], source и прочие вещи, которые в чистом sh работают иначе или не работают совсем. Потом начинается любимая админская игра «почему у меня на одном сервере всё хорошо, а на другом цирк».
Проверять такие вещи удобно через:
bash -n script.sh bash -x script.sh shellcheck script.sh
bash -n проверит синтаксис, bash -x покажет пошаговое выполнение, а shellcheck вообще давно пора считать обязательным другом любого, кто пишет shell-скрипты чаще двух раз в месяц.
Что полезно запомнить
Bash: это не просто поле для ввода команд. Он разбирает текст, подставляет значения, управляет потоками, ищет программы и следит за кодами завершения. Когда это понимаешь, терминал перестаёт быть магией и становится обычным инструментом, просто довольно умным.
Для повседневной работы достаточно запомнить несколько вещей. Всегда проверяйте, что именно запускается через type. Почти всегда ставьте кавычки вокруг переменных. Следите за кодом завершения. Для скриптов используйте нормальный shebang и не ленитесь проверять их через bash -n и shellcheck.
В следующей части цикла можно пойти ещё глубже и разобрать процессы, fork, exec, PID, дочерние процессы и то, как Bash на самом деле запускает команды внутри системы. Там уже Linux показывает характер по полной.