rsync 同步代码,我一个斜杠没写对,目标目录差点被清空
上周半夜,我要把一台测试机上的代码同步到一台华为云 ECS 上。rsync 我用了好几年了,命令随手就敲了:
rsync -avz --delete /data/www/myapp root@10.0.0.5:/data/www/myapp
回车,等输出刷完,感觉没什么不对。到目标机器上一看,傻眼了:/data/www/myapp 下面多了一层 myapp,变成了 /data/www/myapp/myapp。服务读的还是老目录,等于这次同步白干了。
更吓人的是后面的事。发现目录套错之后,我第一反应是把命令“纠正”一下再跑一遍,结果手一抖又把源和目标写反了方向——幸好那次没加 --delete,不然线上目录能被清空一半。这两次失误凑在一起,让我把 rsync 这两个坑刻进脑子里了。
斜杠到底差在哪
rsync 对源目录末尾有没有斜杠,处理方式是完全不同的两套逻辑:
# 写法 A:源目录末尾带斜杠
rsync -av /data/www/myapp/ root@10.0.0.5:/data/www/
# 结果:myapp 目录里的内容,同步到 /data/www/ 下面
# 写法 B:源目录末尾不带斜杠
rsync -av /data/www/myapp root@10.0.0.5:/data/www/
# 结果:myapp 这个目录本身,连同里面的内容,一起同步到 /data/www/ 下面
等等,这里容易看晕,换个更直白的说法:
/data/www/myapp/(带斜杠)= 同步目录里的东西/data/www/myapp(不带斜杠)= 同步这个目录本身
我那天敲的是不带斜杠的源,目标又写了 /data/www/myapp,rsync 就把整个 myapp 目录塞进了目标的 myapp 里面,于是多了一层。命令本身没报错,退出码也是 0,不仔细看输出根本发现不了。
# 同步完在目标机器上数一层,别急着走
ls /data/www/myapp | head
ls /data/www/myapp/myapp 2>/dev/null && echo "套了一层,同步错了"
–delete 是个双刃剑
--delete 的意思是:目标目录里有、源目录里没有的文件,删掉。听着合理,实际用的时候非常危险,因为它只认“源里有没有”,不认“这个文件重不重要”。
我吃过一次小亏:源目录里的 .env 被我顺手加进了 .gitignore,同步时用了 --exclude 又加了 --delete,结果目标机器上的 .env 被当成“源里没有的文件”删掉了。服务第二天启动直接报错,查了半天才发现是配置文件没了。
从那之后,我给 --delete 定了三条规矩:
第一,第一次对一个新目标用 --delete,先加 --dry-run 看一遍它到底要删什么:
rsync -avz --delete --dry-run /data/www/myapp/ root@10.0.0.5:/data/www/myapp/
输出里带 deleting 的行就是它准备删的,逐行看一遍,没误伤再去掉 --dry-run 真跑。
第二,重要目录永远配合 --backup 和 --backup-dir,删掉的文件先挪到备份目录,而不是直接消失:
rsync -avz --delete --backup --backup-dir=/data/backup/rsync-$(date +%Y%m%d) \
/data/www/myapp/ root@10.0.0.5:/data/www/myapp/
这样就算删错了,文件还在备份目录里躺着,能捞回来。
第三,.env、上传目录、日志目录这种目标机器上独有的东西,写进 --exclude,并且每次同步都从同一个脚本里跑,不要手敲:
rsync -avz --delete \
--exclude '.env' \
--exclude 'uploads/' \
--exclude '*.log' \
/data/www/myapp/ root@10.0.0.5:/data/www/myapp/
手敲命令最容易漏参数,写成脚本固定下来,错一次改脚本,以后都不会再错。
我现在每次同步前的固定动作
吃过两次亏之后,我现在同步代码只跑一个固定脚本,两步雷打不动:
#!/bin/bash
set -euo pipefail
SRC="/data/www/myapp/"
DST="root@10.0.0.5:/data/www/myapp/"
BACKUP_DIR="/data/backup/rsync-$(date +%Y%m%d)"
# 第一步:dry-run 看一遍要删的文件
rsync -avz --delete --dry-run \
--exclude '.env' --exclude 'uploads/' --exclude '*.log' \
"$SRC" "$DST" | grep -E '^deleting' || true
echo "上面是要删除的文件,确认没问题后按回车继续,Ctrl+C 取消"
read -r
# 第二步:正式同步,删掉的文件进备份目录
rsync -avz --delete --backup --backup-dir="$BACKUP_DIR" \
--exclude '.env' --exclude 'uploads/' --exclude '*.log' \
"$SRC" "$DST"
set -euo pipefail 是必须的,不然哪一步失败了脚本还会往下跑。grep -E '^deleting' || true 后面那个 || true 也别省,不然 grep 没找到匹配就直接退出了。
还有一个细节:同步之前先把目标机器上的服务停掉。rsync 不是原子操作,大文件同步到一半,服务去读一个写了一半的文件,报错的样子千奇百怪,查起来特别费时间。
最后再说一次
rsync -a 是归档模式,保留权限、时间戳、软链接,日常同步代码基本都用它;-z 是压缩,公网同步加上能省不少流量。
斜杠的事再说一次,因为它真的太容易错了:源目录末尾带斜杠,同步的是里面的内容;不带斜杠,同步的是目录本身。每次敲命令之前,盯着那个斜杠看一眼,能省掉半夜爬起来修目录的麻烦。
- 点赞
- 收藏
- 关注作者
评论(0)