我有一个包含许多子文件夹的文件夹。我想从每个子文件夹中删除所有较小的文件,只留下最大的文件。
例如:
Subfolder1
---------- File 1 ---- 300k
---------- File 2 ---- 299k
---------- File 3 ---- 800k
仅file 3
应保留 800k。如果文件夹只有一个文件,则保留。
该代码有效,但我不能将其放入 for 循环中(用于目录递归):
find . -type f -maxdepth 1 | sort -n -r | tail -n +2 | xargs -I{} rm -v {}
我怎样才能做到这一点?
答案1
~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"
4 directories, 8 files
~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
> {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}'
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
> {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}' | \
> xargs rm -v
'pluto/pluto1/pluto3/zero ed.txt' rimosso
'pluto/pluto2/nozer.txt' rimosso
'pluto/pluto2/zero.txt' rimosso
~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"
4 directories, 5 files
tree
按目录列出,然后按大小降序排列。
awk
代码的第一行跳过了tree
的输出第一行或者带有尾部斜杠的行(即目录)awk
代码的第二行从完整路径构建一个 dirname (for
循环),然后打印完整路径名当且仅当 dirname 在前几行中遇到过一次(即,对于每个目录,它从列出的第二个文件开始打印)
答案2
理由
这是我尝试构建一个可以与之配合使用的命令任何目录和文件名。一般来说,Linux 中的路径(以及文件系统中的名称)可能包含除空字符 ( 0x00
) 和 之外的任何字符/
。有问题的字符可能是“ ”(空格)、任何其他白色字符、
'
、"
、换行符、其他不可打印字符。因此,重要的是:
- 放弃用其他字符替换某些字符的工具(例如,许多
ls
将打印内容替换?
为不可打印内容的实现); - 将所有名称作为以空字符结尾的字符串传递(选择可以解析它们的工具);
- 正确引用。
我受到了以下讨论的启发另一个答案。
实际命令
测试版本,它只会删除ls
将被删除的文件:
find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r ls -l
ls
是的,尽管我刚才说过,我还是在这里使用它。这是因为ls
输出没有被进一步解析。我使用它只是为了显示结果。如果你碰巧有目录或文件的名称中有麻烦的字符,那么你将观察到它的行为,ls
这应该会让你相信永不解析ls
(除非你知道这样做绝对安全)。但麻烦的名字仍会一路传递下去ls
,这就是重点。
了解测试版本(请参阅下面的解释)在你让工作版本之前尝试一下(略低于)删除您的文件。请记住,我只是互联网上的一个普通人。
工作版本,它将删除您的文件:
find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r rm
解释
这是分成多行的测试版本(尽管它仍然是一行bash
;注意我使用这个技巧内联评论):
find -type d -exec `# Find all directories under (and including) the current one.` \
sh -c ' `# In every directory separately...` \
find "$0" -maxdepth 1 -mindepth 1 -type f -exec `# ...find all files,...` \
stat --printf "%s %n\0" \{\} + | # ...get their sizes and names,...
sort -znr | # ...sort by size...
tail -zn +2' `# ...and discard the "biggest" entry.` \
{} \
\; | # (All the directories have been processed).
cut -zf 2- -d " " | # Then extract filenames...
xargs -0r ls -l # ...and ls them (rm in the working version).
所用技术及克服障碍:
- 解析字符串的工具被告知要使用以空字符结尾的字符串:
stat --printf "…\0"
;sort -z
,,;tail -z
cut -z
xargs -0 …
;find -print0
(在这个例子中不需要,但一般情况下很常见,因此我还是提到它)。
sh -c '…'
是在内部使用管道的方式find -exec
。find -type d -exec sh -c 'find "{}" …
将中断包含"
;的目录名,find -type d -exec sh -c 'find "$0" … ' {} \;
但工作正常。{}
内部find
语句中的 被转义 (\{\}
) 以防止外部find
替换它们。cut
可以紧随其后tail
,它将cut
每个目录运行一个。将其放置在外部find
使单个可以cut
一次完成所有切割。- 该
-r
选项xargs
可防止ls
(rm
在工作版本中)在没有输入时运行xargs
。