Wie verwende ich find, wenn der Dateiname Leerzeichen enthält?

17

Ich möchte Dateinamen an andere Programme weiterleiten, aber alle ersticken, wenn die Namen Leerzeichen enthalten.

Nehmen wir an, ich habe eine Datei namens.

foo bar

Wie kann ich findden richtigen Namen zurückgeben?

Natürlich möchte ich:

foo\ bar

oder:

"foo bar"

EDIT : Ich möchte nicht durchgehen xargs, ich möchte eine korrekt formatierte Zeichenfolge herausholen, finddamit ich die Zeichenfolge der Dateinamen direkt an ein anderes Programm weiterleiten kann.

Fehler
quelle
5
wozu leitest du es? Kennen Sie die -execFlagge mit find? Sie können diesen Fehler möglicherweise beheben und Ihren Befehl effizienter gestalten, indem -execSie ihn nicht an andere Befehle weiterleiten. Nur meine $
.02
6
@ Bug: findformatiert die Dateinamen gut; Es wird ein Name pro Zeile geschrieben. (Dies ist natürlich mehrdeutig, wenn ein Dateiname ein Zeilenvorschubzeichen enthält.) Das Problem ist also, dass das empfangende Ende "erstickt", wenn es ein Leerzeichen erhält. Dies bedeutet, dass Sie uns mitteilen müssen, was das empfangende Ende ist, wenn Sie eine aussagekräftige Antwort wünschen .
rici
2
Was Sie als "richtig formatiert" bezeichnen, wird tatsächlich "von der Shell nicht mehr verwendet". Die meisten Dienstprogramme, die eine Reihe von Dateinamen lesen können, würden sich an einem Namen mit Shell-Escapezeichen verschlucken, aber es wäre in der Tat sinnvoll, wenn (sagen wir) findeine Option zur Ausgabe von Dateinamen in einem für die Shell geeigneten Format angeboten würde. Im Allgemeinen funktioniert die -print0GNU- findErweiterung jedoch auch in vielen anderen Szenarien, und Sie sollten auf jeden Fall lernen, sie zu verwenden.
Tripleee
2
@bug: Übrigens, ls $(command...)füttert die Liste nicht durch stdin. Es setzt die Ausgabe von $(command...)direkt in die Befehlszeile. In diesem Fall liest die Shell aus c und verwendet den aktuellen Wert von $IFS, um zu entscheiden, wie die Ausgabe in Wörter aufgeteilt wird. Im Allgemeinen ist es besser, wenn Sie verwenden xargs. Sie werden keinen Leistungstreffer bemerken.
rici
2
find -printf '"%p"\n'fügt jeden gefundenen Namen in doppelte Anführungszeichen ein, fügt jedoch keine doppelten Anführungszeichen in einen Dateinamen ein. Wenn Ihre Dateinamen keine Anführungszeichen enthalten, können Sie das Problem ignorieren: oder durchleiten sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Wenn Ihre Dateinamen von der Shell verarbeitet werden, sollten Sie wahrscheinlich einfache Anführungszeichen anstelle von doppelten Anführungszeichen verwenden (andernfalls sweet$HOMEwird dies zu etwas ähnlichem sheet/home/you). Und dies ist immer noch nicht sehr robust gegenüber Dateinamen mit Zeilenumbrüchen. Wie wollen Sie damit umgehen?
Tripleee

Antworten:

18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Mit findUnterstützungen -print0und xargsUnterstützungen -0:

find . -type f -print0 | xargs -0 <command>

-0 Die Option weist xargs an, das ASCII-NUL-Zeichen anstelle des Leerzeichens zu verwenden, um die Dateinamen zu beenden (zu trennen).

Beispiel:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
cuonglm
quelle
Funktioniert nicht Wenn ich renne ls $(find . -maxdepth 1 -type f -print0 | xargs -0)bekomme ich ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
Bug
1
Haben Sie es so probiert, wie Gnouc es tatsächlich geschrieben hat? Wenn Sie darauf bestehen, versuchen Sie, das $(..)in doppelte Anführungszeichen zu setzen"$(..)"
Übelsuppe
3
@ Bug: Ihr Befehl ist falsch. Probier genau ich worte und lies die manpage von findund xargs.
Cuonglm
Ich verstehe, dann möchte ich wieder einen formatierten String, den ich direkt pfeifen könnte.
Bug
1
@ Bug: Verwenden Sie einfach xargs -0 <Ihr Programm>
Cuonglm
10

Mit -print0ist eine Option, aber nicht alle Programme unterstützen Nullbyte-getrennte Datenströme verwenden, so dass Sie verwenden müssen , werden xargsmit der -0für einige Dinge Option, wie Gnouc Antwort zur Kenntnis genommen.

Eine Alternative wäre, find's -execoder -execdirOptionen zu verwenden. Der erste der folgenden Befehle füttert die Dateinamen somecommandnacheinander, während der zweite eine Liste von Dateien enthält:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Möglicherweise ist es in vielen Fällen besser, Globbing zu verwenden. Wenn Sie eine moderne Shell haben (bash 4+, zsh, ksh), können Sie rekursives Globbing mit globstar( **) erhalten. In der Bash musst du folgendes einstellen:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

shopt -s globstar extglobIn meinem .bashrc steht eine Zeile, die besagt, dass dies für mich immer aktiviert ist (und erweiterte Globs, die auch nützlich sind).

Wenn Sie keine Rekursivität wünschen, verwenden Sie ./*.txtstattdessen einfach jeden * .txt im Arbeitsverzeichnis. findEs verfügt über einige sehr nützliche, fein abgestimmte Suchfunktionen und ist für Zehntausende von Dateien obligatorisch (an diesem Punkt stoßen Sie auf die maximale Anzahl von Argumenten der Shell). Für die tägliche Verwendung ist dies jedoch häufig nicht erforderlich.

Übelsuppe
quelle
Hey @evilsoup, was macht das {} in diesem Skript?
Ayusman
3

Persönlich würde ich die Suchaktion verwenden -exec, um diese Art von Problem zu lösen. Oder, falls erforderlich, xargswas eine parallele Ausführung ermöglicht.

Es gibt jedoch eine Möglichkeit find, eine bash-lesbare Liste von Dateinamen zu erstellen. Es überrascht nicht, dass es -execund bashinsbesondere eine Erweiterung des printfBefehls verwendet:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Während dies jedoch korrekt mit Shell-Escape-Zeichen versehene Wörter ausgibt, kann es nicht mit Shell-Escape-Zeichen verwendet werden $(...), da $(...)Anführungszeichen oder Escape-Zeichen nicht interpretiert werden. (Das Ergebnis von $(...)unterliegt der Wortteilung und der Pfadnamenerweiterung, sofern es nicht in Anführungszeichen gesetzt ist.) Folgendes wird also nicht das tun, was Sie wollen:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Was Sie tun müssten, ist:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Beachten Sie, dass ich keinen wirklichen Versuch unternommen habe, die obige Monstrosität zu testen.)

Aber dann können Sie auch Folgendes tun:

find ... -exec ls {} +
rici
quelle
Ich denke nicht, dass das lsSzenario den Anwendungsfall des OP angemessen erfasst, aber dies ist nur eine Spekulation, da uns nicht gezeigt wurde, was er tatsächlich erreichen möchte. Diese Lösung funktioniert tatsächlich sehr gut; Ich erhalte die (vage) erwartete Ausgabe für alle lustigen Dateinamen, die ich ausprobiert habe, einschließlichtouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
Tripleee
@triplee: Ich habe auch keine Ahnung, was OP machen will. Der einzige wirkliche Vorteil beim Erstellen der Zeichenfolge, an die die Übergabe erfolgen soll, evalbesteht darin, dass Sie sie noch nicht übergeben müssen eval. Sie können es in einem Parameter speichern und später verwenden, möglicherweise mehrmals mit verschiedenen Befehlen. OP gibt jedoch keinen Hinweis darauf, dass dies der Anwendungsfall ist (und wenn dies der Fall ist, ist es möglicherweise besser, die Dateinamen in ein Array
einzufügen
0
find ./  | grep " "

erhalten Sie die Dateien und Verzeichnisse enthält Leerzeichen

find ./ -type f  | grep " " 

erhalten Sie die Dateien enthält Leerzeichen

find ./ -type d | grep " "

erhalten Sie die Verzeichnisse enthält Leerzeichen

Kannan Kumarasamy
quelle
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
user283965
quelle
Dies ist eine interessante Antwort, aber keine Antwort auf diese Frage.
Scott