Перейти к содержимому
Ступень 00.1 Рабочее окружение

Терминал и Git за один вечер

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

УРОКstage-0/0-1-environment/01-terminal-git.mdx2 чУрок 1 из 3
После урока вы сможете
  • Перемещаться по файлам и запускать команды в терминале Linux, macOS или WSL
  • Склеивать команды конвейерами | и перенаправлять вывод в файлы через > и >>
  • Вести историю проекта в Git — init, add, commit, log, diff, ветки
  • Клонировать репозиторий курса и сохранять в нём свои решения и прогресс

Почти всё, что вы будете делать в курсе, делается командами. Установить библиотеки — pip install. Скачать модель — git clone. Запустить обучение — python train.py. Посмотреть, чем занята видеокарта, — nvidia-smi. У арендованного сервера с GPU вообще нет рабочего стола: только чёрное окно и мигающий курсор.

терминалтерминал (командная строка)EN terminal / command lineОкно, где команды вводятся текстом. Исполняет их оболочка (shell) — bash или zsh. — это и есть такое окно. Вы набираете команду текстом, нажимаете Enter, и компьютер печатает ответ. Внутри окна работает программа-оболочка (shell): она читает ваши команды и запускает нужные программы. В Linux и WSL оболочка обычно называется bash, в macOS — zsh; для команд этого урока разницы нет.

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

Вторая половина урока — система контроля версийсистема контроля версийEN version control system (VCS)Хранит историю изменений файлов. Самая популярная — Git. Git. Он хранит историю вашего кода: можно откатиться к вчерашней версии, попробовать безумную идею в отдельной ветке и перенести работу на другой компьютер. Открытые LLM — DeepSeek, Qwen, MiniMind — публикуют свой код в Git-репозиториях. Курс ChinaAI тоже живёт в Git, и свои решения вы будете сохранять туда же.

Рекомендуем WSL (Windows Subsystem for Linux) — настоящий Linux внутри Windows. Откройте PowerShell от имени администратора и выполните:

Окно терминала
wsl --install

После перезагрузки появится приложение Ubuntu — это и есть ваш терминал. Работайте в домашней папке Linux (~), а не в /mnt/c/...: там файлы открываются в разы быстрее. В VS Code поставьте расширение WSL, и редактор будет работать прямо внутри Linux. PyTorch с CUDA в WSL 2 тоже работает.

Сайт курса и домашние работы можно запускать и из обычного PowerShell — так описано на странице «Установка и запуск». WSL нужен, чтобы команды этого урока работали как в Linux, и пригодится с 4-й ступени: vLLM и ряд других GPU-инструментов курса работают только в Linux.

Запасной вариант — Git Bash (ставится вместе с Git for Windows). Для Git его хватает, но многие Linux-инструменты там урезаны.

Окно терминала
grep -i --count "error" train.log
ЧастьПримерЧто это
командаgrepпрограмма, которую запускаем: поиск строк в файле
короткий флаг-iменяет поведение: не различать регистр букв (заглавные и строчные: Error = error)
длинный флаг--countто же, но словом: вывести не строки, а их количество
аргументы"error", train.logчто искать и где

Целиком команда читается так: «найди в файле train.log строки со словом error, не глядя на регистр, и скажи, сколько их». Части разделяются пробелами — поэтому пробелы в именах файлов доставляют неудобства (о них ниже).

Справку почти любая команда печатает по --help (например, grep --help), подробное руководство открывает man grep. Выход из man — клавиша q.

Файловая система — это дерево папок. Терминал всегда «стоит» в какой-то из них: это текущая папка.

Окно терминала
pwd # где я? → /home/ivan
ls # что здесь?
ls -la # подробно, включая скрытые файлы (имя начинается с точки)
cd projects # зайти в папку projects
cd .. # подняться на уровень вверх
cd ~ # домой (или просто cd)
cd - # вернуться в предыдущую папку
ЗаписьЗначение
/корень всей файловой системы
~ваша домашняя папка
.текущая папка
..родительская папка
/home/ivan/notesабсолютный путь — от корня, работает откуда угодно
notes/carp.txtотносительный путь — от текущей папки

Создать, посмотреть, переместить, удалить

Заголовок раздела «Создать, посмотреть, переместить, удалить»
Окно терминала
mkdir -p ml/notes # создать папку (-p: вместе с родительскими, без ошибки, если уже есть)
cd ml/notes
touch todo.txt # создать пустой файл
cp todo.txt todo_backup.txt # копировать
mv todo_backup.txt old.txt # переместить или переименовать
rm old.txt # удалить файл
cat todo.txt # вывести файл целиком
less big_log.txt # листать большой файл (q — выход, / — поиск)
head -n 5 data.csv # первые 5 строк
tail -n 20 train.log # последние 20 строк
tail -f train.log # следить за файлом в реальном времени — так смотрят на лог обучения

У каждой команды есть стандартный вывод (то, что она печатает) и стандартный ввод. Их можно перенаправлять:

Окно терминала
echo "первая заметка" > log.txt # echo печатает текст; > — записать его в файл (старое содержимое пропадёт!)
echo "вторая заметка" >> log.txt # >> — дописать в конец
cat log.txt
первая заметка
вторая заметка

конвейерконвейер (пайп)EN pipeСимвол | передаёт вывод одной команды на вход следующей. | соединяет команды: вывод левой становится вводом правой. Это главная идея Unix — маленькие программы, каждая делает одно дело, а сложное получается их сцеплением.

Создадим файл с текстом и посчитаем самые частые слова, не написав ни строчки на Python:

Окно терминала
cat > carp.txt << 'EOF'
карп плывёт против течения
карп прыгает через врата
кто перепрыгнет врата тот станет драконом
#прыжоквдраконы
EOF
cat carp.txt | tr ' ' '\n' | sort | uniq -c | sort -rn | head -3
2 карп
2 врата
1 через

Конструкция cat > carp.txt << 'EOF' … EOF записывает в файл все строки между двумя EOF. Это удобно, когда текст нужно вставить прямо из урока. Тот же файл можно создать и в обычном редакторе, результат одинаковый.

Теперь разберём конвейер по звеньям: cat выводит файл, tr ' ' '\n' заменяет пробелы переводами строк ('\n' — так записывают невидимый символ «новая строка»; получается одно слово — одна строка), sort сортирует, uniq -c схлопывает одинаковые соседние строки и пишет их количество, sort -rn сортирует по числу по убыванию (-r — в обратном порядке, -n — сравнивать как числа), head -3 оставляет первые три. Каждое звено получает на вход то, что напечатало предыдущее. Все слова, кроме «карп» и «врата», встречаются по одному разу, и порядок между ними зависит от настроек системы. Поэтому третья строка у вас может оказаться другой.

Обратите внимание на последнюю строку файла. Хэштег «#прыжоквдраконы» — это фраза из трёх слов: «прыжок», «в», «драконы». Но пробелов между ними нет, и tr считает всю фразу одним словом (уберите из конвейера | head -3 — и увидите её в списке целиком). Так же, без пробелов между словами, пишут по-китайски: там вся фраза — сплошная цепочка иероглифов. Разбить такой текст на части — отдельная задача, и называется она токенизация. Ей посвящён модуль 4.2.

Ещё один полезный инструмент — grep, поиск строк по образцу:

Окно терминала
grep "врата" carp.txt # строки, где есть «врата»
grep -c "карп" carp.txt # сколько таких строк → 2
grep -i "error" train.log # без учёта регистра — найти ошибки в логе
wc -l carp.txt # число строк → 4

Сравните:

Окно терминала
printf 'carp' | wc -c # 4 байта
printf 'карп' | wc -c # 8 байт
printf '€' | wc -c # 3 байта
printf '🐟🐉' | wc -c # 8 байт
printf '🐟🐉' | wc -m # 2 символа

wc -c считает байты, wc -m — символы. Здесь printf, а не echo, потому что echo добавляет в конец невидимый перевод строки, а это ещё один байт. Помните из урока «Как компьютер выполняет программу»: в кодировке UTF-8 латинская буква занимает 1 байт, кириллическая — 2, знак € (как и китайский иероглиф) — 3, эмодзи — 4. Терминал показывает то же самое. Из-за этого один и тот же смысл «стоит» разное число байтов на разных языках. Многие современные токенизаторы — программы, которые режут текст на токены для LLM (byte-level BPE в GPT-2, Qwen и других), — работают именно с байтами, и к этому факту мы вернёмся на ступени 4.

СообщениеЧто значитЧто делать
command not foundтакой программы нет или опечатка в именипроверьте написание; если программа не установлена — поставьте её (sudo apt install …)
No such file or directoryфайла или папки по этому пути нетpwd и ls — где вы и что рядом. Проверьте регистр букв: Data и data — разные имена
Permission deniedне хватает правне добавляйте sudo бездумно. Для своих файлов в домашней папке права администратора не нужны
Is a directoryкоманде нужен файл, а вы дали папкунапример, cat notes/ → нужно ls notes/ или cat notes/carp.txt
курсор мигает, ничего не происходиткоманда ждёт ввода (например, cat без файла) или долго работаетCtrl+C — прервать

Пробелы в именах файлов — частый источник проблем: cd my notes терминал поймёт как две папки. Либо берите имя в кавычки (cd "my notes"), либо, что лучше, называйте файлы без пробелов.

  • Tab дописывает имя файла или команды. Нажмите дважды — увидите варианты. Не набирайте длинные пути руками.
  • ↑ / ↓ — листать историю команд. Ctrl+R — поиск по истории: начните набирать pip и найдёте вчерашнюю длинную команду.
  • Ctrl+C прерывает зависшую или слишком долгую команду, например обучение, которое пошло не туда.
  • Ctrl+L или clear очищает экран.
  • history | grep git — вспомнить, что вы делали с Git.

Терминал хранит переменные окружения — именованные настройки, которые видят все запущенные из него программы. Знак $ перед именем значит «подставь сюда значение переменной»:

Окно терминала
echo $HOME # ваша домашняя папка
echo $PATH # список папок, где ищутся команды
which python3 # какой именно python3 запустится
export HF_HOME=~/hf-cache # задать переменную в текущем сеансе

HF_HOME из последней строки — папка, куда библиотеки Hugging Face складывают скачанные модели (понадобится на ступенях 6–8). Переменная, заданная через export, живёт, пока открыто это окно терминала.

Когда вы набираете python3, оболочка по очереди просматривает папки из PATH и запускает первый найденный файл с таким именем. Именно так работает «активация» виртуального окружения в следующем уроке: она ставит папку окружения в начало PATH.

Без Git история проекта выглядит так: model.py, model_v2.py, model_v2_final.py, model_v2_final_REALLY.py. Git заменяет этот хаос одной папкой и журналом коммитов.

коммиткоммитEN commitСнимок состояния файлов с сообщением, автором и датой. — это снимок всех отслеживаемых файлов (тех, которые вы хоть раз добавили в Git командой git add) с сообщением, автором и датой. Представьте сохранение в компьютерной игре: к нему всегда можно вернуться. Любой коммит можно посмотреть, сравнить с другим или восстановить. Папка проекта вместе со всей историей называется репозиториемрепозиторийEN repository (repo)Папка проекта вместе со всей историей изменений (скрытая подпапка .git).. История хранится в скрытой подпапке .git.

Изменение проходит три зоны:

flowchart LR
W["Рабочая папка<br/>(вы правите файлы)"] -- "git add" --> S["Индекс (staging)<br/>что войдёт в коммит"]
S -- "git commit" --> R["История коммитов<br/>(.git)"]
R -- "git push" --> G["Удалённый репозиторий<br/>(GitHub)"]
G -- "git pull / git clone" --> W
Путь изменения в Git

Зачем промежуточный индекс? Вы поправили пять файлов, а в коммит хотите положить только два, относящихся к одной задаче. git add выбирает, git commit фиксирует.

Один раз на компьютере представьтесь Git (имя и почта попадут в каждый коммит):

Окно терминала
git config --global user.name "Иван Петров"
git config --global user.email "ivan@example.com"
git config --global init.defaultBranch main

Дальше — настоящая сессия. У каждого коммита есть хеш — уникальный номер-отпечаток вроде f0ef469. У вас хеши будут другими: они зависят от времени и автора.

Окно терминала
mkdir my-ml-notes && cd my-ml-notes # && — выполнить вторую команду, только если первая прошла без ошибки
git init
echo "# Мои заметки по ChinaAI" > README.md
git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)

git status — самая важная команда: она говорит, что происходит и что делать дальше. Вызывайте её постоянно.

Окно терминала
git add README.md
git commit -m "Первая заметка"
[main (root-commit) f0ef469] Первая заметка
1 file changed, 1 insertion(+)
create mode 100644 README.md

Изменим файл и посмотрим разницу до коммита:

Окно терминала
echo "- 0.1: терминал и git" >> README.md
git diff
diff --git a/README.md b/README.md
index 9d290fe..c9c0f7c 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
# Мои заметки по ChinaAI
+- 0.1: терминал и git

Плюс — добавленная строка, минус — удалённая. git diff показывает ещё не добавленное в индекс, git diff --staged — то, что уже добавлено и войдёт в коммит.

Окно терминала
git add README.md
git commit -m "Добавил урок 0.1"
git log --oneline
a2b1457 (HEAD -> main) Добавил урок 0.1
f0ef469 Первая заметка

Пометка (HEAD -> main) означает «вы здесь»: вы стоите на ветке main, и она указывает на этот коммит.

Испортили файл и хотите вернуть последнюю закоммиченную версию? git restore README.md (если вы ещё не сделали git add; иначе сначала git restore --staged README.md). Посмотреть файл, каким он был в старом коммите: git show f0ef469:README.md.

Ошибки Git, которые встречаются в первую неделю:

СообщениеЧто значитЧто делать
fatal: not a git repositoryвы не внутри папки с репозиториемcd в папку проекта или git init
Author identity unknownGit не знает ваше имя и почтувыполните две команды git config --global ... выше
nothing to commit, working tree cleanизменений нет — всё уже закоммиченоэто не ошибка
nothing added to commit but untracked files presentзабыли git addgit add имя_файла, затем git commit
в терминале открылся текстовый редакторвы написали git commit без -m, и Git просит ввести сообщениевыйти без коммита: в Vim — :q! и Enter, в nano — Ctrl+X. Затем повторите с -m "сообщение"

веткаветкаEN branchНезависимая линия коммитов. Позволяет экспериментировать, не ломая основную версию. — это независимая линия коммитов. Основная ветка обычно называется main. Для эксперимента создают новую ветку: если идея не сработает, main останется нетронутой.

Окно терминала
git switch -c experiment # создать ветку и перейти на неё
git branch # список веток; звёздочка — та, на которой вы стоите
echo "черновик" > draft.txt
git add draft.txt && git commit -m "Черновик эксперимента"
git switch main # вернуться: draft.txt исчез из папки!
git merge experiment # влить эксперимент в main
git log --oneline --graph --all
* 47576e9 (HEAD -> main, experiment) Черновик эксперимента
* a2b1457 Добавил урок 0.1
* f0ef469 Первая заметка

Обе ветки, main и experiment, теперь указывают на один и тот же коммит. После git switch main файл draft.txt действительно пропадает из папки: Git показывает состояние той ветки, на которой вы стоите. После merge файл возвращается. Здесь слияние вышло простым (fast-forward): в main за это время ничего не менялось. Если обе ветки правили одну строку, Git попросит разрешить конфликт вручную. Это не страшно: откройте файл, выберите нужный вариант, затем git add и git commit. Как с ветками и pull request работают в команде и как по истории Git найти коммит, который всё сломал (git log -S, git blame, git bisect), — в уроке 11.1.2.

Файл .gitignore в корне проекта перечисляет то, что Git должен игнорировать:

.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
data/
*.pt
*.safetensors
.env
Не коммитимПочему
.venv/окружение пересоздаётся одной командой, а весит сотни мегабайт
*.pt, *.safetensors, data/веса и датасеты бывают гигабайтными. Git для них не предназначен
.envтам API-ключи. Публичные репозитории постоянно сканируют боты, и утёкший ключ находят очень быстро

Удалённый репозиторий (remote) — копия репозитория на сервере в интернете, обычно на GitHub. GitHub — это сайт, где хранят Git-репозитории и работают над ними вместе: что-то вроде облачного диска для кода, который понимает историю коммитов. Там же лежат открытые модели и курс ChinaAI. Три главные команды:

Окно терминала
git clone https://github.com/samnolak/ChinaAI.git # скачать репозиторий со всей историей
git pull # забрать новые коммиты с сервера
git push # отправить свои коммиты на сервер

Репозиторий курса закрытый (private), поэтому GitHub сначала спросит, кто вы. Обычный пароль от аккаунта здесь не подойдёт. Проще всего один раз войти через программу GitHub CLI: команда gh auth login задаст пару вопросов и откроет браузер для входа. Другие варианты — персональный токен (длинный пароль, который выдаёт GitHub) или SSH-ключ. Пошагово, с установкой GitHub CLI, это описано на странице «Установка и запуск». Если сайт курса у вас уже работает, значит, курс уже склонирован — второй раз клонировать не нужно.

После клонирования вы увидите:

ChinaAI/
├── site/ # этот сайт: уроки, интерактивные задания (npm run dev — запустить локально)
├── homework/ # домашние работы: условия, заготовки code/, тесты tests/, автогрейдер check.py
├── solutions/ # эталонные решения ДЗ — заглядывайте только после своей попытки
└── docs/ # документация проекта

Предлагаем простой рабочий процесс с отдельной веткой study:

  1. Один раз создайте свою ветку: git switch -c study. Все решения пишите здесь.
  2. Решили задание — закоммитьте: git add homework/s0-hw01-python-warmup && git commit -m "s0-hw01: Medium". Автогрейдер сохраняет ваш лучший уровень в homework/.results.json. Этот файл стоит в .gitignore курса, и Git его не коммитит: уровни всегда можно восстановить, снова запустив check.py на своих решениях.
  3. Прогресс сайта (пройденные уроки, квизы, карточки) хранится в браузере. Периодически экспортируйте его в JSON на странице «Карта курса и прогресс», положите файл в папку journal/progress/ и закоммитьте. Пошагово это описано на странице «Установка и запуск».
  4. Курс обновился? Заберите обновления и влейте их в свою ветку: git switch main && git pull && git switch study && git merge main.
  5. Хотите резервную копию на GitHub — git push -u origin study.

Чтобы предложить правку в чужой открытый проект, понадобятся ещё форк, второй удалённый репозиторий upstream и pull request — это разобрано в уроке 11.4.2.

Пройдите этот сценарий целиком. Он занимает 15–20 минут и закрепляет всё, что было в уроке.

  1. Создайте папку ~/chinaai-sandbox, зайдите в неё, сделайте git init.
  2. Создайте файл carp.txt с текстом из урока (через cat > carp.txt << 'EOF') и посчитайте частоты слов конвейером. Сохраните результат: ... | head -3 > top.txt.
  3. Закоммитьте оба файла с сообщением «Частоты слов». Проверьте git log --oneline — там должен быть один коммит.
  4. Создайте ветку experiment, допишите в carp.txt строку «карп стал драконом» (через >>) и закоммитьте с сообщением «Карп стал драконом». Подсказка: git commit -am "..." сразу добавляет в индекс все изменённые отслеживаемые файлы.
  5. Вернитесь в main и убедитесь командой tail -1 carp.txt, что новой строки там нет. Влейте ветку: git merge experiment.
  6. Добавьте .gitignore со строкой *.pt, создайте пустой файл model.pt и убедитесь, что git status его не видит.
  7. Найдите папку курса. Если вы уже клонировали его, когда запускали сайт, перейдите в эту папку (cd и путь к ней) и выполните ls homework и git log --oneline -5 — последние 5 коммитов курса. Если ещё не клонировали — перейдите в домашнюю папку (cd ~), чтобы не создать репозиторий внутри песочницы, склонируйте курс и выполните ls ChinaAI/homework.

Ожидаемый результат шагов 5–6 (хеши у вас будут другими):

$ git log --oneline --graph --all
* 1a5e66f (HEAD -> main, experiment) Карп стал драконом
* b3fb89f Частоты слов
$ git status --short
?? .gitignore

model.pt в списке нет: Git его игнорирует. Сам .gitignore виден как новый файл, его тоже нужно закоммитить.

Проверь себя6 вопросов
1 Что выведет cat train.log | grep -i error | wc -l?
2 Сколько байт выведет printf 'карп🐉' | wc -c (4 русские буквы и эмодзи дракона, кодировка UTF-8)?
3 Вы изменили model.py и выполнили git add model.py, но ещё не коммитили. Где сейчас изменение?
4 Что из этого стоит добавить в .gitignore ML-проекта?Можно выбрать несколько ответов
5 Команда echo "loss=2.1" > log.txt выполнена дважды. Сколько строк в log.txt?
6 Какой командой создать новую ветку study и сразу переключиться на неё?
  • Терминал — основной интерфейс ML-инженера: pwd, ls, cd, mkdir, cat, head, tail, grep.
  • > перезаписывает, >> дописывает, | склеивает команды в конвейер.
  • В UTF-8 латиница занимает 1 байт, кириллица — 2, знак € — 3, эмодзи — 4. Это пригодится на ступени 4, когда дойдём до токенизации.
  • Git: add → commit → push. Постоянно смотрите git status, эксперименты ведите в ветках, а секреты и веса держите в .gitignore.
  • Свои решения храните в ветке study, обновления курса забирайте через git pull и git merge main.

Дальше: ставим Python, создаём виртуальное окружение и запускаем Jupyter. Заодно разберёмся, где взять GPU.