
我有一個包含許多子資料夾的資料夾。我想從每個子資料夾中刪除所有較小的文件,只留下最大的文件。
例如:
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
的第二行程式碼從完整路徑(for
循環)建立目錄名,然後列印完整路徑名,前提是在前面的行中遇到過一次目錄名(即,從第二個列出的檔案開始,為每個目錄列印)
答案2
理由
這是我嘗試建立一個可以使用的命令任何目錄和檔名。一般來說,Linux 中的路徑(以及檔案系統中的名稱)可以包含 null ( 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).
使用的技術、克服的障礙:
- 解析字串的工具被告知可以處理以 null 結尾的字串:
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