
Ich habe einen Ordner mit vielen Unterordnern. Ich möchte alle kleineren Dateien aus jedem Unterordner entfernen und nur die größte Datei übrig lassen.
Zum Beispiel:
Subfolder1
---------- File 1 ---- 300k
---------- File 2 ---- 299k
---------- File 3 ---- 800k
Nur file 3
die mit 800k sollte bleiben. Wenn der Ordner nur eine Datei hat, bleibt diese bestehen.
Dieser Code funktioniert, aber ich kann ihn nicht in eine For-Schleife einfügen (für Verzeichnis-Rekursion):
find . -type f -maxdepth 1 | sort -n -r | tail -n +2 | xargs -I{} rm -v {}
Wie kann ich das machen?
Antwort1
~$ 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
Listen nach Verzeichnis und dann nach absteigender Größe.
awk
Die erste Codezeile von überspringttree
die Ausgabe der ersten Zeile vonoderZeilen mit abschließenden Schrägstrichen (z. B. Verzeichnisse)awk
Die zweite Codezeile erstellt einen Verzeichnisnamen aus dem vollständigen Pfad (for
Schleife) und druckt dann den vollständigen Pfadnamen aus, wenn der Verzeichnisname einmal in den vorherigen Zeilen gefunden wurde (d. h., sie druckt für jedes Verzeichnis ab der zweiten aufgelisteten Datei).
Antwort2
Rechtfertigung
Dies ist mein Versuch, einen Befehl zu erstellen, der funktioniert mitbeliebigVerzeichnis- und Dateinamen. Im Allgemeinen dürfen Pfade in Linux (und Namen in Dateisystemen) alle Zeichen außer Null ( 0x00
) und enthalten /
. Problematische Zeichen können " " (Leerzeichen), andere Leerzeichen,
'
, "
, Zeilenumbrüche und andere nicht druckbare Zeichen sein. Daher ist es wichtig:
ls
Verzichten Sie auf Tools, die einige Zeichen durch andere ersetzen (z. B. drucken viele Implementierungen?
für nicht druckbare Zeichen).- Übergeben Sie alle Namen als nullterminierte Zeichenfolgen (wählen Sie Tools aus, die sie analysieren können).
- richtig zitieren.
Inspiriert wurde ich durch die Diskussion unterdiese andere Antwort.
Tatsächliche Befehle
In der Testversion werden nur ls
die folgenden Dateien entfernt:
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
Ja, ich verwende ls
hier trotz allem, was ich gerade gesagt habe. Das liegt daran, dass ls
die Ausgabe nicht weiter analysiert wird. Ich verwende es nur, um das Ergebnis anzuzeigen. Wenn Sie Verzeichnisse oder Dateien mit problematischen Zeichen in ihren Namen haben, werden Sie das Verhalten von beobachten, ls
das Sie überzeugen sollte,nie analysierenls
(es sei denn, Sie sind sich absolut sicher, dass Sie damit auf der sicheren Seite sind.) Trotzdem werden die problematischen Namen bis hierher weitergegeben, ls
und das ist der Punkt.
Verstehen Sie die Testversion(Erklärungen siehe unten)und probieren Sie es aus, bevor Sie die funktionierende Version(knapp unter)Entfernen Sie Ihre Dateien.Denken Sie daran, ich bin nur ein beliebiger Typ im Internet.
Die funktionierende Version entfernt Ihre Dateien:
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
Erläuterung
Hier ist die Testversion, aufgeteilt in mehrere Zeilen (obwohl es immer noch eine Zeile ist bash
; beachten Sie, dass ichdieser Trickzu Inline-Kommentaren):
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).
Verwendete Techniken, überwundene Hindernisse:
- Tools, die Zeichenfolgen analysieren, werden angewiesen, mit nullterminierten Zeichenfolgen zu arbeiten:
stat --printf "…\0"
;sort -z
,tail -z
,cut -z
;xargs -0 …
;find -print0
(in diesem Beispiel nicht erforderlich, aber im Allgemeinen sehr gebräuchlich, deshalb erwähne ich es trotzdem).
sh -c '…'
ist die Möglichkeit, Rohre im Inneren zu verwendenfind -exec
.find -type d -exec sh -c 'find "{}" …
wird bei Verzeichnisnamen, die enthalten, unterbrochen"
;find -type d -exec sh -c 'find "$0" … ' {} \;
funktioniert einwandfrei.{}
in der innerenfind
Anweisung werden maskiert (\{\}
), um ein Ersetzen durch die äußere Anweisung zu verhindernfind
.cut
könnte unmittelbar folgentail
, es würde einescut
pro Verzeichnis ausgeführt. Wenn es außerhalb des äußeren platziert wird,find
führt ein einzelnescut
alle Schnitte auf einmal aus.- Die
-r
Optionxargs
verhindert , dassls
(rm
in der funktionierenden Version) ausgeführt wird, wenn keine Eingabe für vorliegtxargs
.