我有文件:
./aaa
./bbb
./c/ccc
./d/ddd
我有另一个目录,其中包含相同的文件和一些其他目录。
./aaa
./bbb
./c/ccc
./d/ddd
./to-remove-1
./c/to-rem-ove-2
我需要删除第一个列表中的所有文件。
附言实际上第一个列表是由命令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' ' ' | 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
以执行实际删除。
此外,不使用静态文件someFile
作为grep
命令中的-f someFile
,而是使用动态创建参考文件列表的进程替换:-f <(cd "/some/dir" && find . -type f)
;bash
,而不是sh
,是完成这项工作所必需的。
请注意,type -f
仅匹配常规文件,而不匹配文件的符号链接。GNU Find 提供了-xtype -f
也匹配后者。
find . -type f
列出当前目录子树中的所有(常规)文件;
在 Linux 上,-print0
输出以 NUL(零字节,0x0
)而不是换行符结尾的每条路径。grep -Fxv
排除(-v
)所有输入行,这些输入行字面上(-F
)和完全(-x
)与创建参考文件列表(-f <(...)
)的过程替换输出中的一行匹配 - 换句话说:它只输出那些在引用列表中不存在的输入行。
在 Linux 上,附加-z
(--null-data
) 选项通过 NUL 字节而不是换行符将输入分解为记录 - 假设输入是 NUL 分隔的。xargs ... rm
将通过 stdin 传递的输入路径转换为命令行参数以传递给rm
。- 在 Linux 和 BSD 解决方案中,这通常只会导致一次
rm
调用(将单个命令行上容纳的路径传递给rm
),所有输入路径都作为参数传递给该路径。 - 在 POSIX 解决方案中,每个路径必须调用一次
rm
。
- 在 Linux 和 BSD 解决方案中,这通常只会导致一次
- 处理路径中嵌入的空格:为他的输入@user000001帽子的尖端
- Linux 解决方案原则上可以处理嵌入在路径中的所有空格,特别是包括换行符,虽然这种情况很少见,但也是可能的。
- 它通过从头到尾通过管道传递 NUL 分隔的路径来实现这一点。
- 也就是说,在手头的情况下,不支持嵌入式换行符,因为传递给 Grep
-f
的引用列表必须基于行才能按预期工作。即使存在选项--null-data
,GNU Grep 也要求传递给-f
文件中的搜索词以换行符分隔 - NUL 分隔不起作用。
- BSD和POSIX解决方案基本上只能处理嵌入式空格和制表符。
- BSD 解决方案使用
tr 'n' ' '
将所有换行符替换为 DOL,这与 NUL 结合使用xargs -0
在调用xargs
时保留每个输入行作为自己的参数rm
。- 警告:BSD Grep也有一个
-z
选项,但它的目的非常不同。BSD Grep没有等同于 GNU Grep 的-z
(--null-data
) 选项,使用后者是在输入中保留嵌入式换行符的唯一方法。
- 警告:BSD Grep也有一个
- POSIX 解决方案使用
xargs -I {} rm {}
将每个输入行视为要传递给rm
的单个参数 - 缺点是必须为每行(路径)调用一次rm
。
使用仅限 POSIX 的功能,您不能一次传递带有嵌入空格或制表符的多个参数,除非您用引号将每个标记括起来,但这会带来自己的挑战。
- BSD 解决方案使用
- Linux 解决方案原则上可以处理嵌入在路径中的所有空格,特别是包括换行符,虽然这种情况很少见,但也是可能的。
简答题
要比较的目录是A
和B
,"额外"文件将从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)
这样可以避免使用使用命令分组的临时文件。请注意,如果您的文件名中包含空格,这将不起作用。请参阅下面的"注意事项"。
解释
使用uniq
、find
、sort
和xargs
。
uniq -u
将从文件中删除所有重复的行。例如,我们可以使用find
和sort
将您的目录结构简化为以下内容:
.
.
./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
.
分步分解
我们有以下目录结构:
$ tree . . ├── A │ ├── c │ │ └── ccc │ └── d │ └── ddd └── B ├── c │ ├── ccc │ └── to-rem-ove-2 ├── d │ └── ddd └── to-remove-1
我们可以使用
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
的前导目录,所以我们将 在运行find
之前cd
到每个目录中,并将所有文件的列表保存到临时文件中,tmp.txt
.$ (cd A && find .) > tmp.txt $ (cd B && find .) >> tmp.txt
由于
uniq -u
对排序的文件进行操作(重复的行必须 彼此相邻出现),我们必须使用sort
对tmp.txt
进行排序。$ sort tmp.txt | uniq -u ./c/to-rem-ove-2 ./to-remove-1
我们现在可以使用
xargs
从B
中删除"额外"文件 .$ cd B $ sort ../tmp.txt | uniq -u | xargs rm
文件现在消失了:
$ find . . ./c ./c/ccc ./d ./d/ddd
警告
- 根据mklement0的评论,您可以使用
xargs -I {} rm {}
而不是普通xargs rm
,以确保命令不起作用 如果文件名中有空格,那就错了。 再次使用"香草"
xargs rm
不会删除目录,即使 尽管它们列在find
命令的输出中。如果你有一个 目录e
不在A
B
下(因此应该是 已删除),您将收到类似rm: cannot remove ‘./e’: Is a directory
如果要保留这些目录,可以忽略 错误。如果需要删除它们,可以使用
rm -r
的rm
.如果要执行此操作,最好将其与sort -r
结合使用,以便在必要时在目录之前删除目录中的文件。如果不进行此更改,它不会更改功能,但可能会在不应该发布"错误"时发布。
包含所有这些更改的整个命令是
(cd B && { (cd ../A && find .) && (find .) } |
sort -r | uniq -u | xargs -I {} rm -r {})
comm <(sort file1) <(sort file2) -13 | xargs -r rm