Esta respuesta viene en las siguientes partes:
- Uso básico de
-exec
- Utilizando
-execen combinación consh -c
- Utilizando
-exec ... {} +
- Utilizando
-execdir
Uso básico de -exec
La -execopció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 findsaber 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 findcomando.
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 *.txten o debajo del directorio actual. Luego probará si la cadena se helloproduce 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, catse ejecutará para enviar el contenido del archivo al terminal.
Cada uno -exectambién actúa como una "prueba" en los nombres de ruta encontrados por find, al igual que -typey lo -namehace. Si el comando devuelve un estado de salida cero (que significa "éxito"), se considera la siguiente parte del findcomando; de lo contrario, el findcomando 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
findcomando).
Utilizando -execen combinación consh -c
El comando que -execpuede ejecutarse está limitado a una utilidad externa con argumentos opcionales. -execNo 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 -cshell secundario.
Si bashse requieren funciones, úselas bash -cen lugar de sh -c.
sh -cse ejecuta /bin/shcon 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 -cpor 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 $0dentro del script, y si el shell interno genera un mensaje de error, lo colocará como prefijo con esta cadena.
El argumento applesestá disponible como $1en el script, y si hubiera habido más argumentos, entonces estos hubieran estado disponibles como $2, $3etc. También estarían disponibles en la lista "$@"(excepto de los $0cuales no serían parte de "$@").
Esto es útil en combinación con, -execya 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, $1estaría la cadena text, $2sería la cadena txty $3sería cualquier ruta que findhaya 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 fromy toen 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 -execcon find. Usando finden 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 findincluso antes de ejecutar la primera iteración del bucle.
Ver también:
Utilizando -exec ... {} +
Al ;final puede ser reemplazado por +. Esto hace findque 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í, findrecopilará los nombres de ruta resultantes y los ejecutará caten 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í, mvse ejecutará la menor cantidad de veces posible. Este último ejemplo requiere GNU mvde coreutils (que admite la -topció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 findvariantes, pero no es una opción estándar).
Esto funciona como -execla 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 findseguirá prefijando el nombre de base con ./BSD). findNo haré eso).
Ejemplo:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Esto moverá cada *.txtarchivo encontrado a un done-textssubdirectorio 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, -execya 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-textsdirectorio correctamente.
Con -execdir, algunas cosas como estas se vuelven más fáciles.
La operación correspondiente usando en -execlugar de -execdirtendrí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 {} +