rsync 确实不能优雅地处理“文件重命名”吗?

rsync 确实不能优雅地处理“文件重命名”吗?

使用备份工具时duplicity,我注意到如果我重命名源上的文件,数据将通过网络再次重新发送到目标,这有点悲伤。由于内部duplicity使用librsync,我决定看看rsync.

建筑维基页面状态:

处理重命名
移动/重命名的文件会被检测到,并且不会存储或传输两次。它通常意味着计算文件或其块的校验和。缺少此功能的应用程序可以通过结合来补充hsyncAUR,仅同步重命名。

rsync:处理重命名:否

这是否真的意味着,使用时rsync,无法阻止 10 GB 通过网络重新传输到目的地,如果我在源计算机上重命名/test/10GBfile为?/test/10GBfile_newname

鉴于 的长期流行rsync,是否有更好的处理方式?

答案1

没有rsync跟踪重命名的机制,因为它除了运行时之外不维护状态。如果您在源计算机上重命名/test/10GBfile为,则默认情况下只能看到已删除的和已创建的。/test/10GBfile_newnamersync10GBfile10GBfile_newname

参数--fuzzy( -y) 可能有助于识别目标上10GBfile的潜在数据源10GBfile_newname,从而避免以文件复制为代价进行网络复制。但是,它(主要)只能考虑文件的匹配在同一目录下因此,尽管您的示例可以匹配,但重命名为/test/10GBfileto/test/otherdir/10GBfile_newname却不会。

另请注意,文档 ( man rsync) 建议,如果您想使用,--delete则应使用--delay-updates或 ,--delete-after以便--fuzzy在使用之前不会删除 的潜在匹配项。

例子

# Prepare an uncompressible 100MB file
mkdir -p /tmp/test
dd bs=1M count=100 iflag=fullblock if=/dev/urandom >/tmp/test/file1

# Normal first-time copy
rsync -av --fuzzy --delete-after /tmp/test/ remote:/tmp/test

# Skip copy because unchanged
rsync -av --fuzzy --delete-after /tmp/test/ remote:/tmp/test

# Rename file (per your example)
mv /tmp/test/file1 /tmp/test/file2

# Fast copy because fuzzy match
rsync -av --fuzzy --delete-after /tmp/test/ remote:/tmp/test

添加另外两个-v标志(即rsync -avvv …)以查看正在发生的情况的逐块详细信息。

答案2

--fuzzy已经得到了回答,但还有另一个涉及硬链接的有趣黑客。

第一次转账后

$ rsync -avHP --delete-after ~/family/Photos remotebox:backups

您创建一个硬链接工作目录:

$ cd ~/family
$ cp -rlp Photos Photos-work

然后你可以使用

$ rsync -avHP --delete-after --no-inc-recursive ~/family/Photos ~/family/Photos-work remotebox:backups

将新结构传输到远程。

这里解释了为什么以及如何工作:

https://lincolnloop.com/blog/detecting-file-moves-renames-rsync/

答案3

那一页声称选项补丁--detect-renamed可用于rsync 3.0.9.它有补丁的链接,链接到bugzilla 讨论。我的rsync 3.1.3没有--detect-renamed选择。所提到的讨论正在进行中:

2021-01-15 14:19:12 UTC 时间

此功能请求太旧了,已经失去了相关性,因为 btrfs/zfs/etc 是比 rsync 更优化的备份解决方案。

有些人不同意“失去相关性”,我同意那些不同意的人的观点。

红迪网页面索赔:

btrfs 同步支持此功能,但您需要两端都有 btrfs。

我认为是什么埃拉特拉特提到的。

以下是我现在的解决方法(git我认为对我来说有点过分了)。

我的具体情况:我不经常重命名,我想要同步链接(不仅仅是一对一),而不是备份。因此,我决定创建 shell 命令文件,在其中编写mv命令(在末尾添加新命令),以便由我最初编写的来回运行 rsync 的脚本额外执行。该脚本尚未经过太多测试,运行风险自负,欢迎提出改进意见。我希望 rsync--detect-renamed选项能够尽快投入生产。

接下来的完整脚本很长,因为当同步一侧的 shell 文件大小大于另一侧的 shell 文件时,它会单独处理,并且包括一些检查,因为我不确定(最近才开始使用该脚本)comm程序是否正确找到了唯一的行,因为它据称适用于“排序”(每个手册页)文件。核心是:

# do renames / moves at both ends
cd $remote_path 
bash -c "$(comm --nocheck-order -3 -1 $f_RE $f_LO)"
cd $local_path 
bash -c "$(comm --nocheck-order -3 -2 $f_RE $f_LO)"

# merge files via temp file
(comm --nocheck-order -1 -2 $f_LO $f_RE) > $f_LO.3
(comm --nocheck-order -3 -1 $f_RE $f_LO) >> $f_LO.3
(comm --nocheck-order -3 -2 $f_RE $f_LO) >> $f_LO.3
cp $f_LO.3 $f_RE
mv $f_LO.3 --force $f_LO
rm $f_LO.1 $f_LO.2  

完整的外壳功能:

#!/bin/bash
do_sync(){
    if [ -d $remote_path ]; then 
        
        # ===== workaround for renaming / moving (run manually made commands before rsync) ===== #
    
        # man comm: comm - compare two sorted files line by line
        complex_flag=0 # later set by script to 1 if changes identified from both sync directions
        to_rsync=1 # to run rsync by default
        f_RE=$remote_path/_rename_move.sh
        f_LO=$local_path/_rename_move.sh

        if [ -f $f_RE ]; then
             if [ -f $f_LO ]; then
                # if both exist, -gt greated than, stat --printf="%s" size in bytes
                if [ $(stat --printf="%s" $f_RE) -gt $(stat --printf="%s" $f_LO) ]; then
                    # small file (2nd) is fully contained in the beginning of larger file (maybe test binary mode more efficient)
                    # -1     suppress column 1 (lines unique to FILE1) : man comm
                    if [ -z "$(comm --nocheck-order -3 -1 $f_RE $f_LO)" ]; then
                        # run only additional commands 
                        cd $local_path 
                        bash -c "$(comm --nocheck-order -3 -2 $f_RE $f_LO)"
                        # overwrite small with larger one                     
                        cp $f_RE $f_LO
                    else complex_flag=1; fi
                # remote smaller than local
                elif [ $(stat --printf="%s" $f_RE) -lt $(stat --printf="%s" $f_LO) ]; then
                    # small file (1nd) is fully contained in the beginning of larger file (maybe test binary mode more efficient)
                    if [ -z "$(comm --nocheck-order -3 -2 $f_RE $f_LO)" ]; then
                        # run only additional commands
                        cd $remote_path
                        bash -c "$(comm --nocheck-order -3 -1 $f_RE $f_LO)"
                        # overwrite small with larger one                     
                        cp $f_LO $f_RE
                    else complex_flag=1; fi
                # same size but different contents
                elif [ ! $(sha256sum $f_RE | awk '{ print $1 }') = $(sha256sum $f_LO | awk '{ print $1 }') ]; then
                    complex_flag=1;
                fi
                # nothing to do if files are the same
             # if only remote exists                
             else
                cd $local_path && $f_RE
             fi

             # neither file was found to be part of another as a whole
             # expect changes (moves/renames) from both ends
             if [ $complex_flag -eq 1 ]; then

                # doing echo "$()" removes trailing empty lines compared to for some reason (TODO why?)

                # check that doing symmetrically with appending to local results in same number of lines in a file
                # and selecting matching in both and adding distinct from both too results in same number of lines in a file
                cp $f_RE $f_LO.1 && (comm --nocheck-order -3 -1 $f_RE $f_LO) >> $f_LO.1
                cp $f_LO $f_LO.2 && (comm --nocheck-order -3 -2 $f_RE $f_LO) >> $f_LO.2
                (comm --nocheck-order -1 -2 $f_LO $f_RE) > $f_LO.3
                (comm --nocheck-order -3 -1 $f_RE $f_LO) >> $f_LO.3
                (comm --nocheck-order -3 -2 $f_RE $f_LO) >> $f_LO.3
                counts_1="$(wc $f_LO.1 | awk '{ print $1,$2,$3 }')"
                counts_2="$(wc $f_LO.2 | awk '{ print $1,$2,$3 }')"
                counts_3="$(wc $f_LO.3 | awk '{ print $1,$2,$3 }')"
                # same counts, Ok
                if [ $counts_1 = $counts_2 ] && [ $counts_2 = $counts_3 ]; then
                    cd $remote_path 
                    bash -c "$(comm --nocheck-order -3 -1 $f_RE $f_LO)"
                    cd $local_path 
                    bash -c "$(comm --nocheck-order -3 -2 $f_RE $f_LO)"
                    cp $f_LO.3 $f_RE
                    mv $f_LO.3 --force $f_LO
                    rm $f_LO.1 $f_LO.2
                else
                    echo "========= manual intervention might be needed ==========="
                    echo "Results of analysis of $f_LO & $f_RE via [comm] app has not matched;"
                    echo "renaming/moving not performed;"
                    echo "rsync of $local_path & $remote_path not performed; see differences between files:"
                    echo "$f_LO.1, $f_LO.2, $f_LO.3"
                    echo "========================================================="
                    to_rsync=0
                fi
         fi   
        # if only local exists
        elif [ -f $f_LO ]; then
            cd $remote_path && $f_LO
        fi

        # ===== end of workaround ===== #

        if [ $to_rsync -eq 1 ];then
            rsync $options $local_path/ $remote_path
            rsync $options $remote_path/ $local_path
            rsync $options $local_path/ $remote_path
        fi

        # below is to move old versions away 
        find "$local_path" -path "$local_path/prevs" -prune -o -name '*.bak' -exec mv "{}" "$local_path/prevs" \;
        find "$remote_path" -path "$remote_path/prevs" -prune -o -name '*.bak' -exec mv "{}" "$remote_path/prevs" \;

# previous versions, prune works "more correct", forgot why
#        find $local_path -maxdepth 1 -name '*.bak' -exec mv "{}" $local_path/prevs \;
#        find $remote_path -maxdepth 1 -name '*.bak' -exec mv "{}" $remote_path/prevs \;
    else
        echo $remote_path is not available
    fi
}

# trailing / would prevent proper pruning in find commands
local_path=/home/$(id -un)/Documents
remote_path=/media/$(id -un)/Projects
do_sync

答案在https://serverfault.com/questions/489289/handling-renamed-files-or-directories-in-rsync据我了解,它有一些缺点。

带有硬链接的:大多数系统不支持目录的硬链接,并且不会检查哪一侧已更改名称(可以进行备份,不能进行同步)。

stat filename可以用来检查哪一侧的文件已重命名,但我不知道如何将其与 rsync 合并。

相关内容