for-Schleife in Ordnern mit \ n Zeichen in ihren Namen

9

Ich habe einige Ordner mit \nCharakter, deren Namen.

zum Beispiel:

$ ls
''$'\n''Test'

Das bezieht sich auf einen Ordner mit einem Testnamen und einer Leerzeile vor seinem Namen.

Wenn ich also einige Skripte wie dieses im übergeordneten Verzeichnis ausführe:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

Es zeigt:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

Weil es zweimal ausgeführt wird, einmal \nund einmal Test, weil der Ordnername zwei Zeilen hat.

Wie kann ich dieses Problem so lösen, dass das Skript \nTestnur einen Ordner kennt ?

Tara S Volpe
quelle
3
Sie müssen die -print0Direktive von find und die -dLeseoption verwenden. Siehe stackoverflow.com/a/40189667/7552
Glenn Jackman
1
@glennjackman Bitte antworten!
Nachtisch
@glennjackman Vielen Dank für Ihre Antwort, aber find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; doneBefehl haben diese Ausgabe rmdir: failed to remove 'Test': No such file or directory.
Tara S Volpe
5
Sie müssen die Variable zitieren:rmdir "$file"
Glenn Jackman
1
@glennjackman Poste dies einfach als Antwort. Es ist eine richtige Lösung und außerdem sind Kommentare nicht der beste Ort dafür. Upvote bereits impliziert :)
Sergiy Kolodyazhnyy

Antworten:

12

Sie haben dort nur einen einzigen Befehl, daher reicht es aus, findmit einem -execFlag-Aufruf aufzurufen rmdir:

find -depth -type d -exec rmdir {} \;

Oder verwenden Sie die -deleteOption wie in find -type d -delete, sie funktioniert jedoch nicht mit nicht leeren Verzeichnissen. Dafür benötigen Sie auch -emptyFlagge. Beachten Sie auch, -deleteimpliziert , -depthso dass übersprungenen sein kann. Eine weitere Alternative, die alles als einen Prozess betrachtet:

find -type d -empty -delete

Wenn das Verzeichnis nicht leer ist, verwenden Sie rm -rf {} \;. Um nur Verzeichnisse mit dem \nDateinamen zu isolieren, können wir das ANSI-C-Zitat von bash $'...'mit -nameopption kombinieren:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly, wir könnten so umgehen:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

Es ist erwähnenswert, dass, wenn Ihr Ziel das Entfernen von Verzeichnissen -deleteist, dies ausreicht. Wenn Sie jedoch einen Befehl für ein Verzeichnis ausführen möchten, -execist dies am besten geeignet.

Siehe auch

Sergiy Kolodyazhnyy
quelle
2
rm -rf Vorsichtig verwenden . ; P Hat nicht findeine haben -deletebtw Option? Es löscht leere Verzeichnisse und wirft Fehler für nicht leere wie rmdir- möglicherweise günstiger.
Nachtisch
3
@dessert Das tut es und ich habe es hinzugefügt, aber -deletenicht leere Verzeichnisse werden nicht entfernt. Daher die sorgfältige Verwendung vonrm -rf
Sergiy Kolodyazhnyy
6
Immer trocken laufen solche findBefehle ohne destruktive Nutzlast (dh entfernen Sie den -deleteoder -exec whatever \;) zu überprüfen , ob die Liste der betroffenen Dateien tatsächlich korrekt ist , und enthält keine Sachen , die nicht gelöscht werden sollte ...
Byte Kommandant
2
Dies ist askubuntu.com, also können wir GNU annehmen find. Verwenden Sie -exec rmdir {} +diese Option, um mehrere Argumente auf einer rmdirBefehlszeile zu stapeln. Und übrigens " aber es funktioniert nicht mit nicht leeren Verzeichnissen. " Gilt rmdirauch für, also ist das eigentlich kein Unterschied zu -delete. Es ist ein Unterschied zu rm -r.
Peter Cordes
2
find -type d -empty -delete( -deleteimpliziert -depth).
Kevin
10

Sie könnten Shell Globs anstelle von verwenden find:

for d in */ ; do 
    rmdir "$d"
done

Der Shell-Glob */entspricht allen Ordnern im aktuellen Verzeichnis. Diese For-Loop-Konstruktion sorgt automatisch für die korrekte Wortteilung.

Beachten Sie, dass abhängig von Ihren Shell-Optionen möglicherweise versteckte Ordner ignoriert werden (Name beginnt mit a.). Dieses Verhalten kann mit dem Befehl so geändert werden, dass alle Dateien für die aktuelle Sitzung übereinstimmen shopt -s dotglob.

Vergessen Sie auch nicht, immer Ihre Variablen anzugeben.

Byte Commander
quelle
die glob nur Dateien / Verzeichnisse im aktuellen Verzeichnis finden, nicht rekursiv wie findtut
ilkkachu
1
Sie haben Recht @ilkkachu. Dies kann geändert werden, indem die Globstar-Shell-Option aktiviert shopt -s globstarund stattdessen ein **/*/Glob verwendet wird.
Byte Commander
6

Beide bisher geschriebenen Antworten werden rmdireinmal pro Verzeichnis aufgerufen , aber da rmdirich mehrere Argumente annehmen kann, frage ich mich: Gibt es keinen effizienteren Weg?

Man könnte es einfach tun

rmdir */

und das ist definitiv der einfachste und effizienteste Weg, aber es kann bei vielen Verzeichnissen einen Fehler auslösen (siehe Was ist die maximale Länge von Befehlszeilenargumenten in gnome-terminal? ). Wenn dieser Ansatz rekursiv funktionieren soll, aktivieren Sie die globstarShell-Option mit shopt -s globstarund verwenden Sie **/*/statt */.

Mit GNU find(und wenn wir nicht nur verwenden wollen -delete) könnten wir es tun

find -depth -type d -exec rmdir {} +

Dadurch wird die Befehlszeile "ähnlich wie die xargsBefehlszeilen" erstellt ( man find). Ersatz -depthfür , -maxdepth 1wenn Sie nicht wollen , es rekursiv zu arbeiten.

Ein dritter und IMO brillanter Weg wird von steeldriver in dieser Antwort erklärt :

printf '%s\0' */ | xargs -0 rmdir

Dies verwendet die eingebaute Shell printf, um eine durch Null getrennte Argumentliste zu erstellen. Diese Liste wird dann weitergeleitet, an xargsdie rmdirgenau so oft wie nötig aufgerufen wird . Sie können dafür sorgen, dass es rekursiv mit shopt -s globstarund **/*/statt */wie oben beschrieben funktioniert .

Dessert
quelle
2
findist rekursiv, die Schleife des OP jedoch nicht. Verwenden Sie, um dieses Verhalten zu replizieren find -maxdepth 1 -type d -exec rmdir {} +. ( -depthist in diesem Fall überflüssig.) Ordentlicher Trick printfzum Emulieren find -print0, das hatte ich vorher noch nicht gesehen.
Peter Cordes
1
Oh! Ich habe die lsund die whileSchleife gesehen und habe übersehen, dass sie von einer Prozessersetzung mit umgeleitet wurden, findanstatt lsin die Schleife zu leiten und sie aus irgendeinem Grund nicht anzuzeigen. Das find *ist wirklich begraben. Ja, es ist rekursiv und schlägt fehl, wenn das Verzeichnis zu viele Verzeichniseinträge enthält, einschließlich Nicht-Verzeichnisse, da es nicht verwendet wird */. find .wäre viel besser, weil findes schneller ist statals die Glob-Expansion von Bash.
Peter Cordes