trouver exec '{}' non disponible après>

8

Exec nous permet de passer tous les arguments à la fois avec {} +ou de les passer un par un avec{} \;

Maintenant, disons que je veux renommer tous les fichiers jpeg , pas de problème en faisant ceci:

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

Mais si j'ai besoin de rediriger la sortie, '{}'n'est pas accessible après la redirection.

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

Ça ne marcherait pas. Je devrais utiliser une boucle for, stockant la sortie de find dans une variable avant de l'utiliser. Admettons-le, c'est lourd.

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

Alors, y a-t-il une meilleure façon?

Buzut
la source
N'y a-t-il pas un >manquant dans le troisième exemple de code?
choroba
1
Même votre première ligne n'est pas standard et donc non portable. Essayez d'éviter les lignes de commande où {}apparaît dans une chaîne plus longue car ces chaînes ne sont généralement pas développées.
schily
Ce premier exemple ne fait pas ce que vous dites.
ctrl-alt-delor
1
Vous avez corrigé votre question: je ne la changerais plus.
ctrl-alt-delor
1
Pour ce que ça vaut, la raison principale pour laquelle votre redirection ne fonctionne pas comme vous le souhaitiez est qu'elle est gérée par le shell à partir duquel vous lancez find, une fois, et appliquée à la findcommande elle-même. Le {}n'a pas de signification particulière dans ce contexte. La redirection n'est pas un argument findet ne fait certainement pas partie de la -execclause.
John Bollinger

Réponses:

13

Vous pouvez utiliser bash -cdans la find -execcommande et utiliser le paramètre positionnel avec la commande bash:

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

De cette façon {}est fourni avec $1.

L' shavant le {}dit au shell interne son "nom", la chaîne utilisée ici est utilisée par exemple dans les messages d'erreur. Ceci est discuté plus dans cette réponse sur stackoverflow .

oliv
la source
8

Vous avez une réponse ( https://unix.stackexchange.com/a/481687/4778 ), mais voici pourquoi.

La redirection >, ainsi que les tuyaux |et l' $expansion, sont tous effectués par le shell avant l'exécution de la commande. Par conséquent, stdout est redirigé vers optimized_{}, avant le finddémarrage.

ctrl-alt-delor
la source
3

La redirection doit être citée pour éviter que le shell actuel ne l'interprète.
Mais le citer évitera également la sortie de la commande à rediriger.
La solution connue à cela est d'appeler un shell:

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

Dans ce cas, la redirection ( >) est citée sur le shell actuel et fonctionne correctement à l'intérieur du shell appelé. Le called_shellest utilisé comme $0paramètre (le nom) du shell enfant ( sh).

Cela fonctionne bien si un suffixe est ajouté au nom du fichier, mais pas si vous utilisez un préfixe. Pour qu'un préfixe fonctionne, vous devez à la fois supprimer le préfixe ./qui trouve les noms de fichiers avec ${1#./}et utiliser l' -execdiroption.

Vous pouvez (ou non) à utiliser l' -inameoption de sorte que les fichiers nommés *.JPGou *.JpGou d' autres variations sont également inclus.

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

Et, vous pouvez (ou non) également appeler le shell une fois par répertoire au lieu d'une fois par fichier en ajoutant un loop ( for f do … ; done) et un +à la fin:

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

Et, enfin, comme il cjpegest possible d'écrire directement dans un fichier, la redirection pourrait être évitée comme:

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

cjpega une option qui vous permet d'écrire dans un fichier nommé, plutôt que sur une sortie standard. Si votre version de findprend en charge l' -execdiroption, vous pouvez en profiter pour rendre la redirection inutile.

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

Remarque: cela suppose en fait la version BSD de find, qui semble ./supprimer le début du nom de fichier lors de l'extension {}. (Ou à l'inverse, GNU find ajoute ./ au nom. Il n'y a pas de norme pour dire quel comportement est "correct".)

chepner
la source
Si vos findsupports -execdir, vous pouvez utiliser cela au lieu de -exec. Il provoque l'exécution de la commande dans le répertoire où le fichier a été trouvé et {}deviendra à la aa.jpgplace de ./t2/aa.jpg.
chepner
Toujours en erreur: cjpeg: can't open optimized_./aa.jpg.
Isaac
Hm, cela semble être une différence entre GNU findet BSD find. (Les dangers de l'utilisation d'extensions non standard.)
Chepner
Un nom de fichier sans interligne ./semble plus sujet aux erreurs. Une solution raisonnable est proposée dans cette réponse .
Isaac
1

Créez un script cjq80:

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

Rendez-le exécutable

chmod u+x cjq80

Et utilisez-le dans -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
choroba
la source
J'avais pensé à ce sujet, mais ce n'est pas très pratique. Surtout que je devais intégrer cela dans un processus de construction
Buzut
Ajoutez simplement le script à d'autres scripts de build, je le trouve plus lisible que bash -c doublement imbriqué.
choroba
Eh bien, c'est une question de goût. Maintenant, chaque option est spécifiée, donc si quelqu'un rencontre le même problème, il choisira pour lui-même :)
Buzut