Командная строка Linux: академический курс системного администрирования

Углублённый курс по командной строке Linux, ориентированный на систематизацию знаний для эффективного повседневного администрирования. Рассматриваются архитектура оболочки, управление правами доступа, потоки ввода-вывода, управление процессами и автоматизация задач. Курс построен на теоретическом понимании механизмов работы системы с акцентом на логику и принципы.

1. Архитектура командной оболочки и интерпретаторы

Архитектура командной оболочки и интерпретаторы

Когда вы открываете терминал и видите мигающий курсор, за этим простым действием стоит сложная цепочка взаимодействий между ядром операционной системы, драйверами устройств и программой-оболочкой. Понимание этой цепочки — не академическое упражнение, а практическая необходимость: именно от архитектуры оболочки зависит, почему одни команды работают мгновенно, а другие «зависают», почему переменные окружения ведут себя по-разному в разных сессиях и почему скрипт, написанный для Bash, ломается в Dash.

Ядро, оболочка и терминал: три слоя взаимодействия

Ядро (kernel) — это программа, которая управляет всеми ресурсами компьютера: процессором, памятью, дисками, сетью. Оно не имеет пользовательского интерфейса. Чтобы человек мог с ним взаимодействовать, нужен посредник.

Командная оболочка (shell) — это именно такой посредник. Она принимает текстовые команды от пользователя, интерпретирует их и передаёт ядру через системные вызовы (system calls). Результат ядро возвращает оболочке, а та — пользователю.

Терминал (terminal emulator) — это программа, которая эмулирует физический терминал прошлого века. Она отображает символы, обрабатывает ввод с клавиатуры и управляет окном. Терминал сам по себе ничего не исполняет — он лишь передаёт потоки данных между пользователем и оболочкой.

Представьте это как почтовую систему: терминал — это почтовый ящик, оболочка — почтальон, который читает письма и разносит их, а ядро — почтовое отделение, которое обрабатывает отправления. Почтальон не знает, как работает сортировочная машина, а почтовое отделение не знает, кто написал письмо.

Семейство оболочек Bourne и C

Исторически сложились два семейства оболочек. Первое — Bourne shell (sh), созданный Стивеном Боурном в 1979 году. Его синтаксис стал стандартом для написания сценариев. Второе — C shell (csh), чей синтаксис напоминает язык C. Сегодня в системном администрировании доминирует первое семейство.

| Оболочка | Имя | Особенности | |----------|-----|-------------| | Bourne Again Shell | Bash | Стандарт большинства дистрибутивов Linux, расширенный синтаксис | | Z Shell | zsh | Улучшенное автодополнение, плагины, используется в macOS по умолчанию | | Dash | dash | Минималистичная, быстрая, используется как /bin/sh в Debian/Ubuntu | | Korn Shell | ksh | Компромисс между sh и csh, популярен в коммерческих Unix |

Узнать текущую оболочку можно командой echo 0 — оболочку, которая выполняет текущий процесс.

Процесс инициализации: login и non-login сессии

Когда пользователь входит в систему, оболочка проходит процесс инициализации — последовательность чтения конфигурационных файлов. Этот процесс различается в зависимости от типа сессии.

Login-сессия возникает при входе в систему: через консоль (Ctrl+Alt+F1), SSH или графический терминал с явной аутентификацией. Bash в этом случае читает файлы в такой последовательности:

  • /etc/profile — глобальная конфигурация для всех пользователей
  • ~/.bash_profile, ~/.bash_login или ~/.profile — первый существующий из трёх
  • Non-login сессия — это когда вы открываете новый терминал в графической среде или запускаете оболочку из другой оболочки. Здесь Bash читает только ~/.bashrc.

    Это различие объясняет частую проблему: вы добавили переменную в .bashrc, но при входе по SSH она не работает — потому что login-сессия читает .profile, а не .bashrc. Решение — добавить в .profile строку source ~/.bashrc.

    Внутренние и внешние команды

    Оболочка различает два типа команд. Встроенные команды (builtins) исполняются непосредственно процессом оболочки без создания нового процесса. К ним относятся cd, export, alias, echo, type. Внешние команды — это отдельные исполняемые файлы в каталогах, перечисленных в переменной PATH — это список каталогов, разделённых двоеточиями, в которых оболочка ищет исполняемые файлы. Порядок каталогов имеет значение: оболочка останавливается на первом совпадении. Поэтому добавление /usr/local/bin в начало $PATH позволяет переопределить системные утилиты локальными версиями.

    Режимы интерактивного ввода

    Оболочка работает в двух режимах обработки ввода. Канонический режим (canonical mode) буферизирует ввод до нажатия Enter — оболочка получает готовую строку. Неканонический режим (non-canonical mode) передаёт каждый символ немедленно — используется в программах вроде vim или top.

    Управляет этим драйвер терминала line discipline — промежуточный слой между клавиатурой и приложением. Он обрабатывает специальные символы: Ctrl+C (SIGINT), Ctrl+Z (SIGTSTP), Backspace. Именно поэтому нажатие Ctrl+C в оболочке прерывает текущую команду, а не вставляет символ — line discipline перехватывает его до того, как он достигнет оболочки.

    Понимание этой архитектуры позволяет эффективно диагностировать проблемы: если терминал «глючит» после прерванной программы, команда reset сбрасывает настройки line discipline. Если скрипт ведёт себя непредсказуемо — проверьте, какую оболочку указывает shebang-строка (#!/bin/bash или #!/bin/sh), потому что /bin/sh в разных дистрибутивах может указывать на разные интерпретаторы.

    2. Управление правами доступа и владение файлами

    Управление правами доступа и владение файлами

    Представьте сервер, на котором работают десятки сервисов: веб-сервер пишет логи, база данных хранит файлы, планировщик задач создаёт отчёты. Если каждый из них сможет читать и изменять любые файлы системы, один скомпрометированный сервис получит доступ ко всем данным. Именно для предотвращения этого сценария в Unix реализована модель прав доступа (discretionary access control, DAC) — система, которая определяет, кто и что может делать с каждым файлом.

    Триада владелец-группа-остальные

    Каждый файл в файловой системе Linux принадлежит одному пользователю-владельцу (owner) и одной группе (group). Права доступа определяются для трёх категорий субъектов:

  • Владелец (user, буква u) — пользователь, который создал файл
  • Группа (group, буква g) — пользователи, входящие в назначенную группу
  • Остальные (others, буква o) — все остальные пользователи системы
  • Для каждой категории определены три базовых права: чтение (read, r), запись (write, w) и исполнение (execute, x). Команда ls -l отображает эту информацию в первом столбце вывода:

    Первый символ (-) указывает тип файла. Следующие девять символов разбиты на три группы по три: rwx — права владельца, r-x — права группы, r-- — права остальных.

    Битовая маска и восьмеричное представление

    Каждое право можно представить как бит: r=4, w=2, x=1. Три права складываются в число от 0 до 7. Так, rwx = , r-x = , r-- = . Полная тройка 754 означает: владелец может читать, писать и исполнять; группа — читать и исполнять; остальные — только читать.

    Команда chmod принимает как восьмеричное, так и символьное представление:

    Символьный режим удобен для точечных изменений: u+x добавляет право исполнения владельцу, не затрагивая остальные биты. Запись o= с пустым правой частью снимает все права у категории «остальные».

    Специальные биты: SUID, SGID и sticky

    Помимо девяти базовых бит, существуют три специальных бита, которые меняют поведение исполняемых файлов и каталогов.

    SUID (Set User ID, бит s в позиции владельца) заставляет исполняемый файл запускаться с правами владельца файла, а не запустившего его пользователя. Классический пример — /usr/bin/passwd: файл принадлежит root, и любой пользователь, запуская его, временно получает привилегии root для изменения своего пароля. В восьмеричном виде — четвёртая цифра: chmod 4755 file.

    SGID (Set Group ID, бит s в позиции группы) работает аналогично для группы. На каталогах SGID имеет другое значение: все новые файлы в этом каталоге наследуют группу каталога, а не группу создающего пользователя. Это критично для совместной работы: chmod 2775 /shared/project.

    Sticky bit (бит t) на каталоге запрещает удалять файлы всем, кроме владельца файла и root. Классический пример — /tmp: любой может создать там файл, но удалить его может только создатель. Восьмеричное представление — chmod 1777 /tmp.

    Бит маски доступа: umask

    Когда процесс создаёт файл, ядро применяет umask — маску, которая вычитает права из максимальных значений. По умолчанию umask равна 0022: новые файлы получают права (rw-r--r--), каталоги — (rwxr-xr-x).

    Значение umask можно изменить командой umask 002 — это полезно в групповых проектах, где все участники должны иметь возможность редактировать файлы друг друга. Значение umask наследуется дочерними процессами, поэтому его обычно устанавливают в конфигурационных файлах оболочки.

    Расширенные атрибуты и ACL

    Базовая модель DAC с тремя категориями слишком груба для сложных сценариев: что если нужно дать права одному конкретному пользователю, не делая его владельцем и не включая в группу? Для этого существуют расширенные списки управления доступом (Access Control Lists, ACL).

    Команда getfacl показывает полные права на файл, включая ACL-записи:

    Флаг -m модифицирует ACL, -x удаляет запись, -d устанавливает ACL по умолчанию для каталога (наследуется новыми файлами). ACL расширяют, но не заменяют базовую модель: если стандартные права запрещают доступ, ACL не помогут.

    Расширенные атрибуты (chattr, lsattr) позволяют устанавливать иммунитетные флаги. Например, chattr +i file делает файл неизменяемым — даже root не сможет его изменить или удалить без снятия атрибута. Это полезно для защиты критических конфигурационных файлов от случайного изменения.

    Числовые идентификаторы: UID и GID

    За каждым именем пользователя и группы стоят числовые идентификаторы: UID (User ID) и GID (Group ID). Ядро работает именно с числами — имена существуют только для удобства людей. Файл /etc/passwd сопоставляет UID с именем, /etc/group — GID.

    Специальные UID имеют системное значение: 0 — всегда root, 1–99 — зарезервированы для системных пользователей, 65534 — псевдопользователь nobody. При миграции данных между системами важно проверять не имена, а именно числовые UID: пользователь alice с UID 1001 на одном сервере может иметь совершенно другое имя на другом, но файлы будут принадлежать ему по UID.

    Практическое правило: никогда не назначайте UID 0 пользователю, который не должен иметь полных привилегий. Даже если имя не root, UID 0 даёт абсолютную власть над системой.

    3. Потоки ввода-вывода и конвейеры

    Потоки ввода-вывода и конвейеры

    Когда вы вводите команду ls /etc | grep conf | wc -l, три программы обмениваются данными без создания промежуточных файлов на диске. Этот механизм — конвейер (pipeline) — является краеугольным камнем философии Unix: каждая программа делает одну вещь хорошо, а сложные задачи решаются их комбинацией. Чтобы использовать эту философию в полной мере, необходимо понимать, как устроены потоки ввода-вывода (I/O streams) на уровне ядра.

    Три стандартных потока

    Каждый процесс при создании получает три файловых дескриптора — числовых «ручки», через которые он общается с внешним миром:

  • Стандартный ввод (standard input, stdin) — дескриптор 0, откуда процесс читает данные
  • Стандартный вывод (standard output, stdout) — дескриптор 1, куда процесс пишет обычные данные
  • Стандартный поток ошибок (standard error, stderr) — дескриптор 2, куда процесс пишет сообщения об ошибках
  • Разделение stdout и stderr — не формальность. Представьте, что вы запускаете команду и перенаправляете её вывод в файл. Если бы ошибки шли в тот же поток, они смешались бы с данными, и файл стал бы бесполезен. Именно поэтому grep пишет найденные строки в stdout, а сообщение «No such file or directory» — в stderr.

    Перенаправление потоков

    Перенаправление (redirection) позволяет направить стандартные потоки не в терминал, а в файл или из файла. Оператор > перезаписывает файл, >> дописывает в конец:

    Перенаправление stderr выполняется через указание номера дескриптора: 2>. Чтобы объединить оба потока в один файл, используется конструкция 2>&1:

    Порядок записи имеет значение: 2>&1 должно идти после >, потому что операция читается справа налево. Запись command 2>&1 > file сначала направит stderr туда, куда сейчас идёт stdout (в терминал), а затем перенаправит stdout в файл — stderr останется в терминале.

    Конвейер: связь процессов через pipe

    Оператор | создаёт неименованный канал (unnamed pipe) — односторонний буфер в памяти ядра, который соединяет stdout левого процесса с stdin правого. Ключевой момент: все процессы в конвейере запускаются параллельно, а не последовательно. Ядро буферизует данные между ними.

    В этой цепочке каждая программа получает данные, обрабатывает их и передаёт дальше. cat читает файл, grep фильтрует строки, sort сортирует, uniq -c подсчитывает дубликаты, второй sort -rn сортирует по убыванию количества, head -10 берёт первые десять строк. Ни один промежуточный файл не создаётся.

    Важный нюанс: по умолчанию конвейер передаёт только stdout. Чтобы передать stderr через конвейер, нужно перенаправить его в stdout заранее:

    Именованные каналы (FIFO)

    Именованный канал (named pipe, FIFO) — это специальный файл в файловой системе, который работает как конвейер, но существует как именованный объект. Его можно создать командой mkfifo:

    Один процесс пишет в канал, другой — читает. Пока оба не подключатся, операция блокируется. Это позволяет связывать процессы, которые не являются родственными и не могут быть соединены через |.

    Практический пример: один процесс генерирует данные, а несколько независимых скриптов их обрабатывают. Именованный канал работает как простейшая система обмена сообщениями без установки дополнительного программного обеспечения.

    /dev/null, /dev/zero и специальные файлы

    Файловая система /dev содержит специальные файлы устройств — интерфейсы ядра, которые ведут себя как файлы. Два из них используются повсеместно:

    /dev/null — «чёрная дыра». Запись в этот файл уничтожает данные. Чтение возвращает немедленный EOF. Используется для подавления нежелательного вывода:

    /dev/zero — бесконечный источник нулевых байтов. Используется для инициализации файлов или заполнения памяти при тестировании.

    Комбинация перенаправлений и here-документы

    Помимо файлов, источником ввода может быть here-документ (here document) — блок текста, встроенный прямо в команду:

    Двойные кавычки вокруг EOF отключают подстановку переменных и команд. Это удобно для генерации конфигурационных файлов в скриптах без создания временных файлов.

    Here-string — упрощённый вариант для передачи одной строки:

    Буферизация и производительность

    Когда данные проходят через конвейер, каждая программа буферизует ввод и вывод. Различают три режима буферизации: построчный (данные передаются построчно), блочный (данные копятся до заполнения буфера, обычно 4–8 КБ) и полный (весь вывод буферизуется до завершения программы).

    Программы, записывающие в терминал, обычно используют построчную буферизацию. Но при записи в конвейер или файл стандартная библиотека C переключается на блочную буферизацию. Это объясняет, почему вывод tail -f на лог-файл может «задерживаться» — данные копятся в буфере, пока он не заполнится.

    Для принудительной построчной буферизации существует утилита stdbuf:

    Флаг -oL устанавливает построчную буферизацию для stdout, -eL — для stderr. Это критично при обработке логов в реальном времени, когда каждая строка должна немедленно появляться в конвейере.

    4. Мониторинг и управление процессами

    Мониторинг и управление процессами

    Каждая запущенная в Linux программа существует как процесс — экземпляр исполняемого файла, которому ядро выделило виртуальное адресное пространство, дескрипторы файлов и уникальный идентификатор. В любой момент времени система может содержать сотни процессов, и умение наблюдать за ними, управлять их жизненным циклом и диагностировать проблемы отличает уверенного администратора от начинающего.

    Иерархия процессов и PID

    Каждому процессу при создании присваивается уникальный идентификатор процесса (Process ID, PID) — целое число от 1 до 32768 (в старых ядрах) или до (в современных). Процесс с PID 1 — это init (или systemd в современных дистрибутивах), прародитель всех остальных процессов.

    Каждый процесс, кроме init, имеет родительский процесс (parent process, PPID). Это порождает дерево процессов: при запуске команды оболочка создаёт дочерний процесс через системный вызов fork, заменяет его образ через exec и ожидает завершения через wait. Узнать PID текущей оболочки можно через echo $PPID.

    Команда ps отображает процессы. Формат ps aux показывает все процессы всех пользователей в формате BSD, а ps -ef — в формате System V. Для древовидного представления служит pstree:

    Состояния процесса

    Процесс в каждый момент времени находится в одном из состояний (states):

    | Состояние | Код | Описание | |-----------|-----|----------| | Running | R | Выполняется на процессоре или готов к выполнению | | Sleeping | S | Ожидает события (ввод-вывод, таймер) — прерываемый сон | | Disk sleep | D | Ожидает операции с диском — непрерываемый сон | | Stopped | T | Остановлен сигналом SIGSTOP или поставлен на паузу | | Zombie | Z | Завершился, но родитель не прочитал код возврата |

    Состояние zombie заслуживает особого внимания. Зомби-процесс уже освободил все ресурсы, но его запись в таблице процессов остаётся, пока родитель не вызовет wait и не получит код возврата. Если родитель завершился раньше дочернего процесса, зомби становится дочерним процессом init (PID 1), который периодически вызывает wait и очищает их. Накопление зомби — признак ошибки в программе-родителе.

    Сигналы: механизм управления процессами

    Сигнал (signal) — это асинхронное уведомление, которое ядро отправляет процессу. Сигналы — основной инструмент управления процессами из командной строки.

    Наиболее используемые сигналы:

  • SIGINT (2) — прерывание, отправляется по Ctrl+C. Процесс может перехватить и обработать
  • SIGTERM (15) — запрос на завершение. Процесс может корректно завершить работу. Сигнал по умолчанию для kill
  • SIGKILL (9) — принудительное завершение. Нельзя перехватить, игнорировать или обработать. Ядро немедленно уничтожает процесс
  • SIGSTOP (19) — приостановка процесса. Нельзя перехватить
  • SIGCONT (18) — возобновление приостановленного процесса
  • Отправить сигнал можно командой kill:

    Разница между kill, killall и pkill: первая работает по PID, вторая — по имени процесса, третья — по произвольному шаблону. pkill наиболее гибкая, но и наиболее опасная — неправильный шаблон может остановить не тот процесс.

    Управление задачами оболочки

    Оболочка позволяет запускать процессы в фоновом режиме (background) и управлять ими как задачами (jobs). Символ & в конце команды запускает её в фоне:

    Комбинация Ctrl+Z отправляет текущему процессу SIGSTOP, переводя его в состояние остановки. После этого доступны команды:

  • bg %1 — возобновить задачу 1 в фоновом режиме
  • fg %1 — перевести задачу 1 в foreground
  • disown %1 — отсоединить задачу от оболочки (после закрытия терминала задача продолжит работу)
  • Спецификация %n ссылается на задачу по номеру, %string — по имени команды, %% — на текущую задачу.

    Важный нюанс: при закрытии терминала все дочерние процессы получают SIGHUP. Чтобы процесс пережил завершение сессии, используйте nohup command & или disown. Утилита nohup игнорирует SIGHUP и перенаправляет вывод в файл nohup.out.

    Мониторинг ресурсов: top, htop и /proc

    Команда top отображает процессы в реальном времени, отсортированные по потреблению ресурсов. Ключевые столбцы:

  • %CPU — процент времени процессора за последний интервал
  • %MEM — доля оперативной памяти
  • NI — приоритет nice (от -20 до 19, где -20 — наивысший)
  • VIRT/RES — виртуальный и резидентный размер памяти
  • Изменить приоритет можно через nice (при запуске) или renice (для работающего процесса):

    Файловая система /proc — это интерфейс ядра, который отображает внутренние структуры данных как файлы. Каталог /proc/[PID]/ содержит полную информацию о процессе: /proc/[PID]/status — состояние, /proc/[PID]/maps — отображение памяти, /proc/[PID]/fd/ — открытые дескрипторы. Чтение этих файлов не требует специальных утилит — достаточно cat.

    OOM-killer и лимиты ресурсов

    Когда система исчерпывает оперативную память, ядро запускает OOM-killer (Out Of Memory killer) — механизм, который принудительно завершает процессы для освобождения памяти. OOM-killer выбирает жертву на основе oom_score — эвристики, учитывающей потребление памяти, время работы и другие факторы. Проверить oom_score процесса можно через /proc/[PID]/oom_score.

    Для предотвращения OOM-ситуаций используются лимиты ресурсов через ulimit (для оболочки) или cgroups (для системы):

    Лимиты ulimit наследуются дочерними процессами и действуют в рамках сессии оболочки. Для системного контроля ресурсов используются cgroups — механизм ядра, позволяющий ограничивать CPU, память, диск и сеть для групп процессов, но это тема отдельного глубокого разговора.

    5. Основы автоматизации и написания скриптов

    Основы автоматизации и написания скриптов

    Администратор сервера ежедневно выполняет десятки однотипных действий: проверяет свободное место на дисках, перезапускает сервисы, анализирует логи, создаёт резервные копии. Каждое из них — одна или несколько команд в терминале. Но если эту последовательность нужно повторять каждый день в одно и то же время, ручной труд становится источником ошибок и потерянного времени. Скрипт (shell script) — это текстовый файл с командами, который оболочка исполняет последовательно, превращая рутину в автоматический процесс.

    Shebang и выбор интерпретатора

    Первая строка скрипта — shebang (hashbang) — определяет, какая программа будет его исполнять. Она начинается с символов #! и содержит полный путь к интерпретатору:

    Без shebang оболочка попытается исполнить файл текущей оболочкой. Это создаёт проблему переносимости: синтаксис Bash и Dash различается. Скрипт с #!/bin/bash гарантированно выполняется Bash, даже если /bin/sh указывает на Dash. Всегда указывайте shebang явно.

    Сделать файл исполняемым: chmod +x script.sh. Запуск: ./script.sh или bash script.sh. Разница: в первом случае shebang определяет интерпретатор, во втором — аргумент командной строки переопределяет его.

    Переменные, арифметика и подстановка команд

    Переменные в скриптах присваиваются без пробелов: count=5. Пробел вокруг = превращает выражение в вызов команды с аргументами. Для обращения к значению используется {}:

    Подстановка команды (command substitution) позволяет встроить результат команды в выражение. Синтаксис (date +%Y-%m-%d) file_count=(( )):

    Условные конструкции

    Команда if проверяет код возврата (exit status) предыдущей команды: 0 — успех, ненулевое значение — неудача. Конструкция [[ ]] — это расширенная проверка условий Bash, поддерживающая регулярные выражения и логические операторы:

    bash #!/bin/bash for user in 3 >= 1000 {print (eval echo "~home_dir" ]]; then size=home_dir" 2>/dev/null | cut -f1) echo "size" fi done bash while IFS= read -r line; do echo "Processing: 1" local message="(date '+%Y-%m-%d %H:%M:%S')] [message" | tee -a /var/log/script.log }

    log_message "INFO" "Скрипт запущен" log_message "ERROR" "Диск заполнен" bash set -e # прервать скрипт при ошибке любой команды set -u # прервать при обращении к неопределённой переменной set -o pipefail # код возврата конвейера — максимальный код среди всех команд bash temp_file=temp_file'; echo 'Очистка выполнена'" EXIT

    ... основная логика скрипта ...

    bash bash -x script.sh # запуск с трассировкой

    или внутри скрипта:

    set -x # включить трассировку

    ... отладочный участок ...

    set +x # выключить

    минута час день месяц день_недели команда

    30 3 * /home/admin/scripts/backup.sh >> /var/log/backup.log 2>&1 ``

    Это задание запускает скрипт резервного копирования каждый день в 3:30 ночи. Символ означает «каждый». Значения можно перечислять через запятую (1,15,30), указывать диапазоны (1-5) и шаги (/15 — каждые 15 единиц).

    Важный нюанс: cron запускает задачи с минимальным окружением. Переменная opt in v) verbose=true ;; o) output_file="0 [-v] [-o file] [args...]"; exit 0 ;; *) echo "Неизвестная опция: -((OPTIND - 1))

    if @"; do echo "Аргумент: ((OPTIND - 1)) удаляет обработанные опции из списка аргументов, оставляя только позиционные параметры.

    Кавычки вокруг "@` без кавычек.