Linux

Find

¿Cuántas veces abrimos el explorador de archivos y nos pusimos a buscar una imagen o un documento específico? No recordábamos dónde estaba, entonces abrimos carpetas, revisamos en espacios que hacía meses no pasábamos, hasta que luego de unos minutos, frustrados o resignados, decidimos dejar la búsqueda para otro momentos. Luego pasa lo que siempre pasa, en el momento menos pensado, aparece el documento sin que lo busquemos.

La solución no es únicamente ser más organizados, lo cual es útil si nos pasamos el 100% del tiempo trabajando en una computadora, ya que, seamos realistas, la organización inicial que nos propusimos suele ser rápidamente desbordada por la cantidad descomunal de archivos que podemos generar en un solo día. Entonces, la solución tiene que venir por otro camino que nos permita continuar avanzando con el trabajo aunque estemos desbordados de archivos.

Find es una herramienta imprescindible para buscar archivos y directorios en sistemas UNIX/LINUX por medio de sus atributos como nombre, tamaño, fecha, etc. Se trata de uno de los comandos más antiguos del ecosistema Unix. Desarrollado por Eric Schmidt (Quién años más tarde sería el CEO de Google) mientras trabajaba en los laboratorios Bell de AT&T, el comando se introduce oficialmente en la versión 7 de Unix en 1979.

El motivo de su creación

La creación de find se hizo necesaria cuando el sistema de archivos de Unix comenzó a crecer y a estructurarse en forma de árbol, lo que volvió inviable la búsqueda de un archivo recorriendo carpetas. Schmidt diseña find bajo la filosofía que en Unix lideraba todo desarrollo: una herramienta que se enfoque en realizar una sola cosa, pero con la capacidad de poder evaluar expresiones lógicas complejas sobre los atributos de los archivos.

Es escrito durante finales de la década de 1979, en el lenguaje C, mientras Schmidt era aún un estudiante de postgrado y pasante en los laboratorios Bell. Lo interesante de cómo Schmidt encara el desarrollo de find es que no lo pensé simplemente como un buscador de archivos, sino que lo pénsé como un evaluador de expresiones booleanas en tiempo de ejecución.

La lógica detrás del código de Schmidt

Para que find fuera eficiente en los sistemas de la época, que contaban con recursos limitados, Schmidt implementó una estructura de evaluación de izquierda a derecha:

find . -name "*.txt" -type f

El programa no busca todo y después filtra, sino que el código evalúa los flags como operadores lógicos de tipo AND. Si la primera condición (-name "*.txt") es falsa para un archivo, el motor de find aplica un corte, descartando el archivo inmediatamente y sin perder tiempo en si es un archivo o directorio (-type f).

Uso de Find

find no funciona como la mayoría de los programas de terminal en el que reciben un listado de archivos y les aplica un cambio, en este caso, se trata de un motor de evaluación. Recorre un árbol de directorios y, por cada objeto que encuentra, evalúa una expresión de izquierda a derecha.

find, al momento de usarse, espera tres componentes esenciales:

find [ruta] [expresión] [acción]

La ruta

El primer componente, se trata del punto de partida. En caso de no tener ninguna, asume el directorio actual (.).

  • Podemos pasarle un ruta relativa: find src/
  • un absoluta: find /var/log/
  • O varias a la vez: find src/ bin/

El tipo -type

Antes de evaluar por los atributos del archivo, primero debe entender qué está evaluando. Como vimos anteriormente, en Linux (y por lo tanto en Unix) todo es un archivo, aunque hay sutiles diferencias. El flag -type le dice a find qué nodos del árbol nos interesan:

  • -type f: Archivo regular (un .txt, un binario, un script).
  • -type d Directorio (carpetas).
  • -type l: Enlaces simbólicos (symlink).

Si ejecutamos el siguiente comando, listaremos todos los archivos regulares del directorio actual . y sus subdirectorios:

find . -type f

El nombre -name y -iname

En este componente es donde se filtra por la cadena de texto del nombre del archivo.

  • -name: Respeta estrictamente mayúsculas y minúsculas (Case-Senstive).
  • iname: Ignora mayúsculas y minúsculas (Case-Insentitive). El uso de comillas únicamente es obligatorio para utilizar el comodín *. Caso contrario, el shell intenta expandir el * antes de que el comando find se ejecute, rompiendo la lógica.
find . -type f -name "*.config"

Práctica: Antes de seguir, comenzá a probar este comando con -name o -iname en tu propio directorio para ver qué resultados obtenés. Podés ir variado los -type e inclusive las rutas.

Conectar atributos

Por defecto, cuando se colocan dos flags juntos, find aplica un operador lógico AND implícito. Ambos tienen que ser verdaderos para que el archivos aparezca.

Profundidad -maxdepth

find, de por sí, es recursivo. Es significa que si encuentra una carpeta, se mete hasta el fondo del árbol. Con -maxdepth controlamos ese comportamiento:

find . -maxdepth 1 -type f -name "*.sh"

Este flag siempre va al inicio de la expresión, justo después de la ruta en la queremos buscar. Por ejemplo, si colocamos -maxdepth 1 estaremos buscando el archivo únicamente en el directorio actual, sin meterse en subcarpetas. Si lo cambiamos a -maxdepth 2, buscará en el directorio actual, pero también lo hará en el primer nivel de subcarpetas.

Permisos -perm

En Unix, los permisos son atributos críticos, los cuales podemos buscar utilizando la notación octal (Para lectura/escritura del duelo es 664, para ejecutables 755). Por ejemplo, si buscamos scripts que sean estrictamente ejecutables para todos:

find . -type f -name "*.sh" -perm 755

Concatenación explícita

Aunque mencionamos que AND es el comportamiento por default, Schmidt diseñó operadores para cuando la lógica necesita ser más flexible:

  • Operdor NOT: Se ejecuta utilizando -not o ! y sirve para invertir la condición. Si, por ejemplo buscamos todos los scripts que sean ejecutables pero no sean .sh:
    find . -type f -perm 755 -not -name "*.sh"
    
  • Operador OR: Se utiliza -o y se ejecuta cuando queremos que se cumpla una condición o la otra. En este caso se suelen usar paréntesis para agrupar, pero en shell hay que primero colocar el carácter de escape con la barra invertida \( y \) para que no sean interpretado por Bash. Si buscamos archivos que sean .txt o .md podemos hacer:
    find . -type f \( -name "*.txt" -o -name "*-md"\)
    

Tamaño -size

Este flag nos permite poder buscar archivos según su peso en disco. Para esto utilizamos modificadores como k para kilobytes, M para megabytes y G para gigabytes. Y para buscar dentro de un rango, utilizamos + para indicar si tiene que ser superior a un valor o inferior a otro:

find . -type f -size +10M

Para buscar archivos mayores de 10 megabytes.

find . -type f -size -50k

Para buscar archivos menos a 50 kilobytes.

Tiempo -mmin o -mtime

Cuando necesitamos buscar archivos en un determinado tiempo, podemos indicar el tiempo de su última modificación.

  • -mmin: para colocar el tiempo en minutos.
  • -mtime: para colocar el tiempo en días. Al igual que vimos con el tiempo podemos utilizar el signo + para indicar que busca en “desde hace mas de” o - para buscar archivos modificados “desde hace menos de”
    find . -type f -mmin -20
    

    Para listar los archivos modificado hace menos de 20 minutos.

    find . -type f -mtime +5
    

    Nos lista los archivos modificados hace más de 5 días.

Combinando los atributos

Ahora que conocemos muchos atributos, al menos los más importantes, podemos combinarlos utilizando el AND implícito que tiene find:

find . -maxdepth 2 -type f -name "*.log" -size +1M -mmin -60

Lo que hacemos con este comando es listar todos los archivos hasta el segundo nivel de subcarpetas, siempre que sean regulares, sean con una extensión .log, que pesen más de 1 megabyte y hayan sido modificados hace menos de 60 minutos.