Me encuentro constantemente buscando la sintaxis de

find . -name "FILENAME" -exec rm {} \; 

principalmente porque no veo cómo funciona exactamente la parte -exec. ¿Cuál es el significado de las llaves, la barra invertida y el punto y coma? ¿Existen otros casos de uso para esa sintaxis?

Comentarios

  • @Philippos: Veo tu punto. Por favor, ten en cuenta que las páginas de manual son una referencia, es decir, útiles para aquellos con una comprensión del asunto para buscar la sintaxis. Para alguien nuevo en el tema, a menudo son demasiado crípticos y formales para ser útiles. Encontrará que la respuesta aceptada es aproximadamente 10 veces más larga que la entrada de la página de manual, y que ' s por una razón.
  • Incluso la antigua página POSIX man lee un argumento o nombre de utilidad que contenga solo los dos caracteres " {} " se reemplazarán d por el nombre de ruta actual , que me parece suficiente. Además, tiene un ejemplo con -exec rm {} \;, como en su pregunta. En mis días, apenas había otros recursos que la " gran pared gris ", libros impresos man páginas (el papel era más barato que el almacenamiento). Entonces sé que esto es suficiente para alguien nuevo en el tema. Sin embargo, es justo hacer su última pregunta aquí. Desafortunadamente, ni @Kusalananda ni yo tenemos una respuesta para eso.
  • @Philippos 🙂 Oh, eso sería algo emocionante de probar, para un nivel de " emocionante " que está fuera de la escala de mi gráfico.
  • Comeon @Philippos. ¿De verdad le estás diciendo a Kusalananda que no mejoró la página de manual? 🙂
  • @ZsoltSzilagy No dije eso ni quise decir eso. Te alimentó muy bien, solo creo que tienes la edad suficiente para comer solo. (-;

Respuesta

Esta respuesta se divide en las siguientes partes:

  • Uso básico de -exec
  • Usando -exec en combinación con sh -c
  • Usando -exec ... {} +
  • Usando -execdir

El uso básico de -exec

La opción -exec toma una utilidad externa con argumentos opcionales como su argumento y lo ejecuta.

Si la cadena {} está presente en cualquier lugar del comando dado, cada instancia será reemplazada por el nombre de la ruta que se está procesando actualmente ( por ejemplo, ./some/path/FILENAME). En la mayoría de los shells, los dos caracteres {} no necesitan estar entrecomillados.

El comando debe terminarse con un ; para que find sepa dónde termina (ya que puede haber más opciones después s). Para proteger el ; del shell, debe citarse como \; o ";" , de lo contrario, el shell lo verá como el final del comando find.

Ejemplo (el \ en el el final de las dos primeras líneas son solo para continuaciones de línea):

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";" 

Esto encontrará todos los archivos normales (-type f) cuyos nombres coinciden con el patrón *.txt en o debajo del directorio actual. Luego probará si la cadena hello ocurre en cualquiera de los archivos encontrados usando grep -q (que no produce ninguna salida, solo una salida estado). Para aquellos archivos que contienen la cadena, cat se ejecutará para enviar el contenido del archivo a la terminal.

Cada -exec también actúa como una «prueba» en los nombres de ruta encontrados por find, al igual que -type y -name lo hace. Si el comando devuelve un estado de salida cero (que significa «éxito»), se considera la siguiente parte del comando find; de lo contrario, el find El 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 usos más comunes casos de -exec:

  1. Como prueba para restringir aún más la búsqueda.
  2. Para realizar algún tipo de acción en el nombre de ruta (generalmente, pero no necesariamente, al final del comando find).

Usando -exec en combinación con sh -c

El comando que -exec puede ejecutar está limitado a una utilidad externa con argumentos opcionales.No es posible utilizar elementos integrados de shell, funciones, condicionales, canalizaciones, redirecciones, etc. directamente con -exec, a menos que esté envuelto en algo como sh -c shell secundario.

Si se requieren funciones bash, utilice bash -c en lugar de sh -c.

sh -c ejecuta /bin/sh con un script proporcionado en la línea de comando, seguido de argumentos de línea de comando opcionales para ese script.

Un ejemplo simple de usar 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 secundario. Estos se colocarán en $0 y $1 para que los utilice el script.

  1. El cadena sh. Estará disponible como $0 dentro del script, y si el shell interno genera un mensaje de error, lo antepondrá con esta cadena.

  2. El argumento apples está disponible como $1 en el script, y si hubiera más argumentos, estos habrían estado disponibles como $2, $3 etc. También estarían disponibles en la lista "$@" (excepto $0 que no sería 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 determinado sufijo de nombre de archivo y cambie ese sufijo de nombre de archivo a otro sufijo, donde los sufijos se guardan en las 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 inte secuencia de comandos rnal, $1 sería la cadena text, $2 sería la cadena txt y $3 serían cualquier nombre de ruta que find haya encontrado para nosotros. La expansión del parámetro ${3%.$1} tomaría el nombre de 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 secuencia de comandos interna:

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 desde -exec con find. Usar find en un bucle como

for pathname in $( find ... ); do 

es propenso a errores y poco elegante (opinión personal). Divide los nombres de los archivos en espacios en blanco, invoca la agrupación de nombres de archivos y también obliga al shell a expandir el resultado completo de find incluso antes de ejecutar la primera iteración del ciclo.

Consulte también:


Usando -exec ... {} +

El ; al final puede ser reemplazado por +. Esto hace que find 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 + para que esto funcione .

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} + 

Aquí, find recopilará los nombres de ruta resultantes y ejecutará cat en tantos de ellos como sea posible a la vez.

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} + 

De la misma manera, aquí, mv se ejecutará como pocas veces como sea posible. Este último ejemplo requiere GNU mv de coreutils (que admite la opción -t).

Usando -exec sh -c ... {} + también es una forma eficiente de recorrer un conjunto de rutas 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 recorriendo "$@" dentro del script.

Nuestro ejemplo de la última sección que cambia los sufijos del 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" {} + 

Usando -execdir

También hay -execdir ( implementado por la mayoría de find variantes, pero no es una opción estándar).

Esto funciona como -exec con la diferencia de que el comando de shell dado se ejecuta con el directorio de la ruta encontrada como su directorio de trabajo actual y que {} contendrá el nombre base de la ruta encontrada sin su ruta (pero GNU find seguirá prefijando el nombre base con ./, mientras que BSD find no lo hará).

Ejemplo:

find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \; 

Esto moverá cada *.txt -file encontrado a un done-texts subdirectorio preexistente en el mismo directorio donde estaba el archivo. encontrado . También se cambiará el nombre del archivo agregando el sufijo .done.

Esto sería un poco más complicado de hacer con -exec ya que tendríamos que obtener el nombre base del archivo encontrado de {} para formar el nuevo nombre del archivo. También necesitamos el nombre del directorio de {} para ubicar el directorio done-texts correctamente.

Con -execdir, algunas cosas como estas se vuelven más fáciles.

La operación correspondiente usando -exec en lugar de -execdir tendría que emplear un shell secundario:

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 {} + 

Comentarios

  • -exec toma un programa y argumentos y lo ejecuta; algunos comandos de shell constan solo de un programa y argumentos, pero muchos no. Un comando de shell puede incluir redireccionamiento y canalización; -exec no puede (aunque el find completo puede ser redirigido). Un comando de shell puede usar ; && if etc; -exec no puede, aunque -a -o puede hacer algo. Un comando de shell puede ser un alias o una función de shell, o incorporado; -exec no puede. Un comando de shell puede expandir vars; -exec no puede (aunque el shell externo que ejecuta find sí). Un comando de shell puede sustituir $(command) de forma diferente cada vez; -exec no puede. …
  • Decirlo ' un comando de shell es incorrecto aquí, find -exec cmd arg \; doesn ' Para invocar un shell para interpretar una línea de comandos de shell, ejecuta execlp("cmd", "arg") directamente, no execlp("sh", "-c", "cmd arg") (para lo cual el shell terminaría haciendo el equivalente de execlp("cmd", "arg") si cmd no estuviera integrado).
  • Podría aclarar que todos los find argumentos después de -exec y hasta ; o + crea el comando para ejecutar junto con sus argumentos, con cada instancia de un argumento {} reemplazado con el archivo actual (con ;) y {} como último argumento antes de + reemplazado con una lista de archivos como argumentos separados (en el {} + caso). IOW -exec toma varios argumentos, terminados por un ; o {} +.
  • @Kusalananda No ' su último ejemplo también funciona con este comando más simple: find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'?
  • @Atralb Sí, eso también habría funcionado y tuvo el mismo efecto que la última pieza de código, pero en lugar de ejecutar mv en un bucle, una vez por archivo encontrado, ejecuta sh y mv para cada archivo encontrado, que será notablemente más lento para grandes cantidades de archivos.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *