文件同步是在一个位置添加,更改或删除文件,并在另一位置添加,更改或删除相同文件的过程。 本文介绍了cp,tar和rsync这三个实用程序,它们可以帮助实现UNIX文件的同步。 尽管cp和tar命令的同步能力有限,但是rsync为您提供了全部选项。 但是,这三个位置都有。
使用cp命令进行裸拷贝
尽管cp命令不是真正的同步命令,但它可能是将文件从一个位置复制到另一位置的最简单方法。 对于单个文件副本,cp显然非常有效: $ cp source destination
。
要复制整个目录结构,可以使用-r
选项将整个目录结构从一个位置递归复制到另一个位置: $ cp -r source destination
。 执行这种类型的复制仅以递归方式复制文件和目录。 有关文件的权限,所有权和其他元信息不会复制到目标位置。 您可以使用-p
选项为复制的每个文件和目录保留所有权,权限和时间: $ cp -pr source destination
。
使用cp是复制文件的最简单,最公认的方法,但是cp效率低下,并且如果不使用像NFS这样的远程文件系统解决方案,就不可能将目录复制到远程系统。
使用tar
tar实用程序是磁带存档的缩写,最初是作为一种有效的方法创建的,用于将目录结构(包括文件和文件元数据)转换为二进制流,该二进制流可以在备份过程中写入磁带。
通常,您使用tar创建一个.tar文件,其中包含所需的目录: $ tar cf mydir.tar ./mydir
。 c
选项告诉tar创建一个新的归档文件, f
选项告诉下一个参数作为要创建的归档文件的名称(mydir.tar)。 其余所有参数都用作要包含在存档中的文件或目录。 tar命令自动遍历目录结构,因此,如果您将目录指定为要包含的文件之一,则tar将包括该目录以及该目录包含的所有其他文件和目录。
tar要注意的重要方面之一是,您提供的路径名被视为绝对路径。 也就是说,如果您指定tar的完整目录位置,例如/ etc目录: $ tar cf etc.tar /etc
然后,默认情况下,tar将文件提取到其绝对位置。 例如,如果提取了相同的存档: $ tar xf etc.tar
。 然后,将在/ etc目录中重新创建文件和目录结构。 这可能是破坏性的(因为您可能会覆盖/ etc中要保留的文件)。 有两种解决方法。 第一种是使用GNU tar,它支持--strip-path
选项,以从提取的路径中删除元素。
一个简单的替代方法是转到父目录,然后使用相对路径(请参见清单1)。
清单1.更改父目录并使用相对路径
$ cd /
$ tar cf etc.tar ./etc
提取存档文件后,将再次在其相对位置重新创建文件。 您可以使用此技巧来帮助同步目录。 由于tar创建目录结构的字节流,因此可以将tar与管道结合使用,将文件从一个位置复制到另一个位置: $ tar cf - ./etc |( cd /backup; tar xf - )
。 每种情况下的“-”表示tar应该使用标准输出(写时)或标准输入(读时)。 括号有效地在子shell中执行语句。 在管道之前查看先前的代码时,将在标准输出上创建文件的字节流。 在管道之后,转到另一个目录,然后再次从标准输入中提取字节流。
要确保保留文件的所有权和权限,可以使用p
选项保留每个文件和目录的元数据: $ tar cfp - ./etc |( cd /backup; tar xfp - )
。
一旦有了基本结构,就可以执行更复杂的操作。 例如,您只能复制自特定时间以来已更改的文件: $ tar cf - --newer 20090101 ./etc |(cd /backup; tar xf - )
。 此代码创建自2009年1月1日以来发生更改的文件的副本。
通过将操作与rsh或ssh结合使用,文件也可以同步到远程主机: $ tar cfp - ./etc |(ssh user@host -- tar xfp -)
。 以这种方式使用ssh和tar是在远程主机上创建本地计算机备份的好方法。 但是,还有更有效的信息同步方法。
使用rsync进行智能同步
先前用于同步文件的替代方法的主要问题是它们复制每个文件(以及相关的目录结构)。 如果您要创建信息的新副本,这很好,但如果要在一个目录和另一个目录之间同步信息,则效率很低。
考虑到您可能拥有一个使用100GB包含10,000个文件的目录。 如果使用cp或tar更改了一个大小约为10MB的文件,则必须重新复制整个100GB的文件。 在备份情况下,复制该信息量过多。 您希望备份尽可能快速有效地完成。 显然,如果您知道哪个文件已更改,则只能复制该文件,但您并不总是知道此信息。
甚至对tar使用--newer
选项也受到限制,因为您必须确切知道上一次修改的时间。 rsync工具通过比较目录结构和单个文件并确定源目录和目标目录之间的差异位于何处来解决此问题。 一旦清楚了哪些文件和目录已更改,它将仅将那些项目复制到目标位置。 更进一步,rsync将对单个文件使用类似的算法,并且仅复制文件中已更改的部分。
以最简单的形式,您可以使用rsync从一个目录同步到新目录,如下所示: $ rsync -rab
。 这将创建一个新目录b,其中包含目录a中目录结构的副本。 -r
选项告诉rsync递归到目录中并复制整个结构。 但是,如果目标目录已经存在,则将在目标目录b中创建一个新目录a,其中包含文件的副本。 这可能会有一些不幸的副作用。 例如,如果您要将多个目录复制到一个备份目录,清单2将完成您想要的。
清单2.将多个目录复制到备份目录
$ mkdir backup
$ rsync dira backup
$ rsync dirb backup
清单2创建了一个目录backup / dira,其中包含原始dira的副本。 它还创建一个目录backup / dirb,其中包含原始dirb的副本。 以下内容有所不同: $ rsync dira backup/dira
。 第一次使用它时,脚本将完成您期望的工作。 但是第二次使用该选项时,rsync将在指定的目标目录内创建目标目录,并创建目录backup / dira / dira。 这不仅不会创建所需的结构,还会使内容加倍(其中一个永远不会同步)。
使用rsync时,可能需要指定一些其他选项。 默认同步不复制文件元数据,而是将某些特殊文件(如链接)视为普通文件。 您要使用的主要选项是:
-
--delete
删除目标目录中源文件中不再存在的文件。 默认模式是仅同步文件更改并创建新文件。 默认情况下,如果文件已在源中删除,则将其忽略。 使用此选项,您可以创建相同的同步。 -
--recursive
递归复制目录和文件。 -
--times
同步每个文件和目录的修改和创建时间。 -
--owner
如果可能的话,保留文件所有权。 -
--group
如果可能的话,保留组的所有权。 -
--links
将符号链接复制为符号链接,而不是复制文件数据并解释源链接。 -
--perms
保留文件权限。 -
--hard-links
保留硬链接(通过在目标上创建硬链接),而不是复制文件内容。
这些选项中的某些选项仅在两个系统具有相同的配置时才有效且可实现。 例如,仅当源计算机和目标计算机为同一用户使用相同的ID时,才能保留文件所有权和组所有权设置。
除了本地副本,rsync还使用ssh执行远程副本。 若要使用,需要在源目录或目标目录之前指定用户名和远程主机。 例如,要以用户身份将目录同步到远程系统,请执行以下操作: $ rsync --recursive dira user@remote:/backup/dirb
。 如果尚未设置无密码ssh连接,则会提示您输入远程密码。 如果有,那么这可能是执行无人值守的过夜备份的有效方法。
相同的用户名/密码组合也可以用作源,从而允许您从远程源复制到本地目录: $ rsync --recursive user@remote:dira dirb
。 当复制到在互联网上的远程系统中, --compress
它是通过网络传输,这比原始字节副本更有效的选项前也将压缩的信息。 当然,复制到远程系统时,如果裸文件包含敏感信息,则可能不希望复制它们。 为此,您需要使用加密。
加密文件以进行同步
使用文件同步解决方案的最常见原因之一是创建文件的精确备份,以便在出现问题时可以复制或重新创建目录结构的元素。
rsync工具非常适合此操作,因为它可以有效地仅复制在两个目录之间更改的文件。 更有用的是,由于rsync可以同步到远程系统,因此您可以使用它来创建自动和远程备份,而不必将文件分别复制到远程系统。
此过程的局限性在于您创建的副本不会被加密。 如果要将文件复制到远程系统,则它可以是计算机上的远程系统,其他人也可以访问(例如,在托管服务上),并且您希望确保即使已访问文件,无法读取。
仅使用rsync,无法加密文件。 也无法利用rsync所使用的算法来仅加密自上次同步以来已更改的文件。
但是,通过将rsync的执行包装到脚本中,可以利用rsync的输出来创建文件的辅助副本,然后对其进行加密。
该脚本的基础是创建原始目录结构的两个副本。 第一个副本是参考副本,其中包含目录结构的精确副本。 这是必需的,以便在再次同步目录时,可以比较源文件和目标文件,并照常确定差异列表。 使用--itemize-changes
选项进行rsync,rsync将创建一个同步期间每个文件发生情况的参考列表。 输出详细说明文件是已更改(还是新文件)还是文件已删除。 您可以在清单3中看到一个示例。
清单3. rsync的逐项更改
.d..t...... t1/a/
*deleting t1/a/3
.d..t...... t1/b/
>f.st...... t1/b/1
>f+++++++++ t1/b/6
以.d.
开头的行.d.
指示新目录或目录更改。 *deleting
行表示源中的文件已被删除。 >f
行表示文件已更改或已创建新文件(使用>f++++++++
)。
通过分析此输出文件,可以确定源目录和目标引用目录之间的更改。 确定更改后,可以在第三个目录中创建原始文件的相同加密版本。 逐项列出的更改仅用于加密(或删除)自上次同步以来已更改的文件。 您不能使用目录的加密版本直接执行同步,因为文件的加密版本将始终与源文件不同。
完整的脚本如清单4所示。
清单4.完整脚本
#!/usr/bin/perl
use warnings;
use strict;
use File::Basename;
use File::Path;
my $source = shift;
my $dest = shift;
my $encdest = shift;
if (!defined($source) || !defined($dest) || !defined($encdest))
{
print "Error: Not enough arguments!\n";
print "Usage: $0 source destination encrypteddest\n";
exit(1);
}
print STDERR "Running rsync between $source and $dest ($encdest)\n";
system("rsync --delete --recursive --times -og --links --perms " .
"--hard-links --itemize-changes $source $dest " .
">/tmp/$$.rsynclog 2>&1");
open(DATA,"/tmp/$$.rsynclog") or die "Couldn't open the rsynclog\n";
my @changedfiles;
my @delfiles;
while(<DATA>)
{
next if (m/sending incremental file list/);
chomp;
last if (length($_) == 0);
my ($changes,$filename) = split;
push @changedfiles,$filename if ($changes =~ m/^>f/);
push @delfiles,$filename if ($changes =~ m/^\*del/);
}
close(DATA);
my $counter = 0;
foreach my $file (@changedfiles)
{
if (-f "$dest/$file")
{
my $sourcename = encode_filename("$dest/$file");
my $destname = encode_filename("$encdest/$file");
my $dirname = dirname("$encdest/$file");
mkpath($dirname);
system(sprintf('cat "%s" |openssl enc -des3 ' .
'-pass file:/var/lib/passphrase -a >"%s"',
$sourcename,$destname));
$counter++;
}
}
my $delcounter = 0;
foreach my $file (@delfiles)
{
unlink("$encdest/$file");
$delcounter++;
}
print STDERR "Finished (changed: $counter, deleted: $delcounter)\n";
unlink("/tmp/$$.rsynclog");
sub encode_filename
{
my ($filename) = @_;
$filename =~ s/ /\\ /g;
$filename =~ s/'/\\'/g;
$filename =~ s/"/\\"/g;
$filename =~ s/\(/\\(/g;
$filename =~ s/\)/\\)/g;
$filename =~ s/&/\\&/g;
$filename =~ s/#/\\#/g;
return($filename);
}
该脚本相对简单,使用起来非常简单。 要运行,请指定源目录,参考文件的目标目录以及文件的加密版本的目标目录: $ rsyncrypt source destination destination.enc
。
脚本的第一部分在源目录和目标目录之间执行基本的rsync,以确定更改(请参见清单5)。 该操作将生成逐项文件(在/ tmp中)以记录更改。
清单5.在源目录和目标目录之间执行基本的rsync
system("rsync --delete --recursive --times -og --links --perms " .
"--hard-links --itemize-changes $source $dest " .
">/tmp/$$.rsynclog 2>&1");
接下来,分析更改列表,并生成已更改和删除的文件的列表(请参见清单6)。
清单6.解析更改列表
while(<DATA>)
{
next if (m/sending incremental file list/);
chomp;
last if (length($_) == 0);
my ($changes,$filename) = split;
push @changedfiles,$filename if ($changes =~ m/^>f/);
push @delfiles,$filename if ($changes =~ m/^\*del/);
}
对于每个已更改的文件,通过读取参考版本并在加密目标目录中创建加密版本来创建加密版本(请参见清单7)。
清单7.为每个已更改的文件创建加密版本
foreach my $file (@changedfiles)
{
if (-f "$dest/$file")
{
my $sourcename = encode_filename("$dest/$file");
my $destname = encode_filename("$encdest/$file");
my $dirname = dirname("$encdest/$file");
mkpath($dirname);
system(sprintf('cat "%s" |openssl enc -des3 ' .
'-pass file:/var/lib/passphrase -a >"%s"',
$sourcename,$destname));
$counter++;
}
}
文件名必须进行编码,因为我们正在使用外壳执行实际的加密。 需要转义一些特殊字符,否则这些特殊字符将由外壳解释。
对于实际的加密,openssl与一个简单的文本文件(在/ var / lib / passphrase中)一起使用,该文件包含将对信息进行编码的密码短语。 您还可以创建或使用专门生成的密钥来执行该操作,或创建要使用的任何其他加密命令。
最后,由于源目录中可能包含已从原始目录中删除的文件,因此所有删除的文件也将从加密目录内容中删除(请参见清单8)。
清单8.从加密的目录内容中删除已删除的文件
foreach my $file (@delfiles)
{
unlink("$encdest/$file");
$delcounter++;
}
该脚本非常有效,唯一的缺点是它需要信息的两个副本(参考目录和加密版本),而不仅仅是一个。 同样,为了简化过程,权限,所有权和时间戳信息不会同步到加密版本,但是添加起来相对容易。 但好处是,由于使用了rsync来生成更改列表,因此需要加密的文件数量大大减少了,并且可以使用相同的优化算法将新的加密版本的文件同步到您的远程主机,仅传输自上次同步以来已更改的加密文件。
摘要
本文介绍了许多用于同步文件的不同方法。 基本的cp并不是真正的同步,但是对于直接复制很有用。 对于真正的同步操作而言,这比较耗时且效率低下。 使用tar,您可以利用时间参考点来仅复制在指定时间段后更改的文件,但是如果更改不太明显或未被这样的全面比较发现,这也有局限性。
rsync工具是正确同步的更好解决方案。 它执行广泛的检查和比较,以真正比较源目录和目标目录,并即使在网络或公共连接上也可以有效地进行同步。 为了安全起见,可以将此功能与加密阶段结合使用,以确保没有正确的密码短语或加密密钥无法读取远程文件。
翻译自: https://www.ibm.com/developerworks/aix/library/au-filesync/index.html