find exec '{}' ist nach> nicht verfügbar

8

Mit Exec können wir entweder alle Argumente gleichzeitig mit {} +oder einzeln übergeben{} \;

Nehmen wir jetzt an, ich möchte alle JPEGs umbenennen , kein Problem dabei:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec mv '{}' '{}'.new \;

Wenn ich jedoch eine Ausgabe umleiten muss, kann ich nach der Umleitung '{}'nicht darauf zugreifen.

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjpeg -quality 80 '{}' > optimized_'{}' \;

Das würde nicht funktionieren. Ich müsste eine for-Schleife verwenden und die Ausgabe von find in einer Variablen speichern, bevor ich sie verwenden kann. Lassen Sie es uns zugeben, es ist umständlich.

for f in `find . \( -name '*.jpg' -o -name '*.jpeg' \)`; do cjpeg -quality 80 $f > optimized_$f; done;

Gibt es einen besseren Weg?

Buzut
quelle
Ist das nicht ein >in der dritten Codebeispiel fehlt?
Choroba
1
Sogar Ihre erste Leitung ist nicht standardisiert und daher nicht tragbar. Vermeiden Sie Befehlszeilen, {}die in längeren Zeichenfolgen angezeigt werden, da solche Zeichenfolgen normalerweise nicht erweitert werden.
schily
Dieses erste Beispiel macht nicht das, was Sie sagen.
Strg-Alt-Delor
1
Sie haben Ihre Frage behoben: Ich würde sie nicht mehr ändern.
Strg-Alt-Delor
1
Der Hauptgrund dafür, dass Ihre Umleitung nicht wie gewünscht funktioniert, ist, dass sie von der Shell verarbeitet wird, von der aus Sie sie findeinmal starten und auf den findBefehl selbst anwenden . Das {}hat in diesem Zusammenhang keine besondere Bedeutung. Die Umleitung ist kein Argument dafür findund sicherlich nicht Teil der -execKlausel.
John Bollinger

Antworten:

13

Sie können bash -cinnerhalb des find -execBefehls den Positionsparameter mit dem Befehl bash verwenden:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec bash -c 'cjpeg -quality 80 "$1" > "$(dirname "$1")/optimized_$(basename "$1")"' sh {} \;

Auf diese Weise {}ist mit versehen $1.

Die shvor dem {}erzählt die innere Schale seinen „Namen“, verwendete die Zeichenfolge hier in zB Fehlermeldungen verwendet. Dies wird in dieser Antwort zum Stapelüberlauf näher erläutert .

oliv
quelle
8

Sie haben eine Antwort ( https://unix.stackexchange.com/a/481687/4778 ), aber hier ist der Grund.

Die Umleitung >sowie Pipes |und $Erweiterungen werden alle von der Shell ausgeführt, bevor der Befehl ausgeführt wird. Daher wird stdout umgeleitet optimized_{}, bevor findes gestartet wird.

Strg-Alt-Delor
quelle
3

Die Umleitung muss in Anführungszeichen gesetzt werden, um zu vermeiden, dass die vorliegende Shell sie interpretiert.
Durch das Zitieren wird jedoch auch vermieden, dass die Ausgabe des Befehls umgeleitet wird.
Die bekannte Lösung hierfür besteht darin, eine Shell aufzurufen:

find . -name '*.jpg' -exec sh -c 'echo "$1" >"$1".new' called_shell '{}' \;

In diesem Fall wird die Umleitung ( >) in der aktuellen Shell in Anführungszeichen gesetzt und funktioniert in der aufgerufenen Shell ordnungsgemäß. Das called_shellwird als $0Parameter (der Name) der untergeordneten Shell ( sh) verwendet.

Das funktioniert gut, wenn ein Suffix mit dem Namen der Datei hinzugefügt wird, aber nicht, wenn Sie ein Präfix verwenden. Damit ein Präfix funktioniert, müssen Sie sowohl das Präfix für ./Dateinamen entfernen als auch ${1#./}die -execdirOption verwenden.

Sie kann (oder auch nicht) will die verwenden -inameOption , so dass Dateien mit dem Namen *.JPGoder *.JpGoder andere Variationen sind ebenfalls enthalten.

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     cjpeg -quality 80 "$1" > optimized_"${1#./}"
     ' called_shell '{}' \;

Möglicherweise möchten Sie die Shell auch einmal pro Verzeichnis anstatt einmal pro Datei aufrufen (oder auch nicht), indem Sie am Ende eine Schleife ( for f do … ; done) und eine hinzufügen +:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" > optimized_"${f#./}"; done
     ' called_shell '{}' \+

Und schließlich, da cjpegdirekt in eine Datei geschrieben werden kann, könnte die Umleitung wie folgt vermieden werden:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" -outfile optimized_"${f#./}"; done
     ' called_shell '{}' \+
Isaac
quelle
3

cjpeghat eine Option, mit der Sie in eine benannte Datei anstatt in eine Standardausgabe schreiben können. Wenn Ihre Version von diese Option findunterstützt -execdir, können Sie dies nutzen, um die Umleitung unnötig zu machen.

find . \( -name '*.jpg' -o -name '*.jpeg' \) \
  -execdir cjpeg -quality 80 -outfile optimized_'{}' '{}' \;

Hinweis: Dies setzt tatsächlich die BSD-Version von voraus find, die ./beim Erweitern auf den führenden Namen vom Dateinamen zu entfernen scheint {}. (Oder umgekehrt find fügt GNU ./dem Namen hinzu. Es gibt keinen Standard, um zu sagen, welches Verhalten "richtig" ist.)

chepner
quelle
Wenn Ihre findUnterstützung -execdir, können Sie das anstelle von verwenden -exec. Der Befehl wird in dem Verzeichnis ausgeführt, in dem die Datei gefunden wurde, und {}wird aa.jpgstattdessen anstelle von ./t2/aa.jpg.
Chepner
Immer noch im Irrtum : cjpeg: can't open optimized_./aa.jpg.
Isaac
Hm, das scheint ein Unterschied zwischen GNU findund BSD zu sein find. (Die Gefahren der Verwendung von nicht standardmäßigen Erweiterungen.)
Chepner
Ein Dateiname ohne Lead ./scheint fehleranfälliger zu sein. In dieser Antwort wird eine vernünftige Lösung vorgeschlagen .
Isaac
1

Erstellen Sie ein Skript cjq80:

#!/bin/bash
cjpeg -quality 80 "$1" > "${1%/*}"/optimized_"${1##*/}"

Mach es ausführbar

chmod u+x cjq80

Und benutze es in -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
Choroba
quelle
Ich hatte zwar darüber nachgedacht, aber es ist nicht sehr praktisch. Zumal ich dies in einen Build-Prozess einbeziehen musste
Buzut
Fügen Sie das Skript einfach anderen Build-Skripten hinzu. Ich finde es besser lesbar als doppelt verschachteltes bash -c.
Choroba
Nun, es ist Geschmackssache. Jetzt ist jede Option festgelegt. Wenn also jemand auf dasselbe Problem stößt, wählt er es selbst aus :)
Buzut,