rsync --delete --files-from=list / dest/无法删除不需要的文件

32

正如标题中所示,我试图将一个文件夹与一系列文件同步。我希望这个命令会删除dest/中不在列表中的所有文件,但它没有做到。

所以我搜索了一下,现在知道rsync不能做到这一点。

但我需要它,你知道任何方法吗?

PS:该列表是由python脚本创建的,因此你的解决方案可能使用一些python代码。

编辑,让我们具体一些:

列表看起来像这样:

/home/max/Musik/Coldplay/Parachutes/Trouble.mp3
/home/max/Musik/Coldplay/Parachutes/Yellow.mp3
/home/max/Musik/Coldplay/A Rush of Blood to the Head/Warning Sign.mp3
/home/max/Musik/Coldplay/A Rush of B-Sides to Your Head/Help Is Around the Corner.mp3
/home/max/Musik/Coldplay/B-Sides (disc 3)/Bigger Stronger.mp3

以及像这样的命令:

rsync --delete --files-from=/tmp/list / /home/max/Desktop/foobar/

这个方法是有效的,但如果我删除一行,它在 foobar/ 目录中不会被删除。

编辑2:

rsync -r --include-from=/tmp/list --exclude=* --delete-excluded / /home/max/Desktop/foobar/

这个既不适用于 `<input>` 也不适用于 `<textarea>` 元素。


顺便提一下:rsync版本为3.0.6,协议版本为30。抱歉我忘了。 - dAnjou
4
我最讨厌rsync的一件事情,就是它缺乏对你所要求的精确支持。很好的文章。 - Felipe Alvarez
直到现在的2022年,rsync仍然不支持这个功能:))我仍然面临着同样的问题,但是我必须使用rsync同步许多文件和扩展名,所以我不能使用已接受评论中的--include-from解决方案。 - Trung Nguyen
8个回答

24

也许你可以使用包含模式的列表来实现这个目标,然后使用--delete-excluded(正如其名称所示)?类似于:

rsync -r --include-from=<patternlistfile> --exclude=* --delete-excluded / dest/

如果文件名可能包含通配符字符 (*, ?[),则您可能需要修改 Python 以转义它们:

re.sub("([[*?])", r"\\\1", "abc[def*ghi?klm")

编辑:基于模式的匹配与--files-from略有不同,因为rsync不会递归进入与排除模式匹配的目录,这是出于效率的考虑。因此,如果您的文件位于/some/dir/some/other/dir中,则您的模式文件需要如下所示:

/some/
/some/dir/
/some/dir/file1
/some/dir/file2
/some/other/
/some/other/dir/
/some/other/dir/file3
...

或者,如果所有文件都在同一个目录中,您可以稍微改写命令:

rsync -r --include-from=<patternlistfile> --exclude=* --delete-excluded /some/dir/ dest/

然后您的模式变为:

/file1
/file2

编辑:仔细思考后,您可以使用一个模式包含所有目录:

/**/

但是这样你最终会得到整个目录树 dest/,这可能不是你想要的。但将其与-m(删除空目录)结合使用应该可以解决这个问题 - 因此命令最终变成:

rsync -m -r --delete-excluded --include-from=<patternfile> --exclude=* / dest/

还有模式文件:

/**/
/some/dir/file1
/some/other/dir/file3

谢谢你,但是你的命令要求使用 -d 或 -r,但都不起作用。 - dAnjou
3
这些文件是否在子目录中?如果是,那么包括该目录(及其上级目录)在内的模式列表中需要有它们,否则rsync甚至不会递归到这些文件。 - SimonJ
我认为此时在你的系统上先测试一下你的命令是值得的 :P - dAnjou
3
这里的操作正常(除了第一次漏掉了-r选项,这是我重新输入的教训)。我希望您能查看手册并根据自己的情况调整命令,而不是盲目地复制+粘贴。;) - SimonJ
1
实际上,你可能不需要这样做 - 这取决于你想如何处理空目录。接下来会有另一个编辑... - SimonJ
显示剩余5条评论

13

这不是完全的解决方案,但来到这里的人可能会发现这个有用:自rsync 3.1.0以来,有一个--delete-missing-args参数,它在使用--files-from同步两个目录时删除目标目录中的文件。您需要在/tmp/list中指定要复制的文件以及要删除的文件:

rsync --delete-missing-args --files-from=/tmp/list /source/dir /destination/dir

请参阅手册以获取更多详细信息。


4
看起来很有前途,但我误解了这个选项的作用。它只会在目标位置上删除那些在 --files-from 列表中列出的文件,但却找不到源文件的那些文件。 - mivk
这不正是所需的吗? - Omid

10

正如你所解释的,该命令

rsync -r --delete --files-from=$FILELIST user@server:/ $DEST/

当从$FILELIST中移除一个条目时,不会删除目标中的内容。一个简单的解决方案是改用以下方法。
mkdir -p $DEST
rm -rf $TEMP
rsync -r --link-dest=$DEST --files-from=$FILELIST user@server:/ $TEMP/
rm -r $DEST
mv $TEMP $DEST

这段话是关于rsync的使用说明。该命令指示rsync使用空目标。已经存在于link-dest-directory中的文件会被本地硬链接而不是复制。最后,旧目标会被新目标替换。第一个mkdir会创建一个空的$DEST(如果$DEST不存在),以防止rsync出错。(假定$变量携带到相应文件或目录的完整路径。)硬链接会产生一些小的开销,但您无需处理复杂的包含/排除策略。

有趣的解决方案!可惜在将本地同步到服务器时没有这样的技巧。 - PlasmaBinturong

4
受m4t启发,但使用rsync进行清理。
rsync -r --link-dest=$dest --files-from=filelist.txt user@server:$source/ $temp
rsync -ra --delete --link-dest=$temp $temp/ $dest

在您的代码之前需要执行 'rm -rf $TEMP',否则如果$temp中有不需要的文件,它们最终会出现在$dest中。 - kakyo
我认为这里有一个错别字,但不确定具体位置。 - William Entriken

1

显式构建 --exclude-from=... 似乎是同步文件列表的唯一方法。

stdin = subprocess.PIPE
other_params.append("--exclude-from=-") #from stdin 

p = subprocess.Popen( 'rsync -e ssh -zthvcr --compress-level=9 --delete'.split() + other_params + [src, dst], stdin =  PIPE)

if relative_files_list != None:
    #hack: listing of excluded files seems the only way to delete unwanted files at destination
    files = set(map(norm_fn, relative_files_list)) #make hash table, for huge lists
    for path, ds, fs in os.walk(src):
        for f in fs:
            rel_path_f = norm_fn(os.path.relpath(os.path.join(path, f), src))
            if rel_path_f not in files:
                #print 'excluding', rel_path_f.replace('\\', '/')
                p.stdin.write(rel_path_f + '\n')
    p.stdin.close()
assert 0 == p.wait()

0

我知道这个问题很久以前就被问过了,但我对答案不满意。

以下是我解决这个问题的方法,假设播放列表是由mpd创建的:

#!/bin/bash                                                                 

playlist_path="/home/cpbills/.config/mpd/playlists"
playlist="${playlist_path}/${1}.m3u"
music_src="/home/cpbills/files/music"
music_dst="/mnt/sdcard/music/"

if [[ -e "$playlist" ]]; then
  # Remove old files
  find "$music_dst" -type f | while read file; do
    name="$(echo "$file" | sed -e "s!^$music_dst!!")"
    if ! grep -qF "$name" "$playlist"; then
      rm "$file"
    fi
  done

  # Remove empty directories
  find "$music_dst" -type d -exec rmdir {} \; 2>/dev/null

  rsync -vu \
      --inplace \
      --files-from="$playlist" \
      "$music_src" "$music_dst"
else
  printf "%s does not exist\n" "$playlist" 1>&2
  exit 1
fi

0
根据131和m4t的答案,我采取了以下方法:
1. mv $dest $dest2 2. mkdir $dest 3. rsync(包括--files-from等参数...)--link-dest=$dest2 $dest2 $dest 4. rm -rf $dest2
所有操作几乎是免费的,除了文件系统的记录外,不需要复制或临时空间。
然后,您可以从$source运行您通常的rsync命令到$dest,最终您将得到与files-from中的内容完全相同的结果。

-1

rsync 是用于保持目录同步等其他有用功能的理想工具。如果您在源文件上拥有完全相同的副本,并且想要删除目标文件夹中的文件,则可以从源文件夹中删除它们,然后使用 rsync --delete 选项也将从目标文件夹中删除它们。

但是,如果您只想删除任意列表中的文件,我建议您使用SSH来完成:

ssh user@remote.host.com rm /path/to/file1 /path/to/file2

这将在远程主机上执行rm命令。

使用Python,您可以:

import subprocess
FileList = ['/path/to/file1', '/path/to/file2']
subprocess.call(['ssh', 'dAnjou@my.server.com', 'rm'] + FileList)

~享受


5
误解。 我没有要删除的文件列表,我有一个要复制的文件列表。我想删除那些不在列表上的文件。但还是谢谢你的回答。 - dAnjou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接