Learning Platform
Глоссарий Troubleshooting
Урок 06.02 · 20 мин
Начальный
cpmvrmFile Operationstrash-cli

cp, mv, rm: копирование, перемещение, удаление

Три команды, которые Junior DE использует десятки раз в день: cp (copy), mv (move/rename), rm (remove). Простые на вид, но опасные — одной командой можно удалить production-данные. В этом уроке: правильные опции, безопасные паттерны и trash-cli как страховка.


cp: копирование

Базово:

$ cp source.txt dest.txt
$ ls
dest.txt   source.txt

С папками:

$ cp -r src/ dst/
# Скопирует src/ и всё содержимое в dst/

Самые полезные опции:

cp — главные опции
-r / -RRecursive. Обязательно для копирования папок. Без него `cp dir1 dir2` упадёт с 'omitting directory'
-pPreserve mode, ownership, timestamps. Без него копия получает текущий umask и текущее время
-vVerbose. Печатает каждый копируемый файл. Полезно для длинных операций
-iInteractive. Спросить перед перезаписью существующих файлов. Защита от случайностей
-nNo-clobber. НЕ перезаписывать существующие. В отличие от -i, не спрашивает — просто пропускает
-uUpdate. Перезаписать, только если source новее dest. Полезно для синхронизации
-aArchive = -dR --preserve=all. Стандарт для 'сохранить ВСЁ при копировании': mode, ownership, time, links, xattrs
-lHard link вместо копии. Места не занимает, два имени одного файла. Полезно когда копия 1-в-1 не нужна
-sSymlink вместо копии

Самые частые комбинации:

# Обычное копирование папки
$ cp -r src/ dst/

# С сохранением метаданных (для бэкапа)
$ cp -a src/ dst/

# С подтверждением перезаписи (безопасное)
$ cp -i source.txt existing.txt
cp: overwrite 'existing.txt'? n

# Verbose для длинных операций
$ cp -rv ~/data/2026-05 /backup/
'~/data/2026-05/file1.csv' -> '/backup/2026-05/file1.csv'
'~/data/2026-05/file2.csv' -> '/backup/2026-05/file2.csv'
...

cp: подводные камни

Trailing slash имеет значение. Это часто путает.

$ mkdir -p src dst
$ touch src/file.txt

# Без trailing slash на source
$ cp -r src dst
$ ls dst
src/             # Скопировал src ВНУТРЬ dst -> dst/src/

# Со slash на source
$ cp -r src/ dst/
$ ls dst
src/   file.txt  # Тоже скопировал src ВНУТРЬ dst

# Что если хочется СКОПИРОВАТЬ СОДЕРЖИМОЕ src в dst напрямую?
$ cp -r src/. dst/
$ ls dst
file.txt          # Содержимое скопировано НАПРЯМУЮ

Это особенность GNU cp. На BSD/macOS немного по-другому. Запомните идиому: src/. копирует содержимое.

Symlinks по умолчанию НЕ резолвятся.

$ ln -s /etc/passwd ~/link-to-passwd
$ cp ~/link-to-passwd /tmp/copy.txt
$ ls -l /tmp/copy.txt
lrwxrwxrwx 1 user user 11 May 13 14:00 /tmp/copy.txt -> /etc/passwd
# Скопирован symlink, не содержимое!

# Чтобы скопировать содержимое:
$ cp -L ~/link-to-passwd /tmp/copy.txt
$ ls -l /tmp/copy.txt
-rw-r--r-- 1 user user 2847 May 13 14:00 /tmp/copy.txt
# Реальный файл с содержимым passwd

mv: перемещение и переименование

mv — это два действия в одной команде:

  1. Перемещение в другую папку:
    $ mv file.txt /var/data/
  2. Переименование (если same директория):
    $ mv old-name.txt new-name.txt

Под капотом — то же самое: оба случая это rename() syscall, который меняет directory entry. Файл физически не перемещается, если src и dest — на одном устройстве. Просто меняется указатель.

# Быстрое (mv в том же fs)
$ time mv huge-100gb-file.dat /var/data/
real    0m0.012s         # 12 миллисекунд! файл физически не двигался

# Медленное (mv между разными fs — копирование + удаление)
$ time mv huge-100gb-file.dat /mnt/external/
real    5m23.421s        # 5 минут — реально копирование

Если src и dest на разных файловых системах (например, разные диски, или /tmp = tmpfs, / = ext4), mv физически копирует и удаляет. Это намного медленнее.

Опции mv:

$ mv -i source.txt dest.txt    # interactive (спросит перед overwrite)
$ mv -n source.txt dest.txt    # no-clobber (не перезапишет)
$ mv -v source.txt dest.txt    # verbose
$ mv -u source.txt dest.txt    # update (только если src новее)

rm: удаление

Самая опасная команда. Удаляет навсегда (нет «корзины»).

$ rm file.txt
# Файл исчез, ничего не спросит

$ rm -r folder/
# Удалить папку рекурсивно

$ rm -f file.txt
# Force: не спрашивать, не выводить ошибки если файл не существует

Опции:

rm — главные опции и опасность
-r / -RRecursive. Обязательно для папок. БЕЗ ОТКАТА — удаляет всё внутри
-fForce. Не спрашивать, не показывать ошибки. ОПАСНО: одно опечатка и ничего не остановит
-iInteractive. Спрашивает по каждому файлу. Защита от ошибки, особенно с *
-IInteractive ONCE. Спрашивает один раз, если файлов больше 3 (а не по каждому). Компромисс
-vVerbose. Печатает каждый удалённый файл. Очень полезно для аудита
--preserve-rootПО УМОЛЧАНИЮ. Защита от `rm -rf /`. Современный rm откажется удалять корень
# С verbose — видишь, что удаляется
$ rm -rv old-folder/
removed 'old-folder/file1.txt'
removed 'old-folder/file2.txt'
removed directory 'old-folder/subdir'
removed directory 'old-folder'

# Interactive — спрашивает
$ rm -i *.tmp
rm: remove regular file 'a.tmp'? y
rm: remove regular file 'b.tmp'? n     # пропустить

rm: правила безопасности

Junior правила, которые могут сэкономить карьеру:

Правила безопасности rm для Junior DE
1. alias rm='rm -i'Принудительный interactive в .bashrc. Каждое rm спрашивает. Раздражает, но спасает раз в год от катастрофы
2. Сначала lsПеред `rm *` сделать `ls *` — посмотреть, что попадёт под удаление. Перед `rm -rf $VAR` — `echo rm -rf $VAR` чтобы увидеть финальную команду
3. НЕ -rf без причины-rf отключает все защиты. Используйте -r без -f когда возможно. Если файла нет — лучше ошибка, чем тихое удаление
4. pwd перед rm *Команда `rm *` удалит ВСЁ в текущей папке. Сделайте `pwd` — точно ли вы в той папке, что думаете?
5. Кавычки вокруг переменных`rm -rf $DIR/old` при пустой $DIR станет `rm -rf /old`. Кавычки — `rm -rf '$DIR/old'` — спасают от пустых переменных

Реальный кейс: историческое падение SteamOS из-за бага:

# Скрипт от Valve, который чуть не убил home users:
rm -rf "$STEAMROOT/"*
# Если $STEAMROOT пустая (не определена), это становится:
rm -rf /*
# = удалить всё на корне!

Решение — set -u в начале скрипта (ошибка при использовании необъявленной переменной):

#!/usr/bin/env bash
set -euo pipefail     # u — undefined vars fail

rm -rf "$STEAMROOT/"*
# Если STEAMROOT не задан — скрипт упадёт ДО rm

Подробнее в модулях 16-17.


trash-cli: «корзина» для CLI

Установите trash-cli — это аналог корзины для shell:

$ sudo apt install -y trash-cli

# Удалить файл — он попадёт в корзину
$ trash-put old-file.txt

# Восстановить
$ trash-restore

# Показать корзину
$ trash-list
2026-05-13 14:00:00 /home/user/old-file.txt

# Очистить
$ trash-empty

Многие DE алиасят:

alias rm='trash-put'     # rm теперь в корзину
alias rmraw='/usr/bin/rm'  # оригинальный rm как rmraw

Это даёт страховку: случайно удалил — восстановил. На серверах в production, естественно, обычный rm, без корзины.


—preserve опции cp

При копировании можно явно указать, что сохранять:

$ cp --preserve=mode,ownership,timestamps src dst
$ cp --preserve=all src dst              # всё что можно

# Сокращение:
$ cp -p src dst        # mode + ownership + timestamps
$ cp -a src dst        # все + рекурсивно + symlinks

Для бэкапа — cp -a. Для синхронизации между серверами — лучше использовать rsync (модуль 12).

Копирование данных в Docker — bind mounts и COPY в Dockerfile

Реальные DE-сценарии

Бэкап данных:

$ cp -a /var/data/raw/2026-05-13 /backup/raw/
# Сохранение прав, ownership, timestamps

Переименование с timestamp:

$ mv old.log "old-$(date +%Y%m%d-%H%M%S).log"
# old-20260513-140000.log

Создание тестовой копии для эксперимента:

$ cp -r ~/projects/etl ~/projects/etl-experiment
$ cd ~/projects/etl-experiment
# Ломайте здесь, оригинал не пострадает

Чистка старых логов:

# Сначала смотрим, что будет удалено
$ find /var/log/old -mtime +90 -type f -ls

# Потом удаляем
$ find /var/log/old -mtime +90 -type f -delete

Попробуй сам

$ mkdir -p ~/linux-sandbox/lesson-cpmv/{src,dst}
$ cd ~/linux-sandbox/lesson-cpmv

# Создание тестовых файлов
$ touch src/{a,b,c}.txt
$ ls src
a.txt  b.txt  c.txt

# Копирование с verbose
$ cp -v src/a.txt dst/
'src/a.txt' -> 'dst/a.txt'

# Копирование папки полностью
$ cp -rv src dst/all-of-src
'src' -> 'dst/all-of-src'
'src/a.txt' -> 'dst/all-of-src/a.txt'
...

# Различие trailing slash
$ rm -rf dst
$ mkdir dst
$ cp -r src/ dst/
$ ls dst
src/   # вложилось

$ rm -rf dst/*
$ cp -r src/. dst/
$ ls dst
a.txt  b.txt  c.txt   # содержимое напрямую

# mv для переименования
$ mv dst/a.txt dst/aa.txt
$ ls dst

# Безопасное rm с -i
$ rm -ri dst
rm: descend into directory 'dst'? y
rm: remove regular file 'dst/aa.txt'? y
rm: remove regular file 'dst/b.txt'? y
rm: remove regular file 'dst/c.txt'? y
rm: remove directory 'dst'? y

Поставьте trash-cli и попробуйте:

$ sudo apt install -y trash-cli
$ touch /tmp/test-file.txt
$ trash-put /tmp/test-file.txt
$ trash-list
2026-05-13 14:00:00 /tmp/test-file.txt
$ trash-restore

Проверка знанийKnowledge check
Junior пишет скрипт чистки старых данных: `rm -rf $DATA_DIR/old-*`. В production логе появляется: 'rm: cannot remove '/old-*': No such file or directory'. Затем сервис падает: критичные данные удалены. Что случилось?
ОтветAnswer
Переменная $DATA_DIR не была определена (нет export, нет .env, опечатка). Bash подставил пустоту, и команда стала rm -rf /old-*. На production не было файлов по /old-*, но если бы было — они бы удалились. ХУЖЕ: если в DATA_DIR подставилось пустое значение и в команде использовался rm -rf $DATA_DIR/old-*, при пустой переменной это становится rm -rf /old-* — попытка удалить что-то на корне системы. Если бы там были конфиги или другие данные — потеряны. Правильные защиты: (1) set -u в начале скрипта (set -euo pipefail) — bash упадёт при использовании undefined переменной. (2) Кавычки: rm -rf "$DATA_DIR/old-"* — пустая переменная даст error 'No such file', а не операцию на корне. (3) Явная проверка: [[ -n "$DATA_DIR" ]] || { echo 'DATA_DIR not set'; exit 1; }. (4) Использование :? синтаксиса: rm -rf "\${DATA_DIR:?DATA_DIR must be set}/old-"* — выдаст ошибку с сообщением, если переменная пустая. Все production-скрипты должны иметь set -euo pipefail — это базовая защита от категории ошибок.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Что делает `cp -a src dst`?

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

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

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

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