Окно с курсором – это терминал, а внутри него работает программа-оболочка bash. Когда вы вводите команду и жмете Enter, bash должен ее как-то выполнить – для этого ищет на диске одноименный файл-программу. Найдет – запустит, не найдет – напишет command not found. Проверить, где именно лежит та или иная команда, можно вот так:
which whoami
Покажет путь, обычно /usr/bin/whoami. Сперва может показаться, что у каждой команды есть свой файл на диске – но это не совсем правда. Какие-то команды действительно лежат отдельными программами, а какие-то встроены прямо в bash, и никакого отдельного файла для них нет. Чтобы понять, что есть что, есть команда type:
type whoami type echo type cd
whoami – внешний файл, как раз тот, который нашелся через which. Команда echo, в свою очередь, сосуществует в двух видах: и как файл /usr/bin/echo, и как встроенная команда bash. Когда вы пишете echo в терминале, bash берет именно встроенную – она быстрее, потому что не требует запуска отдельной программы. А вот cd – только встроенная, и по-другому быть не может. Если бы cd была отдельной программой, она меняла бы рабочую директорию самой себя, а не bash, и после ее завершения вы оставались бы там же, где и были. Поэтому смена директории физически должна происходить внутри того же процесса bash – она и происходит (позже поймете о чем тут :)
Когда bash ищет внешнюю команду, он не смотрит на весь диск – это было бы очень медленно. Он смотрит только в директориях, перечисленных в специальной переменной $PATH:
echo $PATH
Команда выведет список через двоеточие, что-то вроде /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin. Bash проходит эти директории по порядку и в первой, где найдется нужный файл, его и запускает. Если команда нигде не нашлась – значит, ее нет ни в одной из этих папок. Она либо не установлена, либо установлена куда-то нестандартно.
Окружение мы немного поняли. Дальше полезно уметь быстро понимать контекст – под каким пользователем вы сейчас работаете, на какой машине, в какой директории. Когда за день переключаешься между десятком серверов и тремя пользователями, бошка плывет уже под конец, это не будет лишним.
Подсказку дает само приглашение терминала. student@devops:~$ – это пользователь@машина:директория, а символ в конце говорит о правах: $ – если обычный пользователь, # – если root. Но приглашение может быть сконфигурировано как угодно (бывает, что у пользователя там вообще ничего, кроме $), поэтому полезно знать команды, которые отвечают на те же вопросы явно:
whoami hostname pwd
Это три привычных вопроса в трех ответах: кто я, где я в смысле машины, и где я в смысле директории. Удобно прогонять сразу после ssh на новый сервер – особенно когда есть сомнения, тот ли ssh-конфиг сработал и не оказались ли вы случайно не на той машине.
Те же значения у bash есть уже готовыми, в виде переменных окружения:
echo $USER echo $HOSTNAME echo $PWD
Результат тот же, но без запуска отдельной программы – bash хранит эти данные в памяти своего процесса и просто их печатает.
Одна тонкость на будущее: $HOSTNAME и $PWD bash выставляет сам, а $USER и $LOGNAME кладёт вход в систему (login). В минимальном окружении без полноценного входа – например, внутри контейнера – $USER бывает пустым. Тогда надёжный способ узнать пользователя – whoami (или id -un): они читают это прямо из системы и не зависят от переменных.
sudo whoami?Дальше начинаются интересные штуки. Что мы пока делали – запускали команды и смотрели глазами на их вывод. Но на сервере глазами смотреть некогда – результаты команд нужно складывать в файлы, передавать другим командам, кидать в /dev/null когда они не нужны. Чтобы это получалось, надо понимать, как Linux вообще обходится с выводом программ.
Начнем с самой простой:
echo "Привет"
echo пишет строку в так называемый стандартный вывод (stdout). По умолчанию stdout – это экран терминала, поэтому строка появляется перед вами. Но stdout – это не экран в физическом смысле. Это просто поток, куда программа пишет, и куда этот поток в итоге выльется – экран, файл, другая программа – решает bash, а не сама команда. Из этого и растет вся механика перенаправлений:
echo "первая" > log.txt
echo "вторая" >> log.txt
cat log.txt
> означает «перенаправить stdout в файл, перезаписав его». >> – то же самое, но дописать в конец. Echo при этом ничего не знает про файл и не делает с ним ничего особенного: bash подменяет stdout до запуска самой программы, и echo пишет в stdout как обычно – просто этот stdout теперь не экран, а файл.
И тут важный момент: у каждой программы потоков на самом деле три, а не один.
| номер | имя | что это |
|---|---|---|
| 0 | stdin | вход – то, что команда читает |
| 1 | stdout | обычный вывод – результаты работы |
| 2 | stderr | вывод ошибок |
Три потока команды и перенаправления
> ловит только stdout. Ошибки идут отдельно – в stderr:
ls /nonexistent > out.txt
После этой команды out.txt останется пустым (нечего было класть в stdout), но сообщение об ошибке все равно появится на экране – оно пошло в stderr, который никто не перенаправлял. Если вам нужно поймать именно ошибки – указываете номер потока перед >:
ls /nonexistent 2> errors.txt
А если нужно слить и нормальный вывод, и ошибки в один файл – конструкция выглядит немного странно, но запоминается раз и навсегда:
ls /nonexistent > all.txt 2>&1
Читается так: «stdout направить в all.txt, а stderr (2>) направить туда же, куда сейчас идет stdout (&1)». Порядок здесь важен – если поменять местами, перенаправление сработает не так, как ожидалось.
Отдельно стоит знать про /dev/null – виртуальный файл, который ведет себя как черная дыра: все, что туда записано, исчезает, ничего не сохраняется. Полезен, когда вывод команды не нужен вообще:
команда > /dev/null 2>&1
Контекст «кто я и где я» – это только про вашу сессию. Сам сервер тоже хочется опросить: давно ли работает, не перегружен ли, какое там сейчас время, какая версия ядра.
Начнем с того, как машина себя в целом чувствует:
uptime
Выведет, сколько работает машина, сколько на ней залогинено пользователей, и главное – load average за последние 1, 5 и 15 минут. Load average – это длина очереди процессов к процессору. На 4-ядерной машине load average: 4.00 означает, что CPU полностью загружен, ровно столько процессов хотят считать, сколько ядер. 8.00 – вдвое больше процессов, чем ядер, половина стоит в очереди и ждет. Три числа за разные интервалы дают тренд: растет нагрузка или падает.
В Linux эта метрика считается чуть иначе, чем в классическом Unix. В очередь попадают не только процессы, которым нужен CPU, но и те, что висят в так называемом uninterruptible sleep – обычно это процессы, ждущие ответа от диска или сети. Поэтому высокий load average на Linux не обязательно означает, что процессор в напряге. Иногда CPU простаивает, а load average зашкаливает – потому что висит NFS, или диск тормозит, или сетевая файловая система не отвечает.
uptime показывает load average: 8.00, 7.50, 6.20. Что это значит?Дальше – время. Казалось бы, что про него рассказывать, но в работе DevOps время – это отдельная история:
date date +%F date +%Y-%m-%d_%H-%M
Просто date выдаст текущее время в человеческом формате. С аргументом +... можно попросить любой формат, какой нужен. +%F – короткая дата вида 2026-04-20. +%Y-%m-%d_%H-%M – дата со временем, но без пробелов: 2026-04-20_14-30. Именно такой формат – стандарт для имен файлов, потому что пробелы ломают скрипты, двоеточия не работают на части файловых систем (особенно когда дело доходит до Windows-совместимости), а тире и подчеркивания везде безобидны. Когда чуть позже вы будете писать backup_$(date +%F).tar.gz, форматирование уже не покажется лишним.
Еще про время: серверы почти всегда работают в UTC, независимо от того, в какой стране стоит железо и где находится администратор. Это сделано, чтобы при сборе логов с пяти разных серверов не тратить полдня на пересчет часовых поясов – все события везде в одной шкале. Если вы залогинились на сервер и время кажется смещенным относительно вашего – это почти наверняка UTC. Базу часовых поясов, кстати, хранит файловая система: в /usr/share/zoneinfo/ лежит копия открытой базы IANA tz database, которую сообщество ведет с 1986 года.
И последнее в этой триаде:
uname -r uname -a
uname (от Unix name) выводит данные о самой системе. -r покажет версию ядра – что-то вроде 5.15.0-91-generic. -a – все разом: имя ядра, hostname машины, дата сборки, архитектура. В повседневной работе версия ядра нужна нечасто, но критична в нескольких сценариях: установка драйверов (модули должны соответствовать ядру), проверка уязвимостей по CVE (часто опубликована для конкретного диапазона версий), отладка багов, которые правились в каком-то конкретном релизе. Стоит знать, как ее быстро узнать.
После пары часов работы в терминале у вас накопится десяток-другой команд, которые вы уже вводили – и которые наверняка захочется повторить. Bash про это знает и сохраняет все в файл ~/.bash_history – это обычный текстовый файл, можете в любой момент его открыть и посмотреть. Чтобы выгрузить историю в терминал, есть команда:
history
Но интереснее не столько просмотр, сколько быстрый доступ к недавним командам, не перенабирая их руками. У bash для этого есть набор сокращений:
!5 повторяет команду с номером 5 из истории. Сам номер видно в выводе history – слева в каждой строке. !! повторяет последнюю команду; вроде мелочь, но в одном сценарии незаменим: запустили команду, получили permission denied, забыли sudo. Вместо того чтобы лезть стрелкой вверх и добавлять sudo в начало, пишете sudo !! – и оно подставит предыдущую команду и выполнит ее с правами root. !$ – отдельный приятный трюк, подставляет последний аргумент предыдущей команды. Написали mkdir /opt/app/configs, дальше cd !$ – и вы уже в свежесозданной директории.
Это все хорошо, но в реальной работе главный инструмент истории – Ctrl+R. Жмете, и bash переходит в режим поиска по истории. Вводите часть команды – bash находит самое свежее совпадение и показывает его, готовое к запуску. Жмете Ctrl+R еще раз – следующее, более старое совпадение. Enter – выполнить. Esc – выйти из поиска, оставив команду в строке для редактирования. Это тот же поиск по ~/.bash_history, что и history | grep, только сразу интерактивный и встроен в bash.
По умолчанию bash хранит 500 последних команд. Для серьезной работы это маловато. Размер настраивается несколькими переменными в ~/.bashrc:
HISTSIZE=10000 # сколько хранить в памяти текущей сессии
HISTFILESIZE=20000 # сколько хранить в файле .bash_history
HISTCONTROL=ignoredups # не сохранять подряд идущие дубликаты
После правки ~/.bashrc либо переоткрываете терминал, либо делаете source ~/.bashrc в текущей сессии. И дальше история становится по-настоящему полезной – через месяц можно найти ту самую команду, которой когда-то починили nginx.
Все, что мы делали выше, – запускали команды, смотрели на их вывод, направляли его в файлы. Но иногда нужно не просто посмотреть на вывод, а использовать его как часть другой команды. Например, добавить текущую дату в имя файла бэкапа. Для этого в bash есть $(...):
echo "Сервер $(hostname), пользователь $(whoami)"
Работает так: bash сначала видит $(hostname), выполняет команду внутри, забирает ее вывод и подставляет на это место в исходной строке. То же самое с $(whoami). И только когда все подстановки выполнены, запускается уже сам echo – он видит готовую строку и просто ее печатает. Сам echo ничего не знает ни про hostname, ни про whoami.
Самый частый практический пример – снова про бэкапы:
cp /etc/nginx/nginx.conf /backup/nginx_$(date +%F).conf
date +%F выполнится, вернет 2026-04-20, и cp получит на вход уже готовое имя nginx_2026-04-20.conf. Этот паттерн встретится в курсе еще много раз – везде, где нужно во время выполнения скрипта вписать в имя файла какие-то живые данные.
В старом коде вместо $(...) иногда встречается то же самое в обратных кавычках: `hostname`. Это устаревший синтаксис, он работает, но имеет два неприятных свойства. Во-первых, плохо вкладывается друг в друга – попробуйте написать `dirname `readlink -f $0` ` и сразу увидите, в чем проблема: парсер не понимает, какие кавычки к каким. Во-вторых, легко путается визуально с обычными одинарными кавычками. С $(...) ни того, ни другого: вкладывайте как угодно, конструкция всегда читаемая.
echo $(dirname $(readlink -f /usr/bin/python3))
Внутри есть еще один вызов внутри – все распарсится правильно.
echo "Лог: $(date +%F).log"?В этом уроке мы прошли всякие разные команды, и у каждой из них на самом деле еще десятки опций – мы трогали только самые ходовые. Запоминать все это руками не нужно. Есть три уровня встроенной документации.
Первый уровень – короткая справка от самой команды:
date --help
Флаг --help есть почти у всего: показывает краткое описание команды и список всех ее опций с однострочными пояснениями. Когда нужно быстро вспомнить, как пишется конкретный флаг, – этого обычно хватает.
Когда --help мало, есть полная документация:
man date
man (от manual) открывает развернутую справку: подробное описание, все флаги с полными пояснениями, примеры использования, ссылки на связанные команды. Внутри pager – листается стрелками и Page Up / Page Down, поиск по странице – /слово, выход – q.
Тут есть тонкость, которая часто сбивает в первый раз. Man-страницы разбиты на секции, пронумерованные от 1 до 9: 1 – обычные команды, 5 – форматы конфигурационных файлов, 8 – команды администратора, и так далее. У одного и того же имени могут быть страницы в разных секциях. Например, man passwd покажет описание команды passwd для смены пароля, а man 5 passwd – формат файла /etc/passwd. Это два разных документа про две разные вещи, у которых случайно одинаковое имя. Если когда-нибудь ищете описание формата файла, а получаете команду – попробуйте указать секцию явно.
И третий уровень, не для всех систем, но очень удобный – tldr. Это утилита, которая показывает не полную документацию, а пять-десять самых частых примеров использования команды. Когда вы помните, что у tar есть нужная вам опция, но не помните какая, читать двадцать экранов man не хочется – tldr tar выдаст ровно те команды, которые в большинстве случаев и нужны. Ставится как обычный пакет: sudo apt install tldr, дальше tldr команда.
Был полезен урок?
Войди чтобы оставить комментарий.
Кайф
sudo apt install tldr - apt устанавливает старый клиент, написанный на языке Haskell. Разработчики tldr изменили формат отдачи архивов со своими справками, из-за чего этот заброшенный Haskell-клиент больше не может распаковать скачанный zip-архив и падает с ошибкой...
подкинули доп квест под конец )
для пробника прям хорошо
неплохо, понравилось!
отлично