使用 Python 同步文件夹

同步文件夹的工具有很多,下面是我用 Python 写的一个小脚本,兼容 Windows 和 Linux,算是重复发明轮子,不过也当是练习,呵呵。用法很简单,如下:

python syncdir.py source_dir target_dir

作用是将文件夹 source_dir 中的文件同步到文件夹 target_dir 中,同步的过程遵循以下规则:

  1. 如果文件 f1 在 source_dir 中存在,且不在 target_dir 中,则将 f1 拷到 target_dir 中;
  2. 如果文件 f1 在 source_dir 与 target_dir 中都存在,但最后修改时间不一样或文件大小不一样,则将 f1 从 source_dir 拷到 target_dir 中,覆盖 target_dir 中的原文件。

可以看到,这个规则非常简单,而且是单向的,即只保证同步之后 source_dir 中的文件在 target_dir 中保持一致,但 target_dir 中独有的文件不会反向同步到 source_dir 中。

程序代码如下:

# -*- coding: utf-8 -*-
# https://oldj.net/

u"""
同步两个文件夹

用法:

python syncdir.py source_dir target_dir

执行后,source_dir 中的文件将被同步到 target_dir 中
这个同步是单向的,即只将 source_dir 中更新或新增的文件拷到 target_dir 中,
如果某个文件在 source_dir 中不存在而在 target_dir 中存在,本程序不会删除那个文件,
也不会将其拷贝到 source_dir 中

判断文件是否更新的方法是比较文件最后修改时间以及文件大小是否一致
"""

import os
import sys
import shutil

def errExit(msg):
	print "-" * 50
	print "ERROR:"
	print msg
	sys.exit(1)

def main(source_dir, target_dir):
	print "synchronize '%s' >> '%s'..." % (source_dir, target_dir)
	print "=" * 50
	sync_file_count = 0
	sync_file_size = 0

	for root, dirs, files in os.walk(source_dir):
		relative_path = root.replace(source_dir, "")
		if len(relative_path) > 0 and relative_path[0] in ("/", ""):
			relative_path = relative_path[1:]
		dist_path = os.path.join(target_dir, relative_path)

		if os.path.isdir(dist_path) == False:
			os.makedirs(dist_path)

		last_copy_folder = ""
		for fn0 in files:
			fn = os.path.join(root, fn0)
			fn2 = os.path.join(dist_path, fn0)
			is_copy = False
			if os.path.isfile(fn2) == False:
				is_copy = True
			else:
				statinfo = os.stat(fn)
				statinfo2 = os.stat(fn2)
				is_copy = (
						round(statinfo.st_mtime, 3) != round(statinfo2.st_mtime, 3) 
						or statinfo.st_size != statinfo2.st_size
					)

			if is_copy:
				if dist_path != last_copy_folder:
					print "[ %s ]" % dist_path
					last_copy_folder = dist_path
				print "copying '%s' ..." % fn0
				shutil.copy2(fn, fn2)
				sync_file_count += 1
				sync_file_size += os.stat(fn).st_size

	if sync_file_count > 0:
		print "-" * 50
	print "%d files synchronized!" % sync_file_count
	if sync_file_size > 0:
		print "%d bytes." % sync_file_size
	print "done!"

if __name__ == "__main__":
	if len(sys.argv) != 3:
		if "-h" in sys.argv or "--help" in sys.argv:
			print __doc__
			sys.exit(1)
		errExit(u"invalid arguments!")
	source_dir, target_dir = sys.argv[1:]
	if os.path.isdir(source_dir) == False:
		errExit(u"'%s' is not a folder!" % source_dir)
	elif os.path.isdir(target_dir) == False:
		errExit(u"'%s' is not a folder!" % target_dir)

	main(source_dir, target_dir)

目前这个版本只支持本机不同文件夹下的同步,不支持远程网络同步。这段脚本的编写过程中,我发现其中有几点需要注意。

  1. 在 Windows 下,os.path.join 的第一个参数如果以 : 结尾,得到的结果可能不是我们想要的。比如:os.path.join("D:", "tmp"),我们可能会预期得到 D:\tmp ,但实际得到的是 D:tmp ,所以这样的情况下需要使用 os.sep.join("D:", "tmp")
  2. os.path.join 的第二个参数如果以“/”开头(Windows 下如果以“/”或“\”开头),则第一个参数将被忽略。比如 os.path.join("tmp", "/test") 得到的将是 /test
  3. shutil.copy2 可以将文件拷贝到指定地方,它不仅拷贝文件内容,同时还会把文件的创建时间、最后修改时间等信息一起拷过去。
  4. os.stat(filename).st_mtime 可以取到指定文件的最后修改时间,这个时间是一个浮点小数。用上面的 shutil.copy2 拷贝的文件虽然能保留文件的最后修改时间,但有时由于浮点精度的问题,可能在小数的最后一两位会有误差。因此,一次同步过后,两个相同文件的 st_mtime 有可能会有微小的不一致,所以,脚本中我用了四舍五入,只保留文件最后修改时间小数点后的三位(即精确到微秒)。

当然,这个脚本有点小长,如果需要的话可以精简得小一点,不过仅仅压缩代码行数而不提升程序效率貌似意义不大,所以暂时先这样用着,将来有需要时继续研究。

分类:编程标签:Python
说吴
森林大火

相关文章:

评论:

楚吟风

从cb看到《森林大火》追过来看到这篇。Windows的目录路径形如"c:windows",磁盘路径"c:"没有斜杠。我一般检测下路径末尾是否有斜杠,没有则加上。已经订阅了您的博客。感谢分享,很受用。

dayigu

与 rsync -av 类似?

oldj

对,不过 rsync 更强大,功能也更多一些。

发表评论: