[linux-l] xargs

Mike Dornberger Mike.Dornberger at gmx.de
So Apr 20 08:19:29 CEST 2008


Hi,

On Sat, Apr 19, 2008 at 04:33:29PM +0200, Winfried Wendler wrote:
> Ich weiss aber nicht, warum man in diesem Fall nicht auf xargs verzichten
> kann.
> Mein Vorschlag
> find -type f -print0 -exec grep -i copyright \{\} \;
> 
> xarges ist meines Wissens dazu dar, die Anzahl der Parameter auzusplitten,
> damit sie die Länge einer Kommandozeile nicht überschreiten. Also rm
> [10000 Filenamen] in 10000 mal rm [1 Filename] zu wandeln. Das ist aber
> beim find mit -exec nicht der Fall. Da kommet jedes File schön einzeln.

so funktioniert xargs (ohne entsprechend gesetzte Optionen) aber nicht.
xargs liest eine Liste von stdin und generiert ggf. mehrere Aufrufe des
angegebenen Programms (oder echo, falls keins angegeben ist), so daß aber
die Maximallänge der Argumentenzeichenkette nicht überschritten wird (was
wohl OS-spezifisch ist). Das kann man ganz leicht beispielsweise mittels

$ find / -print0 | xargs -0r sh -c 'echo $#' sh

herausfinden (vorausgesetzt es existieren genug Dateien). (Warum man sh,
bzw. einen beliebigen String nach -c '<Shell-Konstrukt>' mit angeben muß,
siehe `man sh'; $0 wird durch den ersten Parameter besetzt. Leicht zu
überprüfen, wenn man statt dem zweiten sh irgendwas angibt und im
Shell-Konstrukt z. B. ein '; echo $0' mit einbaut. $# ist übrigens die Zahl
der Argumente.)

Das -exec im find fork'ed und exec'ed für jede Datei, für die die vorherigen
Tests true gegeben haben, einen neuen Prozeß. Das will man meist nicht, da
es "teuer" und vergleichsweise langsam ist, bzw. wenn einem das geforke noch
egal ist, kann es auch sein, daß das zu startende Programm lange zur
(De-)Initialisierung braucht oder in irgendeiner Art und Weise "teure"
Resourcen bei jedem Start verbraucht. Man denke z. B. an tar oder
ssh/scp/rsync, etc.

Das -print0 im obigen Vorschlag ist übrigens doppelt falsch, da es zum einen
die Pfadnamen zu jedem Objekt, was find vorher gefunden hat (reguläre
Dateien), auf der Konsole "hintereinanderklatscht" (das \0 Byte wird nicht
angezeigt, erzeugt aber auch keinen Zeilenvorschub) und zum anderen ja
unabhängig vom grep-Ergebnis (was auch ein true liefert, wenn copyright
gefunden wurde in der Datei und false, wenn nicht) ist.

Etwas sinnvoller wäre es, ein -print nach dem -exec zu machen. (Gibt halt
die Namen der Dateien nach der Fundzeile aus.) Ganz sinnvoll ist es, xargs
zu benutzen mit -print0 beim find und -0r als Optionen. -0: nullterminierte
Strings; -r: Rufe das angegebene Programm nicht auf, wenn nichts von stdin
kommt. (Test hierzu: Mal als Programm bei xargs ls verwenden: gibt, wenn die
Tests von find nichts liefern, den Inhalt des aktuellen Verzeichnisses aus;
ls ohne Argument halt.)

Wenn grep mehr als eine Datei im argv hat, dann gibt es automatisch den
Dateinamen bei Fund mit aus. Hier also sinnvollerweise -H (print filename
for each match) mit angeben, da man sonst raten muß, wie die Datei heißt,
wenn die Tests nur eine ergeben (oder find nochmal ohne xargs ausführen).

Noch ein Hinweis: -d kennt xargs aus den GNU Tools, die in sarge sind, noch
nicht. Die man page aus etch rät auch dringend dazu, -0 zu benutzen. (Warum
geht man eigentlich davon aus, daß man zwar Leerzeichen, aber keine \n in
Dateinamen haben kann? Windows-Rechner/entsperchender smb-Share?) -0 (und
-print0) sind aber leider nicht im POSIX-Standard (wie auch -d nicht).
Siehe: http://www.opengroup.org/onlinepubs/000095399/utilities/xargs.html

Ich habe gerade noch herausgefunden, daß man den selben Effekt, wie -d "\n"
erreichen kann, wenn man es denn unbdingt so bauen will und kein -0 hat: Man
muß ein sed zwischenschalten, was geschickt quotet.

find ... | sed -e 's/\([[:space:]\\"'\'']\)/\\\1/g' | xargs ...

(NB: Die o. g. Seite rät weiter unten, einfach alle Zeichen zu quoten, in
dem Fall halt alle außer \n.)

Vgl. auch
$ printf 'ab \\cd\\\nef gh\nx "xx"'\''xx'\''`xx` x' | \
sed -e 's/\([[:space:]\\"'\'']\)/\\\1/g' | xargs printf '~%s~\n'

Wenn man ein \n gequotet haben will (ähnlich wie in der Shell, wenn man
Kommandozeilen über mehrere Zeilen umbrechen will), muß man eine
Substitution rückgängig machen:

$ printf 'ab \\cd\\\nef gh\nx "xx"'\''xx'\''`xx` x' | \
sed -e 's/\([[:space:]\\"'\'']\)/\\\1/g' \
 -e 's/\(^\|[^\\]\)\(\\\\\\\\\)*\\\\$/\1\2\\/' | xargs printf '~%s~\n'

Da find nach OpenGroup.org übrigens kein -printf und -print0 (wie das GNU
find) hat, steht auf der Seite o. g. Seite übrigens, daß man per -exec ein
Script ausführen soll, daß das Quoting übernimmt. In dem Fall muß man halt
mit dem fork und exec für jeden Fund leben.

Grüße,
 Mike Dornberger



Mehr Informationen über die Mailingliste linux-l