find es un utilitario de UNIX® muy antiguo. Su rol es recorrer recursivamente uno o más directorios y encontrar archivos que se correspondan con un cierto conjunto de criterios en esos directorios. Aunque es muy útil, su sintaxis es verdaderamente arcana, y usarlo requiere cierta práctica. La sintaxis general es:
find [opciones] [directorios] [criterio] [acción] |
Si no especifica directorio alguno, find buscará en el directorio corriente. Si no especifica el criterio, esto es equivalente a “verdadero”, por lo que se encontrarán todos los archivos. Las opciones, criterios y acciones son tan numerosas que solo mencionaremos algunas de cada una. Comencemos por las opciones:
-xdev: No extender la búsqueda a los directorios ubicados en otros sistemas de archivos.
-mindepth <n>: Descender al menos <n> niveles bajo el directorio especificado antes de comenzar a buscar los archivos.
-maxdepth <n>: Buscar los archivos que se encuentran a lo sumo n niveles bajo el directorio especificado.
-follow: Seguir los vínculos simbólicos si apuntan a directorios. Predeterminadamente, find no los sigue.
-daystart: Cuando se usan las pruebas relativas a la fecha y la hora (ver debajo), toma el comienzo del día corriente como etiqueta temporal en vez del predeterminado (24 horas antes de la hora corriente).
Un criterio puede ser una o más de varias pruebas atómicas; algunas pruebas útiles son:
-type <tipo_archivo>: Busca los archivos de un tipo dado. tipo_archivo puede ser uno de: f (archivo regular), d (directorio), l (vínculo simbólico), s (socket), b (archivo en modo de bloques), c (archivo en modo caracter) o p (tubería nombrada).
-name <patrón>: Encontrar los archivos cuyo nombre se corresponde con el patrón dado. Con esta opción, se trata al patrón como un patrón de englobamiento del shell (consulte “Patrones de englobamiento del shell”).
-iname <patrón>: Como -name, pero sin tener en cuenta la capitalización.
-atime <n>, -amin <n>: Encontrar los archivos a los que se ha accedido por última vez hace n días (-atime) o hace n minutos (-amin). También puede especificar <+n> o <-n>, en cuyo caso la búsqueda se hará para los archivos accedidos hace al menos o a lo sumo n días/minutos.
-anewer <un_archivo>: Encontrar los archivos que han sido accedidos más recientemente que el archivo un_archivo.
-ctime <n>, -cmin <n>, -cnewer <archivo> Igual que para -atime, -amin y -anewer, pero se aplica a la última fecha en la cual se modificó el contenido del archivo.
-regex <patrón>: Como -name, pero patrón se trata como una expresión regular.
-iregex <patrón>: Como -regex, pero sin distinguir entre mayúsculas y minúsculas.
Existen muchas otras pruebas, debe consultar find(1) para más detalles. Para combinar las pruebas, Usted puede utilizar uno de:
<c1> -a <c2>: Verdadero si tanto c1 como c2 son verdaderas; -a está implícito, por lo tanto puede ingresar <c1> <c2> <c3> si quiere que todas las pruebas c1, c2 y c3 sean verdaderas.
<c1> -o <c2>: Verdadero si c1 o c2 o ambos son verdaderos. Note que -o tiene una precedencia menor que -a, por lo tanto si desea, por ejemplo, los archivos que verifican los criterios c1 o c2 y verifican el criterio c3, tendrá que usar paréntesis y escribir ( <c1> -o <c2> ) -a <c3>. Debe escapar (desactivar) los paréntesis, ya que si no lo hace ¡el shell los interpretará!
-not <c1>: Invertir la prueba c1, por lo tanto -not <c1> es verdadero si c1 es falso.
Finalmente, puede especificar una acción para cada archivo encontrado. Las acciones más usadas frecuentemente son:
-print: Simplemente imprime el nombre de cada archivo en la salida estándar. Esta es la acción predeterminada.
-ls: Imprime en la salida estándar el equivalente de ls -ilds para cada archivo que encuentra.
-exec <línea_de_comandos>: Ejecutar el comando línea_de_comandos sobre cada archivo encontrado. La línea de comandos línea_de_comandos debe terminar con un ;, que deberá desactivar para que el shell no lo interprete; la posición del archivo se representa con {}. Vea los ejemplos de uso para entender mejor esto.
-ok <comando>: Igual que -exec pero pedir confirmación para cada comando.
¿Todavía está aquí? Está bien, ahora practiquemos un poco, ya que todavía es la mejor forma de entender a este monstruo. Digamos que quiere encontrar todos los directorios en /usr/share. Entonces ingresará:
find /usr/share -type d |
Suponga que tiene un servidor HTTP, todos sus archivos HTML están en /var/www/html, que coincide con su directorio corriente. Usted desea encontrar todos los archivos que no se modificaron en el último mes. Debido a que tiene páginas de varios autores, algunos archivos tienen la extensión html y otros la extensión htm. Desea vincular estos archivos en el directorio /var/www/obsolete. Entonces ingresará[19]:
find \( -name "*.htm" -o -name "*.html" \) -a -ctime -30 \ -exec ln {} /var/www/obsolete \; |
Está bien, este es uno un poco complejo y requiere una pequeña explicación. El criterio es este:
\( -name "*.htm" -o -name "*.html" \) -a -ctime -30 |
que hace lo que queremos: encuentra todos los archivos cuyos nombres terminan con .htm o con .html “\( -name "*.htm" -o -name "*.html" \)”, y (-a) que no han sido modificados en los últimos 30 días, lo que es más o menos un mes (-ctime -30) Note los paréntesis: aquí son necesarios, porque -a tiene una precedencia mayor. Si no hubiera paréntesis alguno, se hubieran encontrado todos los archivos que terminen con .htm, y todos los archivos que terminen con .html y que no han sido modificados por un mes, que no es lo que nosotros queremos. Note también que los paréntesis están desactivados para el shell: si hubiésemos puesto ( .. ) en vez de \( .. \), el shell los hubiese interpretado y tratado de ejecutar -name "*.htm" -o -name "*.html" en un subshell... Otra solución podría haber sido poner los paréntesis entre comillas simples o dobles, pero aquí es preferible una contrabarra ya que simplemente tenemos que aislar un caracter solo.
Y finalmente, está el comando a ejecutar para cada uno de los archivos:
-exec ln {} /var/www/obsolete \; |
Aquí también, tiene que desactivar el ; para el shell, ya que de no ser así el shell lo interpretaría como un separador de comandos. Si no lo hace, find se quejará de que le falta un argumento a -exec.
Un último ejemplo: tiene un directorio enorme denominado /shared/images, con todo tipo de imágenes en él. Regularmente, Usted usa el comando touch para actualizar la fecha de un archivo denominado stamp en este directorio, para que tenga una referencia temporal. Usted quiere encontrar todas las imágenes JPEG en el mismo que son más nuevas que el archivo stamp, y ya que Usted obtuvo las imágenes de varias fuentes, estos archivos tienen las extensiones jpg, jpeg, JPG o JPEG. También quiere evitar buscar en el directorio old. Quiere que se le envíe la lista de estos archivos por correo electrónico, y su nombre de usuario es peter:
find /shared/images -cnewer \ /shared/images/stamp \ -a -iregex ".*\.jpe?g" \ -a -not -regex ".*/old/.*" \ | mail peter -s "Imágenes nuevas" |
Por supuesto, este comando no es muy útil si tiene que ingresarlo cada vez, y quisiera ejecutarlo regularmente... Puede programar la ejecución de comandos.
[19] Note que este ejemplo necesita que /var/www y /var/www/obsolete ¡estén en el mismo sistema de archivos!