Xargs/Anwendung

Aus Foxwiki

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 '{}' +

</syntaxhighlight>

  • 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".

<syntaxhighlight lang="bash" highlight="1" line>

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:

<syntaxhighlight lang="bash" highlight="1" line> 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:

<syntaxhighlight lang="bash" highlight="1" line>

find . -name "*.jpg"
  • Dateien finden, deren Namen nicht einem bestimmten Muster entsprechen:

<syntaxhighlight lang="bash" highlight="1" line>

find . \! -name "*.jpg"
  • Dateien finden, deren Namen einem bestimmten Muster entsprechen und die einen bestimmten Text enthalten:

<syntaxhighlight lang="bash" highlight="1" line>

find . -name "*.php" -exec grep -il "suchtext" {} \;
  • Dateien finden, die bestimmte Datei-Endungen haben (Mit Regular-Expressions finden):

<syntaxhighlight lang="bash" highlight="1" line>

find . -regex ".*(php|html|tpl)$"
  • Große Dateien finden (Dateien finden, die größer als ca. 500 MB sind):

<syntaxhighlight lang="bash" highlight="1" line>

find . -type f -size +500000k -exec ls -lh {} \;
  • Dateien finden, deren Pfade einem bestimmten Pattern entsprechen:

<syntaxhighlight lang="bash" highlight="1" line>

find . -path "*/.svn*"
  • Dateien finden, deren Pfade nicht einem bestimmten Pattern entsprechen:

<syntaxhighlight lang="bash" highlight="1" line>

find . \! -path "*/.svn*"
  • Dateien finden, die nicht einem bestimmten User (nicht root) gehören:

<syntaxhighlight lang="bash" highlight="1" line>

find . \! -user root
  • Dateien finden, die “oo” oder “ee” im Namen haben:

<syntaxhighlight lang="bash" highlight="1" line>

find . \( -name "*oo*" -or -name "*ee*" \)
  • Dateien finden, die nicht “oo” oder “ee” im Namen haben:

<syntaxhighlight lang="bash" highlight="1" line>

find . \! \( -name "*oo*" -or -name "*ee*" \)
  • Geht nicht mit find (GNU findutils) 4.4.2:

<syntaxhighlight lang="bash" highlight="1" line>

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!

<syntaxhighlight lang="bash" highlight="1" line>

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:

<syntaxhighlight lang="bash" highlight="1" line>

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…?

Problembehebung