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
Respuesta
Esta respuesta se divide en las siguientes partes:
- Uso básico de
-exec
- Usando
-exec
en combinación consh -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
:
- Como prueba para restringir aún más la búsqueda.
- 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.
-
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. -
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:
- ¿Por qué el bucle sobre el resultado de find ' s es una mala práctica?
- ¿Es posible utilizar `find -exec sh -c` de forma segura?
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 elfind
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 ejecutafind
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, ejecutaexeclp("cmd", "arg")
directamente, noexeclp("sh", "-c", "cmd arg")
(para lo cual el shell terminaría haciendo el equivalente deexeclp("cmd", "arg")
sicmd
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, ejecutash
ymv
para cada archivo encontrado, que será notablemente más lento para grandes cantidades de archivos.
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 impresosman
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.