Encontrar cosas

Última actualización: 2025-04-03 | Mejora esta página

Tiempo estimado: 45 minutos

Hoja de ruta

Preguntas

  • ¿Cómo puedo encontrar los archivos?
  • ¿Cómo puedo encontrar cosas en archivos?

Objetivos

  • Utilice grep para seleccionar líneas de archivos de texto que coincidan con patrones simples.
  • Utilice find para buscar archivos y directorios cuyos nombres coincidan con patrones sencillos.
  • Utilizar la salida de un comando como argumento(s) de otro comando.
  • Explique qué se entiende por ficheros ‘de texto’ y ‘binarios’, y por qué muchas herramientas comunes no manejan bien estos últimos.

Del mismo modo que muchos de nosotros utilizamos ahora “Google” como verbo que significa “encontrar”, los programadores de Unix utilizan a menudo la palabra “grep”. grep” es una contracción de “global/regular expression/print”, una secuencia de operaciones habitual en los primeros editores de texto de Unix. También es el nombre de un programa de línea de comandos muy útil.

grep encuentra e imprime las líneas de los archivos que coinciden con un patrón. Para nuestros ejemplos, utilizaremos un archivo que contiene tres haiku tomados de un concurso de 1998 en la revista Salon (Crédito a los autores Bill Torcaso, Howard Korder, y Margaret Segall, respectivamente. Véase Haiku Error Messsages archivado Página 1 y Página 2 .). Para este conjunto de ejemplos, vamos a trabajar en el subdirectorio de escritura:

BASH

$ cd
$ cd Desktop/shell-lesson-data/exercise-data/writing
$ cat haiku.txt

SALIDA

The Tao that is seen
Is not the true Tao, until
You bring fresh toner.

With searching comes loss
and the presence of absence:
"My Thesis" not found.

Yesterday it worked
Today it is not working
Software is like that.

Busquemos líneas que contengan la palabra “no”:

BASH

$ grep not haiku.txt

SALIDA

Is not the true Tao, until
"My Thesis" not found
Today it is not working

Aquí, not es el patrón que estamos buscando. El comando grep busca en el fichero las coincidencias con el patrón especificado. Para utilizarlo escriba grep, luego el patrón que estamos buscando y finalmente el nombre del archivo (o archivos) en el que estamos buscando.

La salida son las tres líneas del fichero que contienen las letras “no”.

Por defecto, grep busca un patrón distinguiendo entre mayúsculas y minúsculas. Además, el patrón de búsqueda que hemos seleccionado no tiene por qué formar una palabra completa, como veremos en el siguiente ejemplo.

Busquemos el patrón: ‘The’.

BASH

$ grep The haiku.txt

SALIDA

The Tao that is seen
"My Thesis" not found.

Esta vez se muestran dos líneas que incluyen las letras “The”, una de las cuales contiene nuestro patrón de búsqueda dentro de una palabra más grande, “Thesis”.

Para restringir las coincidencias a las líneas que contengan la palabra ‘El’ sola, podemos dar a grep la opción -w. Esto limitará las coincidencias a los límites de las palabras.

Más adelante en esta lección, también veremos cómo podemos cambiar el comportamiento de búsqueda de grep con respecto a su sensibilidad a mayúsculas y minúsculas.

BASH

$ grep -w The haiku.txt

SALIDA

The Tao that is seen

Tenga en cuenta que un “límite de palabra” incluye el principio y el final de una línea, es decir, no sólo las letras rodeadas de espacios. A veces no queremos buscar una sola palabra, sino una frase. También podemos hacerlo con grep poniendo la frase entre comillas.

BASH

$ grep -w "is not" haiku.txt

SALIDA

Today it is not working

Ya hemos visto que no es necesario entrecomillar las palabras sueltas, pero es útil utilizar comillas cuando se buscan varias palabras. También ayuda a distinguir más fácilmente entre el término o frase de búsqueda y el fichero buscado. Utilizaremos comillas en los ejemplos siguientes.

Otra opción útil es -n, que numera las líneas que coinciden:

BASH

$ grep -n "it" haiku.txt

SALIDA

5:With searching comes loss
9:Yesterday it worked
10:Today it is not working

Aquí podemos ver que las líneas 5, 9 y 10 contienen las letras “it”.

Podemos combinar opciones (es decir, banderas) como hacemos con otros comandos Unix. Por ejemplo, busquemos las líneas que contienen la palabra ‘the’. Podemos combinar la opción -w para encontrar las líneas que contienen la palabra ‘the’ y -n para numerar las líneas que coinciden:

BASH

$ grep -n -w "the" haiku.txt

SALIDA

2:Is not the true Tao, until
6:and the presence of absence:

Ahora queremos utilizar la opción -i para que nuestra búsqueda no distinga entre mayúsculas y minúsculas:

BASH

$ grep -n -w -i "the" haiku.txt

SALIDA

1:The Tao that is seen
2:Is not the true Tao, until
6:and the presence of absence:

Ahora, queremos utilizar la opción -v para invertir nuestra búsqueda, es decir, queremos que aparezcan las líneas que no contienen la palabra ‘the’.

BASH

$ grep -n -w -v "the" haiku.txt

SALIDA

1:The Tao that is seen
3:You bring fresh toner.
4:
5:With searching comes loss
7:"My Thesis" not found.
8:
9:Yesterday it worked
10:Today it is not working
11:Software is like that.

Si utilizamos la opción -r (recursiva), grep puede buscar un patrón recursivamente a través de un conjunto de ficheros en subdirectorios.

Busquemos recursivamente Yesterday en el directorio shell-lesson-data/exercise-data/writing:

BASH

$ grep -r Yesterday .

SALIDA

./LittleWomen.txt:"Yesterday, when Aunt was asleep and I was trying to be as still as a
./LittleWomen.txt:Yesterday at dinner, when an Austrian officer stared at us and then
./LittleWomen.txt:Yesterday was a quiet day spent in teaching, sewing, and writing in my
./haiku.txt:Yesterday it worked

grep tiene muchas otras opciones. Para saber cuáles son, podemos teclear:

BASH

$ grep --help

SALIDA

Usage: grep [OPTION]... PATTERN [FILE]...
Search for PATTERN in each FILE or standard input.
PATTERN is, by default, a basic regular expression (BRE).
Example: grep -i 'hello world' menu.h main.c

Regexp selection and interpretation:
  -E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  -F, --fixed-strings       PATTERN is a set of newline-separated fixed strings
  -G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  -P, --perl-regexp         PATTERN is a Perl regular expression
  -e, --regexp=PATTERN      use PATTERN for matching
  -f, --file=FILE           obtain PATTERN from FILE
  -i, --ignore-case         ignore case distinctions
  -w, --word-regexp         force PATTERN to match only whole words
  -x, --line-regexp         force PATTERN to match only whole lines
  -z, --null-data           a data line ends in 0 byte, not newline

Miscellaneous:
...        ...        ...

Usando grep

¿Qué comando daría como resultado la siguiente salida:

SALIDA

and the presence of absence:
  1. grep "of" haiku.txt
  2. grep -E "of" haiku.txt
  3. grep -w "of" haiku.txt
  4. grep -i "of" haiku.txt

La respuesta correcta es 3, porque la opción -w sólo busca coincidencias de palabras completas. Las otras opciones también coincidirán con “de” cuando forme parte de otra palabra.

Comodines

El verdadero poder de grep no proviene de sus opciones, sino del hecho de que los patrones pueden incluir comodines. (El nombre técnico para éstos es expresiones regulares, que es lo que significa la “re” en “grep”) Las expresiones regulares son complejas y potentes; si quieres hacer búsquedas complejas, consulta la lección en nuestro sitio web. Como muestra, podemos encontrar líneas que tengan una ‘o’ en la segunda posición de la siguiente manera:

BASH

$ grep -E "^.o" haiku.txt

SALIDA

You bring fresh toner.
Today it is not working
Software is like that.

Usamos la opción -E y ponemos el patrón entre comillas para evitar que el shell intente interpretarlo. (Si el patrón contuviera un *, por ejemplo, el shell intentaría expandirlo antes de ejecutar grep) El ^ en el patrón ancla la coincidencia al principio de la línea. El . coincide con un único carácter (igual que ? en el shell), mientras que el o coincide con una o real.

Seguimiento de una especie

Leah tiene varios cientos de archivos de datos guardados en un directorio, cada uno de los cuales tiene el siguiente formato:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

Quiere escribir un script de shell que tome una especie como primer argumento de la línea de comandos y un directorio como segundo argumento. El script debe devolver un fichero llamado <species>.txt que contenga una lista de fechas y el número de esa especie visto en cada fecha. Por ejemplo, utilizando los datos mostrados anteriormente, rabbit.txt contendría:

2012-11-05,22
2012-11-06,19
2012-11-07,16

A continuación, cada línea contiene un comando individual, o tubería. Ordene su secuencia en un comando para lograr el objetivo de Leah:

BASH

cut -d : -f 2
>
|
grep -w $1 -r $2
|
$1.txt
cut -d , -f 1,3

Sugerencia: use man grep para buscar como grep texto recursivamente en un directorio y man cut para seleccionar más de un campo en una línea.

En shell-lesson-data/exercise-data/animal-counts/animals.csv se proporciona un ejemplo de un archivo de este tipo

grep -w $1 -r $2 | cut -d : -f 2 | cut -d , -f 1,3 > $1.txt

En realidad, se puede cambiar el orden de los dos comandos de corte y todavía funciona. En la línea de comandos, intente cambiar el orden de los comandos de corte, y eche un vistazo a la salida de cada paso para ver por qué este es el caso.

Llamarías al script de arriba así:

BASH

$ bash count-species.sh bear .

Mujercitas

Tú y tu amiga, que acabáis de terminar de leer Mujercitas de Louisa May Alcott, estáis discutiendo. De las cuatro hermanas del libro, Jo, Meg, Beth y Amy, tu amiga piensa que Jo era la más mencionada. Tú, sin embargo, estás segura de que fue Amy. Por suerte, tienes un fichero LittleWomen.txt que contiene el texto completo de la novela (shell-lesson-data/exercise-data/writing/LittleWomen.txt). Utilizando un bucle for, ¿cómo tabularías el número de veces que se menciona a cada una de las cuatro hermanas?

Sugerencia: una solución puede emplear los comandos grep y wc y un |, mientras que otra puede utilizar las opciones grep. A menudo hay más de una manera de resolver una tarea de programación, por lo que una solución particular se elige generalmente sobre la base de una combinación de producir el resultado correcto, la elegancia, la legibilidad y la velocidad.

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ow $sis LittleWomen.txt | wc -l
done

Solución alternativa, ligeramente inferior:

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ocw $sis LittleWomen.txt
done

Esta solución es inferior porque grep -c sólo informa del número de líneas coincidentes. El número total de coincidencias informado por este método será menor si hay más de una coincidencia por línea.

Los observadores más perspicaces se habrán dado cuenta de que los nombres de los personajes a veces aparecen en mayúsculas en los títulos de los capítulos (por ejemplo, “MEG GOES TO VANITY FAIR”). Si quiere contarlos también, puede añadir la opción -i para no distinguir entre mayúsculas y minúsculas (aunque en este caso, no afecta a la respuesta sobre qué hermana se menciona con más frecuencia).

Mientras que grep busca líneas en ficheros, el comando find busca ficheros en sí. De nuevo, tiene muchas opciones; para mostrar cómo funcionan las más simples, usaremos el árbol de directorios shell-lesson-data/exercise-data que se muestra a continuación.

SALIDA

.
├── animal-counts/
│   └── animals.csv
├── creatures/
│   ├── basilisk.dat
│   ├── minotaur.dat
│   └── unicorn.dat
├── numbers.txt
├── alkanes/
│   ├── cubane.pdb
│   ├── ethane.pdb
│   ├── methane.pdb
│   ├── octane.pdb
│   ├── pentane.pdb
│   └── propane.pdb
└── writing/
    ├── haiku.txt
    └── LittleWomen.txt

El directorio exercise-data contiene un fichero, numbers.txt y cuatro directorios: animal-counts, creatures, alkanes y writing que contienen varios ficheros.

Para nuestro primer comando, vamos a ejecutar find . (recuerde ejecutar este comando desde la carpeta shell-lesson-data/exercise-data).

BASH

$ find .

SALIDA

.
./writing
./writing/LittleWomen.txt
./writing/haiku.txt
./creatures
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts
./animal-counts/animals.csv
./numbers.txt
./alkanes
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Como siempre, . por sí solo significa el directorio de trabajo actual, que es donde queremos que empiece nuestra búsqueda. la salida de find son los nombres de cada archivo y directorio bajo el directorio de trabajo actual. Esto puede parecer inútil al principio, pero find tiene muchas opciones para filtrar la salida y en esta lección descubriremos algunas de ellas.

La primera opción de nuestra lista es -type d que significa ‘cosas que son directorios’. Efectivamente, la salida de find son los nombres de los cinco directorios (incluyendo .):

BASH

$ find . -type d

SALIDA

.
./writing
./creatures
./animal-counts
./alkanes

Observe que los objetos encontrados por find no están ordenados. Si cambiamos -type d por -type f, obtendremos un listado de todos los ficheros:

BASH

$ find . -type f

SALIDA

./writing/LittleWomen.txt
./writing/haiku.txt
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts/animals.csv
./numbers.txt
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Ahora probemos a buscar por nombre:

BASH

$ find . -name *.txt

SALIDA

./numbers.txt

Esperábamos que encontrara todos los archivos de texto, pero sólo imprime ./numbers.txt. El problema es que el shell expande caracteres comodín como * antes de que se ejecuten los comandos. Como *.txt en el directorio actual se expande a ./numbers.txt, el comando que realmente ejecutamos fue:

BASH

$ find . -name numbers.txt

find hizo lo que pedimos; sólo que pedimos la cosa equivocada.

Para conseguir lo que queremos, hagamos lo que hicimos con grep: pongamos *.txt entre comillas para evitar que el shell expanda el comodín *. De esta forma, find obtiene el patrón *.txt, no el nombre de fichero expandido numbers.txt:

BASH

$ find . -name "*.txt"

SALIDA

./writing/LittleWomen.txt
./writing/haiku.txt
./numbers.txt

Listado vs. Búsqueda

Se puede hacer que ls y find hagan cosas similares dadas las opciones adecuadas, pero en circunstancias normales, ls lista todo lo que puede, mientras que find busca cosas con ciertas propiedades y las muestra.

Como hemos dicho antes, el poder de la línea de comandos reside en la combinación de herramientas. Hemos visto cómo hacerlo con tuberías; veamos otra técnica. Como acabamos de ver, find . -name "*.txt" nos da una lista de todos los archivos de texto dentro o debajo del directorio actual. ¿Cómo podemos combinar eso con wc -l para contar las líneas en todos esos ficheros?

La forma más sencilla es poner el comando find dentro de $():

BASH

$ wc -l $(find . -name "*.txt")

SALIDA

  21022 ./writing/LittleWomen.txt
     11 ./writing/haiku.txt
      5 ./numbers.txt
  21038 total

Cuando el shell ejecuta este comando, lo primero que hace es ejecutar lo que hay dentro de $(). Luego reemplaza la expresión $() con la salida de ese comando. Como la salida de find son los tres nombres de fichero ./writing/LittleWomen.txt, ./writing/haiku.txt, y ./numbers.txt, el shell construye el comando:

BASH

$ wc -l ./writing/LittleWomen.txt ./writing/haiku.txt ./numbers.txt

que es lo que queríamos. Esta expansión es exactamente lo que hace el shell cuando expande comodines como * y ?, pero nos permite usar cualquier comando que queramos como nuestro propio ‘comodín’.

Es muy común utilizar find y grep a la vez. El primero busca ficheros que coincidan con un patrón; el segundo busca líneas dentro de esos ficheros que coincidan con otro patrón. Aquí, por ejemplo, podemos encontrar archivos txt que contengan la palabra “searching” buscando la cadena ‘searching’ en todos los archivos .txt del directorio actual:

BASH

$ grep "searching" $(find . -name "*.txt")

SALIDA

./writing/LittleWomen.txt:sitting on the top step, affected to be searching for her book, but was
./writing/haiku.txt:With searching comes loss

Correspondencia y sustracción

La opción -v de grep invierte la coincidencia de patrones, de modo que sólo se imprimen las líneas que no coinciden con el patrón. Teniendo esto en cuenta, ¿cuál de los siguientes comandos encontrará todos los ficheros .dat en creatures excepto unicorn.dat? Una vez hayas pensado tu respuesta, puedes probar los comandos en el directorio shell-lesson-data/exercise-data.

  1. find creatures -name "*.dat" | grep -v unicorn
  2. find creatures -name *.dat | grep -v unicorn
  3. grep -v "unicorn" $(find creatures -name "*.dat")
  4. Ninguna de las anteriores.

La opción 1 es correcta. Al poner la expresión de coincidencia entre comillas se evita que el shell la expanda, por lo que se pasa al comando find.

La opción 2 también funciona en este caso porque el shell intenta expandir *.dat pero no hay ficheros *.dat en el directorio actual, así que la expresión comodín se pasa a find. Encontramos esto por primera vez en el episodio 3.

La opción 3 es incorrecta porque busca en el contenido de los ficheros las líneas que no coinciden con “unicornio”, en lugar de buscar en los nombres de los ficheros.

Archivos binarios

Nos hemos centrado exclusivamente en la búsqueda de patrones en archivos de texto. ¿Qué ocurre si sus datos están almacenados en imágenes, en bases de datos o en algún otro formato?

Un puñado de herramientas extienden grep para manejar algunos formatos que no son de texto. Pero un enfoque más generalizable es convertir los datos en texto, o extraer los elementos similares al texto de los datos. Por un lado, facilita las cosas sencillas. Por otro, las cosas complejas suelen ser imposibles. Por ejemplo, es bastante fácil escribir un programa que extraiga las dimensiones X e Y de archivos de imagen para que grep juegue con ellas, pero ¿cómo escribir algo para encontrar valores en una hoja de cálculo cuyas celdas contengan fórmulas?

Una última opción es reconocer que el shell y el procesamiento de texto tienen sus límites, y utilizar otro lenguaje de programación. Cuando llegue el momento de hacerlo, no seas demasiado duro con el shell. Muchos lenguajes de programación modernos han tomado prestadas muchas ideas de él, y la imitación es también la forma más sincera de elogio.

El shell de Unix es más antiguo que la mayoría de las personas que lo utilizan. Ha sobrevivido tanto tiempo porque es uno de los entornos de programación más productivos jamás creados — quizás incluso el más productivo. Su sintaxis puede ser críptica, pero la gente que la domina puede experimentar con diferentes comandos de forma interactiva, y luego utilizar lo que han aprendido para automatizar su trabajo. Las interfaces gráficas de usuario pueden ser más fáciles de usar al principio, pero una vez aprendidas, la productividad del shell es insuperable. Y como escribió Alfred North Whitehead en 1911, ‘La civilización avanza ampliando el número de operaciones importantes que podemos realizar sin pensar en ellas’

find Comprensión de lectura de tuberías

Escriba un breve comentario explicativo para el siguiente script de shell:

BASH

wc -l $(find . -name "*.dat") | sort -n
  1. Buscar todos los ficheros con extensión .dat recursivamente desde el directorio actual
  2. Cuente el número de líneas que contiene cada uno de estos ficheros
  3. Ordena numéricamente la salida del paso 2

Puntos Clave

  • find encuentra archivos con propiedades específicas que coinciden con los patrones.
  • grep selecciona las líneas de los archivos que coinciden con los patrones.
  • --help es una opción soportada por muchos comandos bash, y programas que pueden ser ejecutados desde dentro de Bash, para mostrar más información sobre cómo usar estos comandos o programas.
  • man [command] muestra la página del manual de un comando determinado.
  • $([command]) inserta la salida de un comando en su lugar.