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 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 — это два действия в одной команде:
- Перемещение в другую папку:
$ mv file.txt /var/data/ - Переименование (если 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: не спрашивать, не выводить ошибки если файл не существует
Опции:
# С 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 правила, которые могут сэкономить карьеру:
Реальный кейс: историческое падение 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).
Реальные 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