Esta respuesta viene en las siguientes partes:
- Uso básico de
-exec
- Utilizando
-exec
en combinación consh -c
- Utilizando
-exec ... {} +
- Utilizando
-execdir
Uso básico de -exec
La -exec
opción toma una utilidad externa con argumentos opcionales como argumento y la ejecuta.
Si la cadena {}
está presente en cualquier parte del comando dado, cada instancia de la misma será reemplazada por el nombre de ruta que se está procesando actualmente (por ejemplo ./some/path/FILENAME
). En la mayoría de los shells, {}
no es necesario citar los dos caracteres .
El comando debe terminarse con un ;
para find
saber dónde termina (ya que puede haber más opciones después). Para protegerlo ;
del shell, debe citarse como \;
o ';'
, de lo contrario, el shell lo verá como el final del find
comando.
Ejemplo ( \
al final de las dos primeras líneas son solo para continuar con las líneas):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Esto encontrará todos los archivos normales ( -type f
) cuyos nombres coincidan con el patrón *.txt
en o debajo del directorio actual. Luego probará si la cadena se hello
produce en alguno de los archivos encontrados grep -q
(lo que no produce ningún resultado, solo un estado de salida). Para aquellos archivos que contienen la cadena, cat
se ejecutará para enviar el contenido del archivo al terminal.
Cada uno -exec
también actúa como una "prueba" en los nombres de ruta encontrados por find
, al igual que -type
y lo -name
hace. Si el comando devuelve un estado de salida cero (que significa "éxito"), se considera la siguiente parte del find
comando; de lo contrario, el find
comando continúa con el siguiente nombre de ruta. Esto se usa en el ejemplo anterior para buscar archivos que contienen la cadena hello
, pero para ignorar todos los demás archivos.
El ejemplo anterior ilustra los dos casos de uso más comunes de -exec
:
- Como prueba para restringir aún más la búsqueda.
- Para realizar algún tipo de acción en el nombre de ruta encontrado (generalmente, pero no necesariamente, al final del
find
comando).
Utilizando -exec
en combinación consh -c
El comando que -exec
puede ejecutarse está limitado a una utilidad externa con argumentos opcionales. -exec
No es posible utilizar las funciones integradas de shell, funciones, condicionales, canalizaciones, redirecciones, etc. directamente , a menos que esté envuelto en algo así como un sh -c
shell secundario.
Si bash
se requieren funciones, úselas bash -c
en lugar de sh -c
.
sh -c
se ejecuta /bin/sh
con un script dado en la línea de comando, seguido de argumentos opcionales de la línea de comando para ese script.
Un ejemplo simple de uso sh -c
por sí mismo, sin find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Esto pasa dos argumentos al script de shell hijo:
La cadena sh
. Estará disponible $0
dentro del script, y si el shell interno genera un mensaje de error, lo colocará como prefijo con esta cadena.
El argumento apples
está disponible como $1
en el script, y si hubiera habido más argumentos, entonces estos hubieran estado disponibles como $2
, $3
etc. También estarían disponibles en la lista "$@"
(excepto de los $0
cuales no serían parte de "$@"
).
Esto es útil en combinación con, -exec
ya que nos permite crear scripts arbitrariamente complejos que actúan sobre los nombres de ruta encontrados por find
.
Ejemplo: busque todos los archivos normales que tengan un sufijo de nombre de archivo determinado y cambie ese sufijo de nombre de archivo a otro sufijo, donde los sufijos se mantienen en variables:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
Dentro de la secuencia de comandos interna, $1
estaría la cadena text
, $2
sería la cadena txt
y $3
sería cualquier ruta que find
haya encontrado para nosotros. La expansión del parámetro ${3%.$1}
tomaría la ruta y eliminaría el sufijo .text
.
O, usando dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
o, con variables agregadas en el script interno:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
Tenga en cuenta que en esta última variación, las variables from
y to
en el shell hijo son distintas de las variables con los mismos nombres en el script externo.
Lo anterior es la forma correcta de llamar a un script complejo arbitrario -exec
con find
. Usando find
en un bucle como
for pathname in $( find ... ); do
es propenso a errores y poco elegante (opinión personal). Está dividiendo nombres de archivos en espacios en blanco, invocando el bloqueo de nombres de archivo, y también obliga al shell a expandir el resultado completo find
incluso antes de ejecutar la primera iteración del bucle.
Ver también:
Utilizando -exec ... {} +
Al ;
final puede ser reemplazado por +
. Esto hace find
que se ejecute el comando dado con tantos argumentos (nombres de ruta encontrados) como sea posible en lugar de una vez para cada nombre de ruta encontrado. La cadena {}
debe aparecer justo antes +
de que esto funcione .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Aquí, find
recopilará los nombres de ruta resultantes y los ejecutará cat
en tantos como sea posible a la vez.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
Del mismo modo aquí, mv
se ejecutará la menor cantidad de veces posible. Este último ejemplo requiere GNU mv
de coreutils (que admite la -t
opción).
El uso -exec sh -c ... {} +
también es una forma eficiente de recorrer un conjunto de nombres de ruta con un script arbitrariamente complejo.
Lo básico es lo mismo que cuando se usa -exec sh -c ... {} ';'
, pero el script ahora toma una lista de argumentos mucho más larga. Estos se pueden recorrer en bucle "$@"
dentro del script.
Nuestro ejemplo de la última sección que cambia los sufijos de nombre de archivo:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
Utilizando -execdir
También existe -execdir
(implementado por la mayoría de las find
variantes, pero no es una opción estándar).
Esto funciona como -exec
la diferencia de que el comando de shell dado se ejecuta con el directorio del nombre de ruta encontrado como su directorio de trabajo actual y que {}
contendrá el nombre de base del nombre de ruta encontrado sin su ruta (pero GNU find
seguirá prefijando el nombre de base con ./
BSD). find
No haré eso).
Ejemplo:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Esto moverá cada *.txt
archivo encontrado a un done-texts
subdirectorio preexistente en el mismo directorio donde se encontró el archivo . El archivo también será renombrado añadiéndole el sufijo .done
.
Esto sería un poco más complicado de hacer, -exec
ya que tendríamos que obtener el nombre base del archivo encontrado {}
para formar el nuevo nombre del archivo. También necesitamos el nombre del directorio de {}
para localizar el done-texts
directorio correctamente.
Con -execdir
, algunas cosas como estas se vuelven más fáciles.
La operación correspondiente usando en -exec
lugar de -execdir
tendría que emplear un shell hijo:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
o,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +