rsync 在操作同步的时候如果源地址为目录的话是有操作区别的。

具体表现在:

源地址如果是目录的时候,如果是以 / 为结尾的则将会同步源文件夹和目标文件夹内的内容!

例如执行 rsync ./test/ user@host:/target/subdir 时将会同步 ./test 文件夹内的内容至 user@host:/target/subdir 内,而如果传递的参数是 ./test 的话就是同步到 user@host:/target/subdir/test

基于以上背景在上周一次上传文件时因为携带了 --delete 参数造成了服务器 /root 目录清空的事故,庆幸的是没有什么重要的项目文件在此目录,但是也对服务器环境造成了不可逆的影响。

总结

  • 在使用 rsync 命令时注意目录操作相关特性并仔细检查源地址和目标地址
  • 在正式执行 rsync 操作前先携带 --dry-run 参数进行预执行操作获取将要影响的文件列表
  • 非必要不进行 --delete 操作,实在需要用的话使用 --delete-after 参数给自己一个后悔的时间

后续

为避免再出错写了一个 rsync 脚本替代原生 rsync 命令,在每次执行前必须先执行携带 --dry-run 参数的命令确认一下将要影响的文件。

代码如下,使用PHP实现:

PHP
#!/usr/bin/env php
<?php

# 初始化变量
$work_dir = trim(`pwd`);
$source = "";
$target = "";
$remaining_index = 1;
$debug = in_array('-v', $argv) || in_array('--verbose', $argv);
$to_index = 0;
$remaining_argv = $argv;
$remaining_argv[0] = '';

# 工具函数
$prepare_path = fn($path) => is_dir($path) ? preg_replace("#(.+?)/?$#", "$1/", $path) : $path;
$now = fn() => date('Y-m-d H:i:s');
$log = fn($msg, $level = 'info', $newline = true) => $debug ? print("[" . $now() . "] [$level] $msg" . ($newline ? "\n" : '')) : null;
$output = fn($msg, $newline = true) => $debug ? $log($msg, newline: $newline) : print("$msg" . ($newline ? "\n" : ''));

# 读取源目录或文件和目标位置
if (in_array('to', $argv) || in_array('>', $argv)) {
  $to_index = array_search('to', $argv) ?: array_search('>', $argv);
  $log("to 索引位置: $to_index");
  if ($to_index > 1) {
    $source = $argv[$to_index - 1];
    $target = $argv[$to_index + 1];
  } else {
    $output("参数错误,\"to\" 必须在源文件或目录和目标位置之间!");
    exit(1);
  }
  $remaining_argv[$to_index] = '';
  $remaining_argv[$to_index + 1] = '';
  $remaining_argv[$to_index - 1] = '';
} elseif (str_contains($argv[1] ?? "", '>')) {
  $sources = explode('>', $argv[1]);
  $source = $sources[0];
  $target = $sources[1];
  $remaining_argv[1] = '';
} else {
  $source = $argv[1] ?? '';
  $target = $argv[2] ?? '';
  $remaining_argv[1] = '';
  $remaining_argv[2] = '';
}
if (!$target || !$source) {
  echo `/usr/bin/rsync --help`;
  $output("参数错误,请输入源文件或目录和目标位置!");
  exit(1);
}


# 源文件检查
if (!realpath($source)) {
  $output("Source File/Directory ($source) Not Exist!");
  exit(1);
}

# 准备好源目录或文件和目标位置字符
$source = $prepare_path(realpath($source));

# start sync
$log("开始同步 $source$target");

# 准备命令行
$remaining_argv = array_filter($remaining_argv, fn($v) => !!$v &&
  !preg_match('#^(-(v|-verbose|i|-itemize-changes|n|-dry-run|r|-recursive))$#', $v)
);
$cmd = "/usr/bin/rsync $source $target --itemize-changes " . implode(' ', $remaining_argv);
$debug && $cmd .= ' --verbose';
is_dir($source) && $cmd .= ' --recursive';
$output("预执行命令:" . $cmd);

$pipe = popen("$cmd --dry-run", 'r');

$dry_run_outputs = [];
while (!feof($pipe)) {
  $line = fgets($pipe);
  $output($line, false);
  if (preg_match("#^(>|\.|cd|\*)#",$line)){
    $dry_run_outputs[] = $line;
  }
}
pclose($pipe);
if (count(array_filter($dry_run_outputs)) === 0) {
  $output("源目录或文件中没有需要同步的内容,退出!");
  exit(0);
}

# 询问是否继续执行
$output("注意:此操作是为目录到目录或者文件到文件的同步操作!如:/foo/faa 同步至 /fbb/fcc 则意为两个文件夹中的内容同步!");
$answer = readline("执行以上操作? [y/N] ");
if (strtolower($answer) !== 'y') {
  $output("已取消!");
  exit(1);
}

$pipe = popen("$cmd", 'r');

while (!feof($pipe)) {
  $line = fgets($pipe);
  $output($line, false);
}
展开
0
希望看到您的想法,请您发表评论x