Wie werden von find gefundene Dateien als Argumente übergeben?

9

Zuerst, um triviale, aber nicht zutreffende Antworten abzuschneiden: Ich kann weder den find+ xargsTrick noch seine Varianten (wie findbei -exec) verwenden, da ich pro Aufruf nur wenige solcher Ausdrücke verwenden muss. Ich werde am Ende darauf zurückkommen.


Betrachten wir nun für ein besseres Beispiel:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Wie gebe ich diese als Argumente weiter program?

Nur das zu tun, reicht nicht aus

$ ./program $(find -L some/dir -name \*.abc | sort)

schlägt fehl, da programfolgende Argumente abgerufen werden:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Wie zu sehen ist, wurde der Pfad mit dem Leerzeichen aufgeteilt und programbetrachtet ihn als zwei verschiedene Argumente.

Zitieren, bis es funktioniert

Es scheint, dass Anfänger wie ich, wenn sie mit solchen Problemen konfrontiert sind, dazu neigen, zufällig Anführungszeichen hinzuzufügen, bis es schließlich funktioniert - nur hier scheint es nicht zu helfen ...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Da die Anführungszeichen das Aufteilen von Wörtern verhindern, werden alle Dateien als einzelnes Argument übergeben.

Einzelne Pfade zitieren

Ein vielversprechender Ansatz:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

Die Zitate sind da, klar. Sie werden aber nicht mehr interpretiert. Sie sind nur ein Teil der Saiten. Sie haben also nicht nur die Wortteilung nicht verhindert, sondern auch Streitigkeiten geführt!

IFS ändern

Dann habe ich versucht, damit herumzuspielen IFS. Ich würde es sowieso findmit -print0und sortmit bevorzugen -z- damit sie selbst keine Probleme mit "verdrahteten Pfaden" haben. Warum also nicht dem nullCharakter die Wortteilung aufzwingen und alles haben?

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

Es teilt sich also immer noch im Raum und nicht im null.

Ich habe versucht, die IFSAufgabe sowohl in $(…)(wie oben gezeigt) als auch zuvor zu platzieren ./program. Auch habe ich versucht , andere Syntax wie \0, \x0, \x00beide zitiert mit 'und "sowie mit und ohne $. Keiner von diesen schien einen Unterschied zu machen ...


Und hier habe ich keine Ideen mehr. Ich habe noch ein paar Dinge ausprobiert, aber alle schienen auf die gleichen Probleme wie die aufgelisteten zurückzuführen zu sein.

Was könnte ich noch tun? Ist es überhaupt machbar?

Klar, ich könnte programdie Muster akzeptieren lassen und selbst suchen. Aber es ist eine Menge Doppelarbeit, wenn man es auf eine bestimmte Syntax fixiert. (Was ist grepzum Beispiel mit der Bereitstellung von Dateien durch a ?)

Außerdem könnte ich das programAkzeptieren einer Datei mit einer Liste von Pfaden veranlassen. Dann kann ich den findAusdruck einfach in eine temporäre Datei kopieren und nur den Pfad zu dieser Datei angeben. Dies kann auf direkten Pfaden unterstützt werden, sodass Benutzer, die nur einen einfachen Pfad haben, diese ohne Zwischendatei bereitstellen können. Aber das scheint nicht schön zu sein - man muss zusätzliche Dateien erstellen und sich um sie kümmern, ganz zu schweigen von der zusätzlichen Implementierung. (Auf der positiven Seite könnte dies jedoch eine Rettung für Fälle sein, in denen die Anzahl der Dateien als Argumente Probleme mit der Befehlszeilenlänge verursacht…)


Lassen Sie mich am Ende noch einmal daran erinnern, dass find+ xargs(und ähnliche) Tricks in meinem Fall nicht funktionieren. Zur Vereinfachung der Beschreibung zeige ich nur ein Argument. Aber mein wahrer Fall sieht eher so aus:

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

Wenn ich also xargsvon einer Suche aus mache, habe ich immer noch die Frage, wie ich mit der anderen umgehen soll ...

Adam Badura
quelle

Antworten:

13

Verwenden Sie Arrays.

Wenn Sie nicht mit der Möglichkeit von Zeilenumbrüchen in Ihren Dateinamen umgehen müssen, könnten Sie damit durchkommen

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

dann

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Wenn Sie tun müssen , um Griff Zeilenumbrüche innerhalb von Dateinamen und haben bash> = 4.4, können Sie verwenden -print0und -d ''zu Null enden die Namen während Array Konstruktion:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(und ähnlich für die XYZ_FILES). Wenn Sie nicht über die neuere Bash verfügen, können Sie eine nullterminierte Leseschleife verwenden, um Dateinamen an die Arrays anzuhängen, z

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
Steeldriver
quelle
Ausgezeichnet! Ich habe über Arrays nachgedacht. Aber irgendwie habe ich nichts darauf gefunden mapfile(oder sein Synonym readarray). Aber es funktioniert!
Adam Badura
Trotzdem könnte man es ein bisschen verbessern. Die Bash <4.4-Version (die ich zufällig habe ...) mit einer whileSchleife löscht das Array nicht. Das heißt, wenn keine Dateien gefunden werden, ist das Array undefiniert. Wenn dies bereits definiert ist, werden neue Dateien angehängt (anstatt die alten zu ersetzen). Es scheint, dass das Hinzufügen declare -a ABC_FILES='()';vorher whileden Trick macht. (Während nur das Hinzufügen ABC_FILES='()';nicht tut.)
Adam Badura
Auch was bedeutet das < <hier? Ist es das gleiche wie <<? Ich denke nicht, dass eine Änderung zu einem <<Syntaxfehler führt ("unerwartetes Token" (")"). Also, was ist es und wie funktioniert es?
Adam Badura
Eine weitere Verbesserung (entlang meiner speziellen Verwendung) besteht darin, ein weiteres Array zu erstellen. Also haben wir die ABC_FILES. Das ist gut. Es ist jedoch nützlich, auch ABS_ARGSein leeres Array zu erstellen, wenn ABC_FILESes leer oder ein Array ist ('--abc-files' "${ABC_FILES[@]}"). Auf diese Weise kann ich es später folgendermaßen verwenden: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"und sicherstellen, dass es ordnungsgemäß funktioniert, unabhängig davon, welche der Gruppen (falls vorhanden) leer ist. Oder anders ausgedrückt: Dieser Weg --abc-files(und --xyz-files) wird nur bereitgestellt, wenn ihm ein tatsächlicher Pfad folgt.
Adam Badura
1
@AdamBadura: while read ... done < <(find blah)ist eine normale Shell-Umleitung <aus einer speziellen Datei, die von PROCESS SUBSTITUTION erstellt wurde . Dies unterscheidet sich von Piping dadurch, find blah | while read ... donedass die Pipeline die whileSchleife in einer Subshell ausführt, sodass die darin festgelegten Variablen für nachfolgende Befehle nicht beibehalten werden.
Dave_thompson_085
3

Sie können IFS = newline verwenden (vorausgesetzt, keine Dateinamen enthalten newline), aber Sie müssen es vor der Ersetzung in der äußeren Shell festlegen:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

Mit, zshaber nicht bash, können Sie auch null verwenden $'\0'. Sogar in bashkönnte man mit Zeilenumbruch umgehen, wenn es einen ausreichend seltsamen Charakter gibt, der niemals so verwendet wird

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

Dieser Ansatz behandelt jedoch nicht die zusätzliche Anforderung, die Sie in Kommentaren zur Antwort von @ steeldriver gestellt haben, die --afiles wegzulassen, wenn find a leer ist.

dave_thompson_085
quelle
Wie ich in Bash verstehe, gibt es keine Möglichkeit, IFSeine Spaltung zu erzwingen null?
Adam Badura
@AdamBadura: Ich bin mir ziemlich sicher, nicht; bash erlaubt kein Null-Byte in einer Variablen, einschließlich IFS. Beachten Sie, dass die read -d ''in steeldriver verwendete Methode eine leere Zeichenfolge ist, die kein Null-Byte enthält. (Und eine
Befehlsoption ist
Sie müssen auch globbing ( set -o noglob) deaktivieren, bevor Sie diesen split + glob-Operator verwenden (außer in zsh).
Stéphane Chazelas
@AdamBadura Ja, in bash ist eine Null genau das gleiche wie $'\0'und auch als ''.
Isaac
1

Ich bin mir nicht sicher, warum du aufgegeben hast xargs.

Wenn ich also xargsvon einer Suche aus mache, habe ich immer noch die Frage, wie ich mit der anderen umgehen soll ...

Die Zeichenfolge --xyz-filesist nur eines von vielen Argumenten, und es gibt keinen Grund, sie als besonders zu betrachten, bevor sie von Ihrem Programm interpretiert wird. Ich denke, Sie können es xargszwischen beiden findErgebnissen durchgehen :

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
Kamil Maciorowski
quelle
Sie haben Recht! Das funktioniert auch! Beachten Sie jedoch, dass Sie den -print0zweiten Platz verpasst haben find. Auch wenn ich diesen Weg gehe, würde ich das auch --abc-filesals echo- nur aus Gründen der Konsistenz.
Adam Badura
Dieser Ansatz scheint einfacher und etwas einzeiliger zu sein als der Array-Ansatz. Es würde jedoch eine zusätzliche Logik erfordern, um den Fall abzudecken, dass, wenn keine .abcDateien vorhanden sind, auch keine vorhanden sein sollten --abc-files(dasselbe gilt für .xyz). Die Array-basierte Lösung von Steeldriver erfordert auch zusätzliche Logik, aber diese Logik ist dort trivial, während sie hier möglicherweise nicht so trivial ist und den Hauptvorteil dieser Lösung zerstört - die Einfachheit.
Adam Badura
Auch ich bin nicht wirklich sicher , aber ich nehme an, dass xargsnie Argumente werden versuchen , zu teilen und einige Befehle statt zu machen, es sei denn , es wird ausdrücklich dazu aufgefordert werden so mit -L, --max-lines( -l), --max-args( -n) oder --max-chars( -s) Argumente. Habe ich recht? Oder gibt es einige Standardeinstellungen? Da mein Programm eine solche Aufteilung nicht richtig handhaben würde und ich es lieber nicht nennen würde ...
Adam Badura
1
@AdamBadura fehlt -print0- behoben, danke. Ich kenne nicht alle Antworten, aber ich stimme zu, dass meine Lösung es schwierig macht, zusätzliche Logik aufzunehmen. Ich würde wahrscheinlich selbst mit Arrays gehen, wenn ich diesen Ansatz kenne. Meine Antwort war nicht wirklich für dich. Sie hatten die andere Antwort bereits akzeptiert und ich nahm an, dass Ihr Problem gelöst ist. Ich wollte nur darauf hinweisen, dass Sie Argumente aus mehreren Quellen weitergeben können xargs, was auf den ersten Blick nicht offensichtlich war. Sie können es als Proof of Concept behandeln. Jetzt kennen wir alle nur noch wenige unterschiedliche Ansätze und können bewusst auswählen, was in jedem Einzelfall zu uns passt.
Kamil Maciorowski
Ja, ich habe die Array-basierte Lösung bereits implementiert und sie funktioniert wie ein Zauber. Ich bin besonders stolz darauf, wie sauber es mit der Option umgeht (wenn keine Dateien, dann nein --abc-files). Aber Sie haben Recht - es ist gut, Ihre Alternativen zu kennen! Vor allem, dass ich fälschlicherweise dachte, es sei nicht möglich.
Adam Badura