Продолжаю цикл «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 functions.
Встроенные команды и внешние программы
Вот здесь уже начинается интересное. Не каждая команда в терминале это отдельный файл на диске. Например, 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 показывает характер по полной.