scp и rsync: копирование файлов через SSH
scp существует с 1995 года. Это первый инструмент, который вы выучите для копирования файлов. И вы будете пользоваться им последний раз через пару недель — потому что есть rsync, и rsync лучше во всём.
В этом уроке: почему scp устарел, и почему rsync — must-have инструмент DE-инженера. rsync делает: incremental sync (только diff передаётся), resume прерванных передач, compression on the wire, mirroring двух директорий, --dry-run чтобы посмотреть что будет до выполнения. Это инструмент номер 1 для backup, deployment, и data sync.
scp — legacy
# Файл с локальной машины на сервер
scp local-file.txt [email protected]:/path/
# С сервера на локальную
scp [email protected]:/path/file.csv ./
# Директорию рекурсивно
scp -r /local/dir [email protected]:/remote/dir/
# С нестандартным портом
scp -P 2222 file.txt [email protected]:/
# С конкретным ключом
scp -i ~/.ssh/key file.txt [email protected]:/
Чем плох scp:
OpenSSH project официально считает scp deprecated с 2019
scp работает, его все знают, на любом сервере он есть. Но для production DE-задач — используйте rsync.
rsync — KING для DE
rsync (remote sync) — разработан в 1996 году Andrew Tridgell. Алгоритм: вместо передачи всего файла, передаются только отличия (delta). Если файл уже частично есть на destination — передаётся только новая часть.
# Базовый синтаксис
rsync OPTIONS src dst
# Локально: копировать директорию
rsync -avh /src/dir/ /dst/dir/
# На remote (через ssh автоматически)
rsync -avh /src/dir/ [email protected]:/dst/dir/
# С remote на local
rsync -avh [email protected]:/src/dir/ /dst/dir/
-avh — три ключевых флага:
Запомни эти три — 80% всех use cases
Trailing slash — главный трюк
Это ловушка номер один. rsync ведёт себя по-разному в зависимости от того, есть ли / в конце source-пути.
# src БЕЗ слеша — копирует src как папку В dst
rsync -avh /home/levo/data /backup/
# Результат: /backup/data/...
# src СО СЛЕШОМ — копирует СОДЕРЖИМОЕ src В dst
rsync -avh /home/levo/data/ /backup/
# Результат: /backup/file1, /backup/file2, ... (содержимое data)
Часто люди забывают про слеш и копируют не туда. Запомни:
data— копирует папку как папку.data/— копирует содержимое папки.
В destination trailing slash значения не имеет. Только в source.
# Все эти эквивалентны
rsync -avh /src/dir/ /dst/dir/
rsync -avh /src/dir/ /dst/dir—dry-run — preview перед действием
ВСЕГДА запускайте rsync с -n (или --dry-run) первый раз — увидите, что rsync планирует делать, без реальных изменений:
# Preview
rsync -avhn /src/ /dst/
# Видите список файлов и итог: "would transfer X files, Y bytes"
# Если всё ок — запускаете без -n
rsync -avh /src/ /dst/
Особенно важно с --delete — если что-то не так с путями, можно случайно удалить кучу файлов.
# ВСЕГДА сначала --dry-run при --delete
rsync -avhn --delete /src/ /backup/
# Смотрите список deleting (что rsync удалит) и transferring (что скопирует)
# Если ок — без -n
rsync -avh --delete /src/ /backup/
—partial и -P — resume
Прервалось копирование на 50% файла? Просто запустите ту же команду — rsync продолжит с того же места:
# Скачать 100GB файл
rsync -avh -P [email protected]:/data/huge.tar.gz ./
# Прервалось. Запустите ту же команду
rsync -avh -P [email protected]:/data/huge.tar.gz ./
# rsync видит частичный файл, продолжает с того же места
-P = --partial + --progress. partial сохраняет недокачанные файлы.
—delete — двусторонний sync
По умолчанию rsync только добавляет файлы в destination. Чтобы destination был точной копией source — нужен --delete:
# Без --delete
# Если файл удалили в source — он остаётся в dst
rsync -avh /src/ /backup/
# С --delete — mirroring
# Файлы, которых нет в src, удаляются из dst
rsync -avh --delete /src/ /backup/
--delete крайне полезен, но опасен:
# СЦЕНАРИЙ КАТАСТРОФЫ
rsync -avh --delete /tmp/empty/ /backup/
# Если /tmp/empty/ пустая — rsync удалит ВСЁ из /backup/
ВСЕГДА с -n сначала. Часто полезно --delete-after (удалять после копирования, не до):
rsync -avh --delete-after /src/ /backup/
—exclude — игнорировать паттерны
# Исключить логи и __pycache__
rsync -avh --exclude='*.log' --exclude='__pycache__' /src/ /dst/
# Из файла
rsync -avh --exclude-from=/path/to/exclude.txt /src/ /dst/
# Файл exclude.txt:
# *.log
# *.tmp
# __pycache__
# .git/
# node_modules/
DE-сценарий: бэкап code-репозитория без node_modules и pycache:
rsync -avh \
--exclude='node_modules' \
--exclude='__pycache__' \
--exclude='.git' \
--exclude='*.pyc' \
/home/levo/project/ \
backup-server:/backups/project/
Стандартные DE-комбинации флагов
# Базовая копия (development)
rsync -avh src/ dst/
# Production — с progress, partial, и compression
rsync -avhP -z src/ user@remote:/dst/
# Mirror (master-slave) c удалением старых
rsync -avh --delete src/ user@remote:/dst/
# Incremental backup ежедневно
rsync -avh -P --delete /data/ backup@nas:/backup/2026-05-13/
# Огромный файл по медленной сети
rsync -avhP --bwlimit=10M big-file.tar.gz remote:/dst/
# --bwlimit=10M — не больше 10 MB/s
rsync через ssh с config
rsync под капотом использует ssh для remote-копирования. Уважает ~/.ssh/config:
Host backup
HostName backup.company.com
User levo
IdentityFile ~/.ssh/work_key
# Теперь
rsync -avh /data/ backup:/storage/
# ssh-конфиг подхватился, идёт через ssh с правильным ключом
С нестандартным портом или флагами ssh:
rsync -avh -e "ssh -p 2222 -i ~/.ssh/special_key" /src/ user@remote:/dst/
DE-сценарии
1. Nightly backup data lake
#!/bin/bash
set -euo pipefail
# Бэкап data lake на NAS
DATE=$(date +%Y-%m-%d)
SRC="/data/lake/"
DST="backup@nas:/backups/lake/$DATE/"
LATEST="backup@nas:/backups/lake/latest/"
# --link-dest = hardlinks для не-изменённых файлов (incremental backup)
rsync -avh -P \
--link-dest="$LATEST" \
--exclude='*.tmp' \
--exclude='_temp/' \
"$SRC" "$DST"
# Обновить symlink latest
ssh nas "ln -sfn /backups/lake/$DATE /backups/lake/latest"
--link-dest — magic для incremental backup: файлы, которые не изменились с последнего бэкапа, hardlink на существующие. Каждый daily backup занимает только размер изменений.
2. Deploy code на сервер
#!/bin/bash
set -euo pipefail
LOCAL="/home/levo/etl-project/"
REMOTE="airflow:/opt/etl-project/"
# Preview
echo "Preview:"
rsync -avhn --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='.env' \
"$LOCAL" "$REMOTE"
read -p "Continue? (y/n): " yn
[ "$yn" != "y" ] && exit 1
# Apply
rsync -avh --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='.env' \
"$LOCAL" "$REMOTE"
# Restart Airflow scheduler
ssh airflow "sudo systemctl restart airflow-scheduler"
3. Скачать data dump
# Партнёр кладёт раз в день data dump на свой FTP
# Прерывистая сеть — rsync с resume
rsync -avhP \
--partial-dir=.rsync-partial \
[email protected]:/exports/daily/ \
/local/imports/daily/
# --partial-dir = специальная директория для прерванных файлов
# Они не «появляются» в основной директории недокаченными
4. Migrate между двумя кластерами
# Старый Hadoop кластер -> новый кластер
rsync -avhP -z --delete \
/hadoop-old/data/ \
user@new-cluster:/hadoop-new/data/
# С ограничением полосы — не убить сетку (приоритет — production трафик)
rsync -avhP --bwlimit=50M \
/hadoop-old/data/ \
user@new-cluster:/hadoop-new/data/
5. Two-way sync через rsync (не идеально)
rsync — это одностороннее копирование. Для двустороннего sync нужны другие инструменты (Unison, Syncthing). Но можно симулировать:
# Direction 1: A -> B
rsync -avh --update A/ B/
# Direction 2: B -> A (only newer)
rsync -avh --update B/ A/
--update копирует только если source файл новее destination. Это «грубый» two-way sync — конфликты не разрешаются красиво. Для серьёзного two-way — Unison.
Performance tips
# Compression: -z (полезно для slow сети, не нужно для local)
rsync -avhz /src/ remote:/dst/
# Whole-file (не delta) — для local SSD быстрее, чем delta
rsync -avh -W /src/ /dst/
# -W = --whole-file. Делает rsync как scp, но с другими фишками
# Inplace updates — update файла «на месте», не пересоздавая
rsync -avh --inplace /src/ /dst/
# Полезно для огромных файлов где нужен только partial update
Попробуй сам
# 1. Базовое копирование локально
mkdir /tmp/src /tmp/dst
echo "file1" > /tmp/src/file1.txt
echo "file2" > /tmp/src/file2.txt
mkdir /tmp/src/subdir
echo "file3" > /tmp/src/subdir/file3.txt
rsync -avh /tmp/src/ /tmp/dst/
ls /tmp/dst/
# Содержимое src/
# 2. Trailing slash effect
rm -rf /tmp/dst
mkdir /tmp/dst
rsync -avh /tmp/src /tmp/dst/
ls /tmp/dst/
# /tmp/dst/src/...
# 3. --dry-run
echo "extra" > /tmp/dst/extra.txt
rsync -avhn --delete /tmp/src/ /tmp/dst/
# Покажет: deleting extra.txt
# 4. Реально удалить
rsync -avh --delete /tmp/src/ /tmp/dst/
ls /tmp/dst/
# extra.txt пропал
# 5. Exclude
echo "log content" > /tmp/src/debug.log
rsync -avh --exclude='*.log' /tmp/src/ /tmp/dst/
ls /tmp/dst/
# debug.log нет
# 6. Прерви и продолжи (на удалённом сервере)
# Можно симулировать с большим файлом
dd if=/dev/zero of=/tmp/src/big.bin bs=1M count=100
rsync -avhP /tmp/src/big.bin /tmp/dst/
# Прерви через Ctrl-C посередине
rsync -avhP /tmp/src/big.bin /tmp/dst/
# Должен продолжить
Cross-link: предыдущий урок 02 — ssh, на котором работает rsync. Следующий урок 04 — diagnose сетевых проблем. Модуль 17 — bash-скрипты для backup-пайплайнов.