Wie übergebe ich das Ergebnis von "find" als Liste von Dateien?

11

Die Situation ist, ich habe einen MP3-Player mpg321, der eine Liste von Dateien als Argument akzeptiert. Ich bewahre meine Musik in einem Verzeichnis mit dem Namen "Musik" auf, in dem sich noch einige Verzeichnisse befinden. Ich möchte nur alle spielen, also starte ich das Programm mit

mpg321 $(find /music -iname "*\.mp3")

. Das Problem ist, dass einige Dateinamen Leerzeichen enthalten und das Programm diese Namen in kleinere Teile zerlegt und sich über fehlende Dateien beschwert. Das Ergebnis findin Anführungszeichen setzen

mpg321 "$(find /music -iname "*\.mp3")"

hilft nicht, weil alle zu einem großen "Dateinamen" werden, der offensichtlich nicht gefunden wird.

Wie kann ich das dann machen? Wenn das wichtig ist, verwende ich bash, werde aber zshbald wechseln .

Phunehehe
quelle

Antworten:

17

Versuchen Sie, find's -print0oder -printfoption in Kombination mit folgendem zu verwenden xargs:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Wie dies funktioniert, wird auf der Handbuchseite von find erläutert :

-print0

Wahr; Drucken Sie den vollständigen Dateinamen in der Standardausgabe, gefolgt von einem Nullzeichen (anstelle des von -print verwendeten Zeilenumbruchzeichens). Auf diese Weise können Dateinamen, die Zeilenumbrüche oder andere Arten von Leerzeichen enthalten, von Programmen, die die Suchausgabe verarbeiten, korrekt interpretiert werden. Diese Option entspricht der Option -0 von xargs.

Steven D.
quelle
Entschuldigung für die ganze Bearbeitung. Ich scheine mich an das Muster -print0 xarg -0 zu erinnern, das bei anderen Arten von Leerzeichen fehlgeschlagen ist, aber ich konnte kein Beispiel finden.
Steven D
oh ja das funktioniert! aber ich frage mich immer noch warum mpg321 $(find /music -iname "*\.mp3" -print0)nicht?
Phunehehe
1
Nun, ich glaube, das funktioniert aus zwei Gründen nicht: (1) Die Dateinamen werden durch Nullzeichen getrennt, was mpg321 überhaupt nicht erwartet, und (2) xargs macht die Arbeit, dem anderen Leerzeichen zu entkommen, nicht finden.
Steven D
2
@phunehehe, @Steven: mpg321hat nichts damit zu tun, es ist die Shell, die die Ausgabe von findin separate Argumente aufteilt . Und -print0 | xargs -0funktioniert mit jedem möglichen Dateinamen.
Gilles 'SO - hör auf böse zu sein'
8
find /music -iname "*\.mp3" -exec mpg123 {} +

Mit GNU find können Sie auch -print0und verwendenxargs -0 , aber es macht wenig Sinn, noch ein anderes Tool zu lernen. Die -exec ... {} +Syntax wird kaum erwähnt, da Linux sie später erworben hat -print0, aber es gibt keinen Grund, sie jetzt nicht zu verwenden.

Mit zsh oder bash 4 ist dies viel einfacher:

mpg123 **/*.[Mm][Pp]3

Nur in zsh können Sie ein (Teil eines) Musters unabhängig von Groß- und Kleinschreibung machen:

mpg123 (#i)**/*.mp3
Gilles 'SO - hör auf böse zu sein'
quelle
+1 dazu ist "find -print0" ein hässlicher Hack.
p-statisch
2

Ich denke, Stevens Lösung ist die beste, aber eine andere Möglichkeit besteht darin, das xargs- -IFlag zu verwenden, mit dem Sie eine Zeichenfolge angeben können, die dann im Befehl durch das Argument ersetzt wird (anstatt das Argument nur an das Ende des Befehls anzuhängen). Sie können das verwenden, um das Argument zu zitieren:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Michael Mrozek
quelle
Ich verstehe diese Antwort nicht ... Sie verwenden die -0Flagge von xargs ohne die von find -print0. Auch die -IFlagge von xargs hat eine Reihe von Konsequenzen. Im Gegensatz zu einfach, bei xargsdem alle Zeilen von stdin zu einem Befehl komprimiert werden, xargs -Iwird es mpg321möglicherweise hunderte oder tausende Male ausgeführt (einmal für jede Datei), was sicherlich nicht beabsichtigt ist. Denken Sie auch daran, dass das Zitieren von foo nicht xargserforderlich ist, wie dies intern der Fall ist. Beachten Sie, dass alle Dateinamen xargs -Inicht geöffnet werden, wenn Sie sie in einer Zeile komprimieren und an diese senden .
Sechs
1

Es ist in der Regel am besten direkt , -exec ${tgt_process} \{\} +aber wenn Sie tun müssen eine zuverlässig getrennte Liste von Dateinamen in einer Datei oder einem Stream bekommen aus findwelchen Gründen auch immer, dann können Sie dies tun:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Was Sie daraus machen, sind zwei einzigartige Zeichenfolgen. Am Anfang jedes Dateinamens steht die Zeichenfolge \n///und am Ende jedes Dateinamens steht die Zeichenfolge ///\n. Diese beiden Zeichenfolgen kommen nirgendwo anders in findder Ausgabe vor, außer an diesen Positionen, unabhängig davon, welche Zeichen die Dateinamen enthalten.

Darüber hinaus ist die oben genannte Verwendung POSIX Portable Baseline und kann für nahezu jedes Unix-System verwendet werden. Dies gilt nicht für die Verwendung eines Null-Byte-Begrenzers - trotz seiner Bequemlichkeit -, die von einigen anderen empfohlen wird.

Aber auch dies ist nur notwendig, wenn Sie aus irgendeinem Grund nicht direkt -execIhre können $tgt_process, da dies Ihr Ziel sein sollte. Zum einen erfordert die obige Methode immer noch das Parsen. Wenn Sie beispielsweise möchten, dass jede Dateinamen-Shell in Anführungszeichen gesetzt wird, müssen Sie zuerst sicherstellen, dass alle harten Anführungszeichen im Dateinamen maskiert werden:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Dadurch wird ein ordnungsgemäß Shell-Escape-Array von Dateinamen ausgegeben, unabhängig davon, welche Zeichen sie enthalten. Jetzt müssen Sie nur noch hoffen, dass Ihre Bewerbung auf der Empfängerseite sie nicht beschädigt.

mikeserv
quelle
0

Eine andere Möglichkeit besteht darin, alle Sonderzeichen, die in Ihren Dateinamen enthalten sind, zu umgehen. Z.B:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Dadurch werden die ordnungsgemäß maskierten Dateinamen zur Ausführung an xargs übergeben, und es treten keine Probleme auf.

Priyabrata Patnaik
quelle
1
Dies bricht immer noch bei Zeilenumbrüchen in Dateinamen. Und Sie könnten genauso gut sed 's|.|\\&|g'- das empfiehlt POSIX sowieso.
Mikesserv