Xargs/Anwendung
Dateinamen mit Leerzeichen
Üblicher Weise enthält der IFS das Leerzeichen, daher bricht xargs die Dateinamen dort auseinander.
- Dem ist abzuhelfen, wenn man die GNU-Version der benutzten Tools (find und xargs) verwendet.
find(1) gibt man bekannt, er möge mit ASCII-NUL beendete Zeichenketten ausgeben, und xargs, er möge solche erwarten
find / -user toelpel -print0 | xargs -0 rm
Eingabedatei als Parameter
Sollen die Dateien verschoben werden, erwartet mv die Quelldateien als erstes, das Zielverzeichnis als letztes Argument.
- Dieses Problem löst man bei xargs(1) genauso wie bei
find / -user toelpel -print0 | xargs -0 mv {} /tmp/toelpel-trash
Die Zeichenkombination "{}" zeigt dem xargs(1), an welcher Stelle er die Argumentliste für das Kommando einzufügen hat.
Beispiele
find /tmp -name core -type f -print | xargs /bin/rm -f
Finde Dateien mit dem Namen core in oder unterhalb des Verzeichnisses /tmp und lösche diese.
- Achtung, diese Aktion schlägt fehl falls die Dateinamen Zeilenvorschübe, einfache oder doppelte Anführungszeichen oder Leerzeichen enthalten.
- Mit folgendem Befehl werden alle Dateien aus dem aktuellen Verzeichnis entfernt, die auf das Muster *.tmp</nowiki> passen.
- Dabei werden Dateien, deren Namen Leerzeichen enthalten ebenfalls berücksichtigt: find . -name "*.tmp" -print0 | xargs -0 rm
- Als nächstes eine Anwendung mit der Ersatz-Zeichenkette {}.
- Alle Dateien des Benutzers mit der uid 1001 werden nach /tmp/klaus/test verschoben.
- Achtung
- Die Verzeichnisstruktur wird dabei nicht wieder hergestellt.
- Die Dateien landen wirklich alle in /tmp/klaus/test.
find . -uid 1001 -print | xargs -i mv {} /tmp/klaus/test
- Wenn man eine Datei software.list mit den Namen von Paketen hat, die sich im Verzeichnis ~/installation/ befindet und die wie folgt aufgebaut ist alltray audacity avidemux azureus compizconfig-settings-manager
- kann man mit xargs -a ~/installation/software.list sudo apt-get install
- die Pakete komfortabel alle auf einmal installieren.
- Dabei werden bereits installierte Pakete übersprungen.
- Möchte man mehrere Dateien herunterladen, aber die Geschwindigkeit der Server lastet die eigene Internetverbindung nicht aus, so kann man mit xargs einfach mehrere Instanzen benutzen.
xargs -a downloadlist -n 1 -P 4 wget
- Dieser Befehl übergibt jeweils eine URL aus der Datei downloadlist an wget.
- Dabei wird wget vier mal jeweils mit einer anderen Datei gestartet.
- So werden vier Dateien auf einmal heruntergeladen und die Internetverbindung optimal ausgenutzt.
Weitere Möglichkeiten
This command is equivalent to using find with xargs, only a bit shorter and more efficient.
- But this form of ‑exec can be combined with a shell feature to solve the other problem (names with spaces).
- The POSIX shell allows us to use: sh -c command-line [ command-name [ args... ] ]
- We don't usually care about the command-name, so “X”, “dummy”, “”, or “'inline cmd'” is often used.
- Here's an example of efficiently copying found files to /tmp, in a POSIX-compliant way.
find . -name '*.txt' -type f \ -exec sh -c 'exec cp -f "$@" /tmp' X '{}' +
- Obvious, simple, and readable, isn't it? Perhaps not, but worth knowing since it is safe, portable, and efficient.
Beispiel 1
Wo in meinem Homeordner liegt die Datei "test.pdf"? find ~ -name test.pdf
- Ihr habt vor ein paar Minuten eilig etwas gespeichert, habt aber den Namen der Datei und den Pfad sofort vergessen.
- Irgendwo in eurem Homeordner, irgendein Name, vor ein paar Minuten...
find ~ -type f ! -path '*/.*' -mmin -10 -ls
Die Optionen im Einzelnen
- ~ -- rekursiv in meinem Homeordner
- -type f -- eine Datei (kein Ordner...)
- ! -path '*/.*' -- ohne Dateien oder Ordner, die mit Punkt beginnen.
- -mmin -10 -- nicht älter als 10 Minuten
- -ls -- lange Anzeige (wie ls -l)
- Variante: mit Dotfiles, nicht aber Ordner, die mit Punkt beginnen (also ohne .gnome/ oder .kde/ und so weiter, aber mit .bash_history).
- Und zwar alle Dateien, die nicht älter als zwei Tage sind.
find ~ ! -path '*/.*/*' -type f -ctime -2
- Im Homeordner alle Dateien finden, deren Namen Leerzeichen enthalten.
- Wieder ohne Dateien oder Ordner, die mit Punkt beginnen.
find $HOME ! -path '*/.*' -type f -name '* *'
- $HOME -- ist gleichbedeutend mit ~ * -name '* *' -- Name enthält mindestens ein Leerzeichen
- Ganz ähnlich, aber diesmal wird nach Ordnern gesucht: find $HOME ! -path '*/.*' -type d -name '* *' -type d -- Ordner (type directory)
- Im Ordner public_html alle HTML und PHP Dateien ausfindig machen, in denen ".mp3" erwähnt wird.
- Nur die Dateinamen ausgeben.
find ~/public_html \( -name \*.html -o -name \*.php \) | xargs grep -l '.mp3'
- Zu beachten: ( ) * müssen vor der Bash versteckt, also mit Backslash escaped werden.
- Andere Methode (-exec statt xargs), gleiches Ergebnis.
find ~/public_html \( -name \*.html -o -name \*.php \) -exec grep -l '.mp3' '{}' \+
- Zu beachten: -exec mit \+ abschließen statt mit \; beschleunigt das Abarbeiten ungemein, weil dann mehrere grep Prozesse parallel gestartet werden (vgl. xargs).
- Setzt eine halbwegs aktuelle Version von find voraus.
- Hoppla, da sind auch Dateien mit Leerzeichen im Namen dabei? Kein Problem.
find ~/public_html \( -name \*.html -o -name \*.php \) -print0 | xargs -0 grep -l '.mp3'
- finds Option -print0 erzeugt die richtige Ausgabe für xargs Option -0
- Anderer Einsatzzweck: Plattenplatz wird knapp, wo sind die großen Dateien?
- Also zum Beispiel: in meinem Homeordner alle Dateien, die größer als 500MB sind.
find ~ -size +500M
- Alle Dateien/Ordner in meinem Homeordner finden, die nicht mir gehören: find ~ ! -user $( whoami ) -ls
- Alle Dateien/Ordner in meinem Homeordner, die root gehören: find ~ -user root
- Alle Dateien/Ordner in meinem Homeordner, die die Rechte auf 777 gesetzt haben,
- also Lese/Schreib/Ausführrechte für alle haben, lange Ausgabe wie "ls -l".
find ~ -perm 777 -ls
- Welche Art von Dateien (Mimetype) liegen im Ordner Documents, mit Rücksicht auf Dateien mit Leerzeichen im Namen, nur in diesem Ordner, keine Unterordner: find ~/Documents/ -maxdepth 1 -type f -print0 | xargs -0 file
- Find sucht immer rekursiv, es sei denn, man schränkt mit "-maxdepth" die Tiefe ein.
- Die Liste ließe sich beliebig fortsetzen.
- Wie vielseitig find ist, zeigt sich schon an der Länge der manpage.
- Find findet nach Name, Regular expression, Größe, Datum, Dateityp,...
- und so weiter und läßt sich mit "-exec" und der Pipe für "xargs" zu beinahe allem verwenden, was nur auf bestimmte Dateien/Ordner angewandt werden soll.
- Darum wird das Kommando in vielen Shellskripten verwendet.
Beispiel 2
Dateien im aktuellen Ordner und Unterordnern finden, deren Namen einem bestimmen Muster entsprechen:
find . -name "*.jpg"
- Dateien finden, deren Namen nicht einem bestimmten Muster entsprechen:
find . \! -name "*.jpg"
- Dateien finden, deren Namen einem bestimmten Muster entsprechen und die einen bestimmten Text enthalten:
find . -name "*.php" -exec grep -il "suchtext" {} \;
- Dateien finden, die bestimmte Datei-Endungen haben (Mit Regular-Expressions finden):
find . -regex ".*(php|html|tpl)$"
- Große Dateien finden (Dateien finden, die größer als ca. 500 MB sind):
find . -type f -size +500000k -exec ls -lh {} \;
- Dateien finden, deren Pfade einem bestimmten Pattern entsprechen:
find . -path "*/.svn*"
- Dateien finden, deren Pfade nicht einem bestimmten Pattern entsprechen:
find . \! -path "*/.svn*"
- Dateien finden, die nicht einem bestimmten User (nicht root) gehören:
find . \! -user root
- Dateien finden, die “oo” oder “ee” im Namen haben:
find . \( -name "*oo*" -or -name "*ee*" \)
- Dateien finden, die nicht “oo” oder “ee” im Namen haben:
find . \! \( -name "*oo*" -or -name "*ee*" \)
- Geht nicht mit find (GNU findutils) 4.4.2:
find . -regex “.*(php|html|tpl)$” mkdir /tmp/tst cd /tmp/tst mkdir -p 1/2/3 mkdir -p 4/5/6 touch la.php touch 1/2/uu.tpl touch 4/lala.html touch 4/5/6/eee.php touch 4/5/oooooo' find . -regex “.*(php|html|tpl)$”
- es wird nix ausgegeben :(
Beispiel 3
Angenommen, Sie möchten eine Liste der Verzeichnisse in /usr/share erhalten, dann tippen Sie: find /usr/share -type d
- Angenommen, Sie haben einen HTTP-Server und alle Ihre HTML-Dateien befinden sich in /home/httpd/html, wo Sie sich auch gerade befinden.
- Sie möchten eine Liste aller Dateien, deren Inhalt seit einem Monat nicht verändert worden ist.
- Da die Seiten von verschiedenen Schreibern stammen, enden einige auf html und einige auf htm.
- Sie möchten diese Dateien in das Verzeichnis /home/httpd/obsolete verknüpfen.
- Geben Sie folgendes ein:[
- Denken Sie daran, dass in diesem Beispiel beide Verzeichnisse auf dem selben Dateisystem sein müssen!
find \( -name "*.htm" -o -name "*.html" \) -a -ctime -30 -exec ln {} /home/httpd/obsolete \;
- Gut, das hier ist etwas komplex und verlangt nach Erklärung.
- Das Suchkriterium ist Folgendes: \( -name "*.htm" -o -name "*.html" \) -a -ctime -30
- Es findet alle Dateien, die entweder auf .htm oder auf .html enden (( -name "*.htm" -o -name "*.html" )) und (-a) die in den letzten 30 Tagen nicht modifiziert wurden (-ctime -30).
- Beachten Sie die Klammern, die hier notwendig sind, da -a eine höhere Wertigkeit hat.
- Ließen Sie sie weg, würde das Kommando alle Dateien mit der Endung .htm finden sowie die Dateien, die auf .html enden und seit einem Monat nicht modifiziert wurden.
- Beachten Sie auch, dass die Klammern vor der Shell geschützt wurden.
- Gäben Sie ( .. ) anstelle von \( .. \) ein, würde die Shell versuchen, diese zu interpretieren und das Kommando -name "*.htm" -o -name "*.html" in einer Sub-Shell auszuführen.
- Sie können diesen Schutz übrigens auch durch Anführungszeichen erreichen.
- Und schließlich das Kommando, das für jede gefundene Datei ausgeführt wird: -exec ln {} /home/httpd/obsolete \;
- Auch hier müssen Sie das ;</nowiki> vor der Shell schützen, da diese es sonst als Kommandoseparator interpretiert und find sich beschweren wird, dass -exec ein Argument fehlt.
- Ein letztes Beispiel:
- Sie haben ein großes Verzeichnis mit allen möglichen Bilddateien: /shared/images.
- Normalerweise benutzen Sie touch, um den Zeitstempel einer Datei namens stamp in diesem Verzeichnis aufzufrischen, um eine Zeitreferenz zu haben.
- Sie wollen eine Liste aller JPEG-Dateien, die jünger als die Datei stamp sind.
- Da Sie die Dateien von verschiedenen Quellen haben, haben Sie die Endungen jpg, jpeg, JPG oder JPEG.
- Sie möchten nicht im Verzeichnis old suchen, Sie möchten diese Liste zugeschickt bekommen und Ihr Benutzername ist john:
find /shared/images -cnewer \ /shared/images/stamp \ -a -iregex ".*\.jpe?g" \ -a -not -regex ".*/old/.*" \ | mail john -s "Neue Images"
- Nun wäre es nicht sehr schön, dieses Kommando regelmäßig neu eingeben zu müssen, also brauchen Sie…?