Wie kann ich auf elegante Weise eine Aktion für alle Dateien mit einer bestimmten Erweiterung in Unterordnern ausführen?


17

Meine derzeit beste Wette ist:

for i in $(find . -name *.jpg); do echo $i; done

Problem: behandelt keine Leerzeichen in Dateinamen.

Hinweis: Ich würde auch gerne eine grafische Methode verwenden, z. B. den Befehl "tree".


Können Sie nicht einfach Anführungszeichen setzen $i? Ich mache das und es funktioniert gut. Stimmt etwas mit diesem Ansatz nicht?
Umar Farooq Khawaja

Antworten:


26

Der kanonische Weg ist zu tun

find . -name '*.jpg' -exec echo {} \;

(Ersetzen \;durch +, um mehr als eine Datei echogleichzeitig zu übergeben.)

oder (GNU-spezifisch, obwohl einige BSDs es jetzt auch haben):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i

2
Wenn Sie mit xargs -0 , sollten Sie es mit Spiel finden -0
itsbruce

1
@ MichaelKjörling, kein Zitat {} macht keinen Unterschied. {} wird durch find erweitert, nicht durch die Shell. Eine Verbesserung wäre nicht zu gebrauchen, echowas Fluchtsequenzen wie \b(zumindest die Unix-konformen echos) erweitert.
Stéphane Chazelas

1
@sch Bist du sicher? Die findManpage wird find . -type f -exec file '{}' \;im EXAMPLESAbschnitt angezeigt . Vielleicht behandeln einige Muscheln Zahnspangen speziell.
QuasarDonkey

1
Zitate schaden nicht, machen aber keinen Unterschied. Alle '{}', \{\}, {}, "{}", '{'}werden von der Shell (was auch immer Bourne-artig Shell) mit einem Argumente erweitert zu finden , dass die beiden Zeichen „{“ und „}“. Ersetzen Sie "find" durch "echo find", oder printf '<%s>\n' findüberprüfen Sie dies noch einmal.
Stéphane Chazelas


2

Bessere Antworten wurden bereits gegeben.

Beachten Sie jedoch, dass Leerzeichen nicht das einzige Problem in dem von Ihnen angegebenen Code sind. Tabulator-, Zeilenvorschub- und Platzhalterzeichen sind ebenfalls ein Problem.

Mit

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Dann sind nur Zeilenumbrüche ein Problem.


1

Was Sie erwähnt haben, ist eines der grundlegenden Probleme, mit denen Menschen konfrontiert sind, wenn sie versuchen, Dateinamen zu lesen. Manchmal vergessen Personen mit begrenzten Kenntnissen und einem falschen Verständnis der Datei- und Ordnerstruktur, dass " In UNIX ist alles eine Datei ". Sie verstehen also nicht, dass sie auch mit Leerzeichen umgehen müssen, da der Dateiname aus zwei oder mehr Wörtern mit Leerzeichen bestehen kann.

Lösung: Eine der bekannteren Methoden hierfür ist ein sauberes Lesen .

Wir lesen hier alle vorhandenen Dateien und speichern sie in einer Variablen. Wenn wir das nächste Mal die gewünschte Verarbeitung durchführen, müssen wir diese Variable nur in Anführungszeichen setzen, damit die Dateinamen mit Leerzeichen erhalten bleiben. Dies ist eine der grundlegenden Methoden, die anderen Antworten der anderen hier funktionieren jedoch genauso gut.

SKRIPT :

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Hier lese ich die Dateien, indem ich ihnen den Pfad gebe, und was auch immer ich lese, ich halte sie in einer Variablen I, die ich zu einem späteren Zeitpunkt während der Verarbeitung zitiere, um die Leerzeichen zu erhalten, um sie richtig zu verarbeiten.

Hoffe das hilft dir irgendwie.


1
"Alles ist eine Datei" bezieht sich auf etwas anderes. Ihre Lösung ist GUN-spezifisch und unterstützt keine Backslash- oder Newline-Zeichen in Dateinamen. "while read" -Schleifen in Shells (IMO) sind oft ein Hinweis auf eine schlechte Shell-Skriptpraxis.
Stéphane Chazelas

@sch: huh, und ich dachte immer, es ist ok. Vielen Dank für den Hinweis. Ich muss es mir wahrscheinlich genauer ansehen.
The Dark Knight

Entschuldigung, ich meinte oben "GNU", nicht "GUN" (es ist das erste Mal, dass ich merke, dass es sich um Anagramme handelt, übrigens ...)
Stéphane Chazelas

1

Einfach mit findund bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

Auf diese Weise verwenden wir $1in bash, wie in einem einfachen Skript, nette Perspektiven, um fortgeschrittene (oder nicht fortgeschrittene) Aufgaben auszuführen.

Eine effizientere Methode (um zu vermeiden, dass für jede Datei eine neue Bash gestartet werden muss):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +

0

In der letzten Version von Bash können Sie die globstarOption verwenden:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Für einfache Aktionen können Sie die Schleife sogar ganz überspringen:

echo **/*.jpg

Während dies in zsh (woher das Feature stammt) und mit ksh93 (mit set -G) funktioniert , sollte es in bash nicht verwendet werden, da die bash-Version (genau wie GNU grep -r) grundlegend kaputt ist, da sie in symbolische Links zu Verzeichnissen (äquivalent zu find -Loder zshs) übergeht ***/*.jpg). Beachten Sie auch, dass im Gegensatz zu find Dotfiles nicht in Dotdirs absteigen (standardmäßig).
Stéphane Chazelas
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.