Buscar exec '{}' no está disponible después de>

8

Exec nos permite pasar todos los argumentos a la vez con {} +o pasarlos uno por uno con{} \;

Ahora digamos que quiero cambiar el nombre de todos los archivos JPEG , no hay problema para hacer esto:

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

Pero si necesito redirigir la salida, '{}'no es accesible después de la redirección.

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

Esto no funcionaria. Tendría que usar un bucle for, almacenar el resultado de find en una variable antes de usarlo. Admitámoslo, es engorroso.

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

Entonces, ¿hay una mejor manera?

Buzut
fuente
¿No >falta en el tercer ejemplo de código?
choroba
1
Incluso su primera línea no es estándar y, por lo tanto, no es portátil. Intente evitar las líneas de comando donde {}aparece en cadenas más largas, ya que estas cadenas no suelen expandirse.
schily
Ese primer ejemplo no hace lo que dices.
ctrl-alt-delor
1
Has solucionado tu pregunta: ya no la cambiaría.
ctrl-alt-delor
1
Por lo que vale, la razón principal por la que su redirección no funciona como deseaba es que es manejada por el shell desde el que inicia find, una vez, y aplicada al findcomando en sí. El {}no tiene un significado especial en ese contexto. La redirección no es un argumento para find, y ciertamente no es parte de la -execcláusula.
John Bollinger

Respuestas:

13

Puede usar bash -cdentro del find -execcomando y usar el parámetro posicional con el comando bash:

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

De esa manera {}está provisto $1.

El shantes de la {}cuenta la carcasa interior de su "nombre", la cadena que se utiliza aquí se utiliza en los mensajes de error, por ejemplo. Esto se discute más en esta respuesta en stackoverflow .

oliv
fuente
8

Tiene una respuesta ( https://unix.stackexchange.com/a/481687/4778 ), pero esta es la razón.

La redirección >, y también las tuberías |, y la $expansión, son realizadas por el shell antes de que se ejecute el comando. Por lo tanto, stdout se redirige a optimized_{}, antes de findcomenzar.

ctrl-alt-delor
fuente
3

Es necesario citar la redirección para evitar que el shell actual la interprete.
Pero citarlo también evitará que la salida del comando sea redirigida.
La solución conocida a esto es llamar a un shell:

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

En este caso, la redirección ( >) se cita en el shell actual y funciona correctamente dentro del shell llamado. El called_shellse utiliza como $0parámetro (el nombre) del shell hijo ( sh).

Eso funciona bien si se agrega un sufijo al nombre del archivo, pero no si usa un prefijo. Para que funcione un prefijo, debe eliminar ambos ./que encuentran el prefijo de los nombres de archivo con ${1#./}y utilizar la -execdiropción.

Usted puede (o no) desea utilizar la -inameopción para que los archivos nombrados *.JPGo *.JpGo también se incluyen otras variaciones.

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

Y, es posible que (o no) también desee llamar al shell una vez por directorio en lugar de una vez por archivo agregando un bucle ( for f do … ; done) y un +al final:

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

Y, finalmente, como cjpegpuede escribir directamente en un archivo, la redirección podría evitarse como:

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

cjpegtiene una opción que le permite escribir en un archivo con nombre, en lugar de la salida estándar. Si su versión de findadmite la -execdiropción, puede aprovecharla para hacer innecesaria la redirección.

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

Nota: en realidad, esto supone la versión BSD de find, que parece eliminar el ./nombre del archivo cuando se aplica {}. (O, por el contrario, GNU find agrega ./ al nombre. No hay un estándar para decir qué comportamiento es "correcto").

chepner
fuente
Si es findcompatible -execdir, puede usar eso en lugar de -exec. Hace que el comando se ejecute en el directorio donde se encontró el archivo y {}se convertirá en aa.jpglugar de ./t2/aa.jpg.
chepner
Aún en el error: cjpeg: can't open optimized_./aa.jpg.
Isaac
Hm, eso parece ser una diferencia entre GNU findy BSD find. (Los peligros de usar extensiones no estándar.)
chepner el
Un nombre de archivo sin un encabezado ./parece ser más propenso a errores. Se propone una solución razonable en esta respuesta .
Isaac
1

Cree un script cjq80:

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

Hazlo ejecutable

chmod u+x cjq80

Y úsalo en -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
choroba
fuente
Pensé en esto, pero no es muy útil. Especialmente porque tuve que incorporar esto en un proceso de compilación
Buzut
Simplemente agregue el script a otros scripts de compilación, me parece más legible que bash -c doblemente anidado.
choroba
Bueno, es cuestión de gustos. Ahora se especifica cada opción, por lo que si alguien encuentra el mismo problema, elegirá por sí mismo :)
Buzut