Bash – rm und der Standard input

Mai 26, 2011 um 12:58 am | Veröffentlicht in Bash, GNU/Linux, Ubuntuusers | 15 Kommentare

Mit einem bestimmten Problem habe ich mich in letzter Zeit herumgeärgert: Ich möchte einen Haufen Dateien deren Namen über den Standardeingabekanal der Bash hereingeflogen kommen mit einem Befehl löschen. Natürlich könnte ich jede einzelne Datei angeben. Diese Methode wird aber sehr aufwändig wenn ich alle Dateien löschen möchte, deren Namen nicht in der Liste auftauchen.

Achtung, dank mehrerer Kommentare habe ich gelesen, dass ls hier die falsche Wahl ist. Eine bessere Alternative scheint find zu sein.

Anwendungsbeispiel

Ich habe mehrere ähnliche Dateien in meinem Directory. Diese werden automatisiert bearbeitet und das Ergebnis jeweils in einer Datei mit dem Zusatz Clean gespeichert. Nun interessieren mich die ursprünglichen Dateien nicht mehr und ich möchte sie ebenso automatisiert löschen. (Nein, ein Ersetzen der Urprungsdateien gleich beim Bearbeiten fällt leider aus technischen Gründen aus.) Ein kurzes ls gibt uns z.B. folgende Liste aus:

taach@prompt:~/test$ ls
Datei 1 Clean.txt
Datei 1.txt
Datei 2 Clean.txt
Datei 2.txt
Datei 3 Clean.txt
Datei 3.txt
Datei 4 Clean.txt
Datei 4.txt
Datei 5 Clean.txt
Datei 5.txt

Zum Glück stellt ls mit --ignore bereits eine Möglichkeit Dateinamen zu ignorieren die bestimmte Muster enthalten:

taach@prompt:~/test$ ls –ignore=*Clean.txt
Datei 1.txt
Datei 2.txt
Datei 3.txt
Datei 4.txt
Datei 5.txt

Diese Variante ist jedoch Case sensitive. Wenn das Muster also in verschiedener Groß-/Kleinschreibung vorliegt, empfiehlt es sich stattdessen grep einzusetzen:

taach@prompt:~/test$ ls | grep -iv Clean
Datei 1.txt
Datei 2.txt
Datei 3.txt
Datei 4.txt
Datei 5.txt

Die Option -i von grep schaltet die Case sensitivity aus und -v negiert die Auswahl. In diesem Fall werden also alle Zeilen entfernt in denen das Muster Clean oder clean vorkommt. An sich könnten wir nun diese Liste rm übergeben. Dafür verwenden wir xargs, welches notwendig ist, dass rm die Liste aus der Standardeingabe auch wirklich übergeben wird. Wenn die Dateinamen jedoch, wie in diesem Fall, Leerzeichen enthalten bringt uns rm diese Fehlermeldung:

taach@prompt:~/test$ ls –ignore=*Clean.txt | xargs rm
rm: Entfernen von „Datei“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „01.txt“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „Datei“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „02.txt“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „Datei“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „03.txt“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „Datei“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „04.txt“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „Datei“ nicht möglich: Datei oder Verzeichnis nicht gefunden
rm: Entfernen von „05.txt“ nicht möglich: Datei oder Verzeichnis nicht gefunden

Das passiert deswegen, weil die Leerzeichen ungeschützt vorliegen und die Bash, bevor die Liste nun endgültig rm übergeben wird, diese als Ende eines Dateinamens interpretiert. Wir müssen also xargs sagen, dass die Leerzeichen maskiert bleiben müssen ('\ ' statt ' '), damit die Bash sie ignoriert. Dazu gibt es die Option -0 welche jegliche Maskierungen aufrecht erhält:

taach@prompt:~/test$ ls –ignore=*Clean.txt | xargs -0 rm
rm: Entfernen von „Datei 01.txt\nDatei 02.txt\nDatei 03.txt\nDatei 04.txt\nDatei 05.txt\n“ nicht möglich: Datei oder Verzeichnis nicht gefunden

Verdammt, was ist denn jetzt schon wieder los? Ah, zwischen den Dateinamen ist plötzlich ein Zeichen aufgetaucht: '\n' Dieses Zeichen, auch Endline genannt, kennzeichnet einen Zeilenumbruch. Da wir xargs gesagt haben, dass alle Maskierungen aufrecht erhalten bleiben sollen, liegt nun auch der Zeilenumbruch als Reintext vor. Und da das Zeichen nun einfacher Text ist wird die ganze lange Wurst wie ein einziger Dateiname behandelt. Nun müssen wir xargs also noch mitteilen, wo die Dateinamen enden. Dazu verwenden die Option -d mit dem Parameter '\n':

taach@prompt:~/test$ ls –ignore=*Clean.txt | xargs -0 -d ‚\n‘ rm

und siehe da alle Dateien, mit Ausnahme derer mit Clean im Namen wurde gelöscht:

taach@prompt:~/test$ ls
Datei 1 Clean.txt
Datei 2 Clean.txt
Datei 3 Clean.txt
Datei 4 Clean.txt
Datei 5 Clean.txt

Das ganze geht natürlich auch so:

taach@prompt:~/test$ rm `ls –ignore=*Clean.txt | xargs -0 -d ‚\n’`

bzw. im Bash-Style:

taach@prompt:~/test$ rm $(ls –ignore=*Clean.txt | xargs -0 -d ‚\n‘)

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

=-=-=-=-=
Powered by Blogilo

15 Kommentare »

RSS feed for comments on this post. TrackBack URI

  1. ggf. hilft jemandem in solch einem Fall auch der „find“-Befehl weiter ->

    z.B.: find ~/test -iname „*Clean.txt“ -exec rm {} \;

    • Stimmt ist wesentlich kürzer. Bin gar nicht auf die Idee gekommen es mit find zu probieren.

    • Hmm, find scheint doch recht interessant zu sein. Muss ich mir näher ansehen.

  2. Das geht doch aber auch wesentlich einfacher.

    rm

    beherrscht nämlich auch Wildcards. Daher würde

    rm Datei\ ?.txt
    

    dasselbe bewirken. Das Leerzeichen muss allerdings escaped werden, da rm sonst versucht „Datei“ und „?.txt“ zu löschen.

    • Dann müssten aber alle Dateien die ich jemals auf diese Art und Weise bearbeite genauso benannt sein. Sie heißen aber eben nicht immer Datei 1.txt, Datei 2.txt usw., sondern auch mal File 1.txt oder so. Ich wollte, dass derselbe Befehl für alle Fälle verwendbar ist und eben nicht jedesmal neu angepasst werden muss.

  3. He,

    ich habe mir sagen lassen, dass es immer gefährlich ist bei solchen Dingen mit dem Befehl ls zu arbeiten. Schau mal hier im Ubuntuusers-Wiki in der Ersten Warnungsbox steht was zu ls in Skripten:
    http://tinyurl.com/3v5x8mh

    Lg Sab.

    • Danke für den Hinweis. Ich werde es mal mit find probieren.

  4. ls für sowas zu benutzen ist böse🙂

    ls -1
    Datei\ 1\ Clean.txt
    Datei\ 1.txt
    Datei\ 2\ Clean.txt
    Datei\ 2.txt
    Datei\ 3\ Clean.txt
    Datei\ 3.txt
    Datei\ 4\ Clean.txt
    Datei\ 4.txt
    Datei\ 5\ Clean.txt
    Datei\ 5.txt

    find . -type f -name ‚* ?.txt‘ -delete

    ls -1
    Datei\ 1\ Clean.txt
    Datei\ 2\ Clean.txt
    Datei\ 3\ Clean.txt
    Datei\ 4\ Clean.txt
    Datei\ 5\ Clean.txt

    • Ja, habe ich auch gerade gelesen. Muss mich mal mit find herumspielen.

  5. Meine 2ct zu Löschen mit „find“:
    * unbedingt in der manpage den Eintrag zu „-print0“ lesen, insbesondere in Kombination mit „xargs -0“
    * für Gedächtnisschwache: „-delete“ statt „-exec rm {} \;“ (Zumindest bei GNU find)

    „ls“ am besten nur zur Ausgabe gen Mensch nutzen. In Skripten ist es eine Katastrophe zu erwarten, dass da nicht noch Escape-Sequenzen mit ausgegeben werden die das autom. Verarbeiten nicht stören.

    http://mywiki.wooledge.org/ParsingLs
    via der AW von lhunath unter
    http://stackoverflow.com/questions/867877/preserve-ls-colouring-after-greping

    • Danke für den Hinweis.

  6. Neben eigenem Humbug hast Du hier ja auch jede Menge Tipps eingesammelt, die es selbst kaum besser machen.
    find -not -name "*Clean.txt" -delete
    ist der beste Weg, und oft brauchbar rm *something, wenn es denn passt.

    Das maskieren von „{}“ ist Humbug, wie der Hinweis -print0 mit xargs zu benutzen – gäbe es kein -delete wäre -execdir rm tatsächlich besser, aber gnu-find kennt eben -delete. Die Daten erst kunstvoll mit abzuschließen, und mit xargs wieder auseinanderzuklamüsern soll wohl demonstrieren, wie viele Tools man kennt, nicht aber einen sinnvollen Umgang damit.

    Ach, Antiqua hat es auch geschrieben, sehe ich jetzt, und Benjamin wäre nahe dran gewesen, wenn er nicht unbedingt xargs hätte erwähnen wollen.

    Und wo Du dabei bist: Schnell auch wieder Backticks abgewöhnen, und statt `cmd` $(cmd) verwenden. Vielen Dank.

    • Man lernt nie aus.

      Die Backticks habe ich erwähnt, da es sich hierbei um die traditionelle UNIX-Schreibweise handelt. Ich persönlich verwende den Bash-Style auch lieber.

      Danke für deinen Beitrag.

  7. […] hier den Originalbeitrag weiterlesen: Bash – rm und der Standard input « Taach! – Der Morgenblog […]


Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Erstelle eine kostenlose Website oder Blog – auf WordPress.com.
Entries und Kommentare feeds.

%d Bloggern gefällt das: