
Um zunächst triviale, aber nicht anwendbare Antworten abzuschneiden: Ich kann weder den find
+ xargs
-Trick noch seine Varianten (wie find
mit -exec
) verwenden, da ich nur wenige solcher Ausdrücke pro Aufruf verwenden muss. Ich werde am Ende darauf zurückkommen.
Betrachten wir nun für ein besseres Beispiel Folgendes:
$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
Wie übergebe ich diese als Argumente an program
?
Es einfach zu tun, reicht nicht aus
$ ./program $(find -L some/dir -name \*.abc | sort)
schlägt fehl, da program
folgende Argumente ausgegeben werden:
[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc
Wie man sieht, wurde der Pfad mit Leerzeichen aufgeteilt und program
als zwei unterschiedliche Argumente betrachtet.
Zitieren, bis es klappt
Es scheint, dass unerfahrene Benutzer wie ich bei solchen Problemen dazu neigen, wahllos Anführungszeichen hinzuzufügen, bis es schließlich funktioniert – nur scheint das hier 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 eine Worttrennung verhindern, werden alle Dateien als ein einziges Argument übergeben.
Zitieren einzelner Pfade
Ein erfolgversprechender 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 Anführungszeichen sind natürlich da. Aber sie werden nicht mehr interpretiert. Sie sind nur Teil der Zeichenfolgen. Sie haben also nicht nur die Worttrennung nicht verhindert, sondern sind auch in Argumente geraten!
IFS ändern
Dann habe ich versucht, mit herumzuspielen . Ich würde sowieso mit und mit IFS
bevorzugen – damit sie selbst keine Probleme mit „verdrahteten Pfaden“ haben. Warum also nicht dem Charakter die Worttrennung aufzwingen und alles haben?find
-print0
sort
-z
null
$ ./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 wird also immer noch im Raum aufgeteilt, aber nicht im null
.
IFS
Ich habe versucht, die Zuweisung sowohl in $(…)
(wie oben gezeigt) als auch davor zu platzieren ./program
. Außerdem habe ich andere Syntax ausprobiert, wie \0
, \x0
, \x00
sowohl in Anführungszeichen mit '
und "
als auch mit und ohne $
. Nichts davon schien einen Unterschied zu machen…
Und jetzt habe ich keine Ideen mehr. Ich habe noch ein paar Dinge ausprobiert, aber alles schien auf die gleichen Probleme hinauszulaufen wie aufgelistet.
Was könnte ich sonst tun? Ist das überhaupt machbar?
Natürlich könnte ich dafür sorgen, dass program
die Muster akzeptiert werden und die Suche selbst durchgeführt wird. Aber es ist eine Menge doppelte Arbeit, wenn man es auf eine bestimmte Syntax festlegen muss. (Wie wäre es grep
zum Beispiel mit der Bereitstellung von Dateien per a?)
Ich könnte auch program
eine Datei mit einer Liste von Pfaden akzeptieren lassen. Dann kann ich den find
Ausdruck einfach in eine temporäre Datei kopieren und nur den Pfad zu dieser Datei angeben. Dies könnte über direkte Pfade unterstützt werden, sodass der Benutzer, wenn er nur einen einfachen Pfad hat, diesen ohne Zwischendatei angeben kann. 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 erforderlichen zusätzlichen Implementierung. (Auf der positiven Seite könnte es jedoch eine Rettung für Fälle sein, in denen die Anzahl der Dateien als Argumente Probleme mit der Befehlszeilenlänge verursacht …)
Zum Schluss möchte ich Sie noch einmal daran erinnern, dass find
+ xargs
(und ähnliche) Tricks in meinem Fall nicht funktionieren. Der Einfachheit halber zeige ich nur ein Argument. Aber mein tatsächlicher 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 eine xargs
Suche von einer Seite aus durchführe, bleibt mir immer noch die Frage, wie ich mit der anderen umgehen soll …
Antwort1
Verwenden Sie Arrays.
Wenn Sie die Möglichkeit von Zeilenumbrüchen in Ihren Dateinamen nicht berücksichtigen müssen, können Sie Folgendes verwenden:
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 duTunWenn Sie Zeilenumbrüche in Dateinamen verarbeiten müssen und über Bash >= 4.4 verfügen, können Sie -print0
und verwenden -d ''
, um die Namen während der Array-Konstruktion mit Null zu beenden:
mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)
(und das Gleiche gilt für XYZ_FILES
). Wenn SienichtWenn Sie die neuere Bash haben, können Sie eine nullterminierte Lese-Schleife verwenden, um Dateinamen an die Arrays anzuhängen, z. B.
ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
Antwort2
Sie können IFS=newline verwenden (vorausgesetzt, kein Dateiname enthält ein Newline), Sie müssen es jedoch 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 zsh
but not können Sie auch bash
null verwenden . Auch in können Sie newline verwenden, wenn es ein ausreichend seltsames Zeichen gibt, das nie verwendet wird, wie$'\0'
bash
IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...
Dieser Ansatz berücksichtigt jedoch nicht die zusätzliche Anforderung, die Sie in Kommentaren zur Antwort von @steeldriver gestellt haben, nämlich --afiles wegzulassen, wenn „find a“ leer ist.
Antwort3
Ich bin nicht sicher, ob ich verstehe, warum Sie aufgegeben haben xargs
.
Wenn ich also eine
xargs
Suche von einer Seite aus durchführe, bleibt mir immer noch die Frage, wie ich mit der anderen umgehen soll …
Der String ist nur eines von vielen Argumenten und es gibt keinen Grund, ihn als etwas Besonderes zu betrachten, bevor er von Ihrem Programm interpretiert wird. Ich denke, Sie können ihn zwischen beiden Ergebnissen --xyz-files
weitergeben :xargs
find
{ 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