Remove files excluding files-list in file in shell - 从一个目录中删除另一个目录中不存在的文件

Remove files excluding files-list in file in shell - delete files from one directory that don't also exist in another

我有文件和文件:

./aaa
./bbb
./c/ccc
./d/ddd

我有另一个包含相同文件和其他文件的目录。

./aaa
./bbb
./c/ccc
./d/ddd
./to-remove-1
./c/to-rem-ove-2

我需要删除所有不在第一个列表中的文件。

P.S。实际上第一个列表是由命令 find /some/dir/ -type f > somefile 生成的。所以我们有另一个目录。但我正在寻找一个文件。

在 Linux(使用 GNU 实用程序)

cd "/other/dir/"
# Consider using -xtype f to also include *symlinks* to files.
find . -type f -print0 |
  grep -Fxvz -f <(cd "/some/dir" && find . -type f) |
    xargs -0 echo rm

开BSD/OSX

cd "/other/dir/"
find . -type f |
  grep -Fxv -f <(cd "/some/dir" && find . -type f) |
    tr '\n' '[=11=]' | xargs -0 echo rm

POSIX 兼容变体 - 较慢

cd "/other/dir/"
find . -type f |
 grep -Fxv -f <(cd "/some/dir" && find . -type f) |
   xargs -I {} echo rm {}

以上解决方案执行干运行;删除 echo 以执行实际删除。
此外,不是在 grep 命令中使用静态文件 someFile 作为 -f someFile,而是使用动态创建参考文件列表的进程替换:-f <(cd "/some/dir" && find . -type f)bash,而不是 sh,才能完成这项工作。
请注意,type -f 仅匹配 regular 文件,而不匹配 symlinks 文件。 GNU Find 提供 -xtype -f 以匹配后者。

  • find . -type f 列出当前目录子树中的所有(常规)文件;
    在 Linux 上,-print0 输出每个路径以 NUL(零字节,0x0)而不是换行符结尾。
  • grep -Fxv 排除 (-v) 所有字面 (-F) 和完全 (-x) 与 [=50 的输出中的一行匹配的输入行=] 创建参考文件列表 (-f <(...)) - 换句话说:它仅输出参考列表中不存在的那些输入行。
    在 Linux 上,额外的 -z (--null-data) 选项将输入按 NUL 字节而不是换行符分成记录 - 假设输入是 NUL 分隔的。
  • xargs ... rm 将通过标准输入传递的输入路径转换为命令行参数以传递给 rm
    • 在 Linux 和 BSD 解决方案中,这通常只会导致 一个 rm 调用(尽可能多的路径适合单个命令行被传递给 rm), 所有 输入路径作为参数传递给
    • 在 POSIX 解决方案中,rm 必须在每个路径 中调用一次

  • 处理路径中嵌入的空格@user000001 致敬
    • Linux解决方案可以原则上处理所有路径中嵌入的空格,特别是包括 newlines,这虽然很少见,但却是可能的。
      • 它通过从开始到结束在管道中传递以 NUL 分隔的路径来实现这一点。
      • 也就是说,在手头的案例中,不支持嵌入的换行符,因为传递给 Grep 的引用列表 -f 必须 基于行 才能按预期工作。即使存在选项 --null-data,GNU Grep 也要求传递给 -f 的文件中的搜索词是 newline 分隔的 - NUL 分隔不起作用。
    • BSD 和 POSIX 解决方案可以从根本上 只处理嵌入的空格和制表符.
      • BSD 解决方案使用 tr '\n' '[=40=]' 将所有换行符替换为 NUL,当 xargs 调用 rm 时,结合 xargs -0 将每个输入行保留为自己的参数.
        • 警告:BSD Grep 有一个-z 选项,但其用途非常不同。 BSD Grep no 等同于 GNU Grep 的 -z (--null-data) 选项,使用后者是保留 embedded 输入中的换行符。
      • POSIX 解决方案使用 xargs -I {} rm {} 将每个输入行视为一个 单个 参数传递给 rm - 缺点是rm 然后必须为每一行(路径)调用一次
        使用 POSIX-only 功能,您不能一次传递 多个 带有嵌入空格或制表符的参数,除非您用引号将每个标记括起来,但这也有其自身的挑战。

简答

要比较的目录是 AB,"extra" 个文件已从 B 中删除:

$ (cd A && find .) > tmp.txt
$ cd B && find . >> ../tmp.txt
$ sort ../tmp.txt | uniq -u | xargs rm
$ rm ../tmp.txt

一个班轮

$ (cd B && { (cd ../A && find .) && (find .) } | sort | uniq -u | xargs rm)

这避免了使用命令分组来使用临时文件。请注意,如果您的文件名中有空格,这将不起作用。请参阅下面的 "Caveats"。

说明

使用 uniqfindsortxargs

uniq -u 将从文件中删除所有重复的行。例如,我们可以将您的目录结构减少到以下,使用 findsort:

.
.
./c
./c
./c/ccc
./c/ccc
./c/to-rem-ove-2
./d
./d
./d/ddd
./d/ddd
./to-remove-1

有了这个,uniq -u 给我们:

./c/to-rem-ove-2
./to-remove-1

您可以将其通过管道传送到 xargs 并使用 rm 删除文件。例如... | uniq -u | xargs rm.

分步分解

  1. 我们有以下目录结构:

    $ tree .
    .
    ├── A
    │   ├── c
    │   │   └── ccc
    │   └── d
    │       └── ddd
    └── B
        ├── c
        │   ├── ccc
        │   └── to-rem-ove-2
        ├── d
        │   └── ddd
        └── to-remove-1
    
  2. 我们可以使用 find 命令列出所有目录。

    $ find .
    .
    ./B
    ./B/to-remove-1
    ./B/c
    ./B/c/ccc
    ./B/c/to-rem-ove-2
    ./B/d
    ./B/d/ddd
    ./A
    ./A/c
    ./A/c/ccc
    ./A/d
    ./A/d/ddd
    

    我们不想让 uniq 的前导目录,所以我们将 cd 到 运行 find 之前的每个目录,并将所有文件的列表保存到临时文件,tmp.txt.

    $ (cd A && find .) > tmp.txt
    $ (cd B && find .) >> tmp.txt
    
  3. 由于 uniq -u 对排序的文件进行操作(重复的行必须 彼此相邻),我们必须使用 sort.

    tmp.txt 进行排序
    $ sort tmp.txt | uniq -u
    ./c/to-rem-ove-2
    ./to-remove-1
    
  4. 我们现在可以使用 xargsB 中删除 "extra" 个文件。

    $ cd B
    $ sort ../tmp.txt | uniq -u | xargs rm
    

    文件现已消失:

    $ find .
    .
    ./c
    ./c/ccc
    ./d
    ./d/ddd
    

注意事项

  1. 根据 ,您可以使用 xargs -I {} rm {} 而不是普通的 xargs rm 以确保该命令不执行 如果文件名中有空格,那就错了。
  2. 再次使用 "vanilla" xargs rm 不会删除目录,即使 尽管它们列在 find 命令的输出中。如果你有一个 B 下的目录 e 不在 A 中(因此应该是 删除),您将收到类似

    的错误
    rm: cannot remove ‘./e’: Is a directory
    

    如果你想保留这些目录,你可以忽略 错误。如果你需要删除它们,你可以使用 rm -r rm。如果您这样做,最好将它与 sort -r 结合使用,以便在必要时先删除目录中的文件。如果您不进行此更改,它不会更改功能,但可能会在不应该发布的情况下发布 "errors"。

包含所有这些更改的整个命令是

(cd B && { (cd ../A && find .) && (find .) } | 
 sort -r | uniq -u | xargs -I {} rm -r {})
comm <(sort file1) <(sort file2) -13 | xargs -r rm