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:
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”:
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’.
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.
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.
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:
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:
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:
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’.
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
:
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:
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:
grep "of" haiku.txt
grep -E "of" haiku.txt
grep -w "of" haiku.txt
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:
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:
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í:
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
).
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
.
):
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:
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:
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:
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
:
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
$()
:
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:
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:
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
.
find creatures -name "*.dat" | grep -v unicorn
find creatures -name *.dat | grep -v unicorn
grep -v "unicorn" $(find creatures -name "*.dat")
- 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’
- Buscar todos los ficheros con extensión
.dat
recursivamente desde el directorio actual - Cuente el número de líneas que contiene cada uno de estos ficheros
- 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.