Learning Platform
Глоссарий Troubleshooting
Урок 03.03 · 18 мин
Начальный
UnixФилософияPipesBash

Unix-философия: do one thing well

Если читать любую книгу по Unix или Linux, на третьей странице вы встретите фразу «Unix philosophy». Это не просто красивые слова — это вполне конкретный набор принципов, сформулированных Doug McIlroy в 1978 году, которые объясняют, почему ls | grep | sort | head работает так хорошо, а аналог в Windows PowerShell — куда более громоздкий.

В этом уроке разберём, что именно McIlroy сказал, почему это правило живёт уже 50 лет, и как оно отражается в каждой команде bash, которую вы будете писать.


Doug McIlroy 1978

Doug McIlroy — один из инженеров Bell Labs, где в 1969 году появился Unix. Он же изобрёл pipes (|) — может быть, главное изобретение Unix. В 1978 году в Bell System Technical Journal он сформулировал философию проекта в одном абзаце:

«Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new features.»

«Expect the output of every program to become the input to another, as yet unknown, program.»

«Don’t insist on interactive input.»

Перевод и пояснение:

  1. Делай одну вещь, делай её хорошо. Если программа умеет 100 вещей — ни одну не делает по-настоящему хорошо.
  2. Ожидай, что вывод твоей программы пойдёт в другую программу. Делай вывод machine-readable (текстом, по строкам), а не художественным форматированием.
  3. Не настаивай на интерактивном вводе. Программа должна работать в pipeline без участия человека.

Из этих трёх принципов выросло почти всё, что вы будете делать в bash. Давайте на конкретных примерах.


Принцип 1: одна программа — одна задача

В Unix вы не найдёте программу «утилита для работы с файлами», которая умеет копировать, переименовывать, удалять, искать и сортировать. Вместо этого есть:

Каждая утилита — одна задача
cpТолько копирует. Не переименовывает, не удаляет, не сортирует. Делает одну вещь
mvТолько перемещает/переименовывает. Под капотом — rename() syscall, очень дешёвая операция
rmТолько удаляет. Даже не спрашивает (если без -i). Простота = надёжность
lsТолько показывает список. Не редактирует, не удаляет
grepТолько фильтрует строки по паттерну. Не сортирует, не группирует
sortТолько сортирует. С разными опциями (числовая, по полю, обратная)
uniqТолько убирает дубликаты подряд идущих строк. Сам не сортирует — это работа sort
wcТолько считает (слова, строки, байты). Больше ничего

Каждая утилита маленькая (ls — около 5000 строк C-кода, cat — около 600 строк). Каждая делает одну вещь, и делает её хорошо: десятилетиями оттачивались опции, обработка edge cases, производительность.

Сравните это с PowerShell. Там есть один cmdlet Get-ChildItem, который умеет всё: список файлов, фильтрация, сортировка, рекурсивный обход, форматирование вывода. Внутри — десятки параметров. Сложно запомнить, сложно отладить.

Unix-way: маленькие инструменты + способ их соединять. Это даёт композицию: из 20 базовых утилит можно собрать миллион полезных pipeline’ов.


Принцип 2: text streams как universal interface

Программы в Unix общаются через текст. Это не случайно — это сознательный выбор.

Когда ls выдаёт список файлов, она выдаёт его текстом, по одной строке:

$ ls /etc | head -5
adduser.conf
alternatives
apparmor.d
apt
bash.bashrc

Когда grep принимает вход, она читает текст построчно. Когда sort принимает вход — то же самое. Когда wc считает — построчно.

Это позволяет соединять что угодно с чем угодно:

$ ls /etc | grep '^a' | sort | head -3
adduser.conf
alternatives
apparmor.d

Что произошло:

  1. ls /etc выдал список текстом.
  2. grep '^a' отфильтровал строки, начинающиеся с a.
  3. sort отсортировал (lexicographically).
  4. head -3 взял первые три.

Каждая программа не знает о других. Она просто читает текст из stdin и пишет текст в stdout. Pipe (|) соединяет stdout одной программы с stdin следующей.

Pipeline как сборка из деталей
ls /etcВыдаёт список файлов, по одной строке. Не знает, кто будет читать вывод
stdout -> stdin
grep '^a'Читает строки, оставляет только те, что начинаются с 'a'. Не знает, откуда пришли строки
stdout -> stdin
sortЧитает все строки, сортирует, пишет результат. Стандартный bytewise sort
stdout -> stdin
head -3Читает строки, выдаёт первые 3, закрывает stdin. Sort и предыдущие тоже остановятся

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

$ grep ERROR /var/log/airflow/worker.log | grep "$(date +'%Y-%m-%d %H')" | wc -l

Это четыре независимые программы, соединённые pipes. Никто не писал «программу для подсчёта ошибок за час в логах Airflow» — она собралась из деталей.


Принцип 3: машиночитаемый вывод

«Не настаивай на интерактивном вводе» — это и про обратную сторону. Не делай вывод человеко-ориентированным, если он может быть использован программно.

Сравните два вывода ls:

# Без аргументов — компактно, для глаз
$ ls /etc | head -3
adduser.conf
alternatives
apparmor.d

# С -la — детально, всё ещё машиночитаемо
$ ls -la /etc | head -3
total 1296
drwxr-xr-x   1 root root  4096 May 13 14:00 .
drwxr-xr-x   1 root root  4096 May 13 14:00 ..

Строки идут одна за другой, разделители — пробелы и табы. Это парсится любым awk, sed, cut. Если бы ls выдавал ASCII-art таблицу с рамками и подсветкой — это бы парсилось гораздо хуже.

Современные tools иногда нарушают этот принцип ради красивого вывода (eza с цветами, bat с подсветкой синтаксиса). Но они умные: когда обнаруживают, что stdout — это pipe, а не терминал, переключаются на plain mode.

# В терминале — цветной вывод
$ eza -la

# В pipe — plain text без цветов
$ eza -la | head -5

Это называется «is stdout a tty?» детект. Старая Unix-традиция: красиво в терминале, плоско в pipe.

TIP

Когда пишете свои скрипты, придерживайтесь того же. printf "%s\t%s\n" "$name" "$size" — это машиночитаемо. echo "Файл $name размером $size байт" — нет. В будущих модулях про bash-скрипты мы будем явно следовать этому принципу.


Откуда взялось это всё

В 1969 году Ken Thompson и Dennis Ritchie в Bell Labs делали Unix. Компьютер был слабый (PDP-7, 8 KB памяти), но они захотели сделать многозадачную систему. Ограничения железа вынудили их делать маленькие программы — иначе ничего не помещалось в память.

McIlroy в 1973-м сделал концептуальный прорыв — добавил pipes как операционно-системную примитив. Идея: одна программа пишет в специальный buffer ядра, другая читает оттуда. Через эту трубу программы общаются без знания друг о друге.

Эту идею в 1978-м McIlroy и сформулировал как философию. К тому моменту Unix уже распространился по университетам и компаниям, и философия закрепилась как культурный стандарт.

Сегодня каждый Linux-сервер, каждый macOS-ноутбук, каждый Android-смартфон под капотом следуют этой философии. Когда вы пишете cat access.log | grep 404 | awk '{print $7}' | sort | uniq -c | sort -rn | head — вы делаете именно то, о чём McIlroy писал 50 лет назад.


Где философия нарушается (и почему это OK)

Любая философия имеет исключения. В Linux они тоже есть.

Случаи, где Unix-философия нарушается
systemdInitd-система делает много: процессы, логи, network, dns, time, монтирование. Многие критикуют за нарушение Unix-way
DockerОдин CLI делает всё: образы, контейнеры, сети, volumes, compose. Удобно, но не очень в Unix-духе
ffmpegОдна программа делает аудио-видео конвертацию ВСЕХ форматов мира. Сотни флагов

Это не плохо — это компромисс. systemd упрощает управление сервисами за счёт нарушения «do one thing». Docker одной командой делает то, что иначе требовало бы 10 утилит. ffmpeg покрывает все мыслимые сценарии видео.

Когда задача сложная и требует много контекста, монолитный инструмент выигрывает. Когда задача простая и хорошо ложится на «вход -> выход» — побеждают мелкие утилиты с pipe.

Для Junior DE: на 80% задач используются маленькие unix-утилиты с pipes. На 20% — большие инструменты типа Docker. Освоить надо и то, и другое.


Why this matters for DE

Вся работа DE — это data flow. Файл с данными -> читаем -> парсим -> фильтруем -> агрегируем -> пишем результат. Это буквально pipeline.

Unix-философия даёт нам готовый набор кирпичей для этого:

Типовой DE-pipeline на bash
curl URLСкачать данные с REST API или из S3. Стандартная команда для HTTP
|
jqПарсер JSON, написанный для unix-way. Принимает JSON в stdin, выдаёт обработанный JSON в stdout
|
awkПревращаем JSON в TSV. awk — мини-язык для обработки построчного текста
sortСортируем по нужному полю
|
uniq -cСчитаем уникальные комбинации. Получаем 'агрегацию'
>
output.tsvЗаписываем результат в файл. Готовая агрегация, без Python, без зависимостей

Такой pipeline:

  • работает на любой Linux-машине без установки чего-либо лишнего;
  • читает данные на лету, не загружая всё в память;
  • легко модифицируется (поменяйте jq-выражение — получите другую трансформацию);
  • запускается через cron в одну строку.

Это — сила Unix-философии. Сделать это же на Python заняло бы 50 строк, потребовало бы requests, pandas, и десяток зависимостей. И запускалось бы дольше.


Попробуй сам

Запустите классический pipeline для анализа логов:

$ mkdir -p ~/linux-sandbox/lesson-philosophy
$ cd ~/linux-sandbox/lesson-philosophy

# Создаём фейковый лог
$ cat > access.log <<'EOF'
192.168.1.1 GET /index.html 200
192.168.1.2 GET /about.html 200
192.168.1.1 GET /api/users 500
192.168.1.3 POST /login 200
192.168.1.2 GET /index.html 200
192.168.1.1 GET /api/users 500
192.168.1.4 GET /index.html 200
EOF

# Топ IP по числу запросов
$ cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn
      3 192.168.1.1
      2 192.168.1.2
      1 192.168.1.4
      1 192.168.1.3

# Сколько 500 ошибок
$ grep ' 500$' access.log | wc -l
2

# Какие URL получали ошибки
$ awk '$4 == 500 {print $3}' access.log | sort -u
/api/users

Каждая команда здесь — отдельная программа. Соединили — получили реальный отчёт. Это и есть Unix-философия в живом виде.


Проверка знанийKnowledge check
Почему Unix-философия 'делай одну вещь хорошо' оказывается практичнее для DE, чем монолитный инструмент?
ОтветAnswer
Композиция. Из 20 маленьких утилит можно собрать миллион полезных pipeline'ов под конкретную задачу. Из монолитного инструмента можно использовать только то, что в нём предусмотрено разработчиком. Когда задача нестандартная (а в реальной работе DE 80% задач нестандартные — каждый клиент, каждый лог, каждый CSV-формат свои), маленькие утилиты выигрывают: вы собираете решение под себя за минуты, без программирования. Кроме того, маленькие утилиты проще тестировать, дольше живут (grep написан в 1973-м и до сих пор актуален), и легче изучать (каждая утилита влезает в одну man-страницу). Минус: на сложных задачах с большим количеством state и нелинейной логикой pipes становятся неудобными — тут уже нужен полноценный язык (Python). Поэтому Unix-философия не отменяет программирование — она дополняет его, давая инструмент для быстрых трансформаций данных.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Кто и когда сформулировал Unix-философию?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4