Encontrando archivos
Última actualización: 2023-04-24 | Mejora esta página
Tiempo estimado: 15 minutos
Hoja de ruta
Preguntas
- ¿Cómo puedo encontrar archivos?
- ¿Cómo puedo encontrar algunas cosas dentro de mis archivos?
Objetivos
- Usar
grep
para seleccionar las líneas de archivos de texto que coincidan con patrones simples. - Usar
find
para encontrar archivos cuyos nombres coincidan con patrones simples. - Usar la respuesta de un comando como parámetro de entrada para otro comando.
- Explicar lo que se entiende por archivos de ‘texto’ y ‘binarios’, y por qué muchas herramientas comunes no manejan bien estos últimos.
De la misma manera que muchos de nosotros ahora usamos “googlear” como un verbo que significa “encontrar”, los programadores de Unix usan cotidianamente la palabra “grep”. “grep” es una contracción de “global/regular expression/print” (global/expresión regular/imprimir), una secuencia común de operaciones en los primeros editores de texto de Unix. También es el nombre de un programa de línea de comandos muy útil.
El comando grep
encuentra e imprime líneas en archivos
que coinciden con un patrón. Para nuestros ejemplos, usaremos un archivo
que contenga tres haikus publicados en 1998 en la revista
Salon. Para este conjunto de ejemplos, Vamos a estar trabajando
en el subdirectorio “writing”:
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.
Del dicho “Para siempre, o cinco años”
Nota: no hemos vinculado a los haikus originales porque parecen ya no estar disponibles en el sitio de Salon. Como Jeff Rothenberg dijo, “La información digital dura para siempre — o cinco años, lo que ocurra primero.”
Busquemos líneas que contengan la palabra “not”:
SALIDA
Is not the true Tao, until
"My Thesis" not found
Today it is not working
En este ejemplo, not
es el patrón que estamos buscando.
Es bastante simple: cada carácter alfanumérico coincide con sí mismo.
Después del patrón viene el nombre o los nombres de los archivos que
estamos buscando. La salida son las tres líneas del archivo que
contienen las letras “not”.
Vamos a probar un patrón distinto: “The”.
SALIDA
The Tao that is seen
"My Thesis" not found.
Esta vez, las dos líneas que incluyen las letras “The” forman parte del producto de la búsqueda. Sin embargo, una instancia de esas letras está contenida en una palabra más grande, “Thesis”.
Para restringir los aciertos a las líneas que contienen la palabra
“The” por sí sola, podemos usar grep
con el
indicador-w
. Esto limitará los coincidencias a
palabras.
SALIDA
The Tao that is seen
Nota que la palabra o patrón de búsqueda incluye el comienzo y el
final de una línea, no solamente aquellas letras rodeadas de espacios. A
veces, queremos buscar una frase, en vez de una sola palabra. Esto puede
hacerse fácilmente con grep
usando la frase entre
comillas.
SALIDA
Today it is not working
Hemos visto que no requerimos comillas alrededor de palabras simples, pero es útil utilizar comillas cuando se buscan varias palabras consecutivas. También ayuda a facilitar la distinción entre el término o frase de búsqueda y el archivo en el que se está buscando. Utilizaremos comillas en los próximos ejemplos.
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, flags) como lo
hacemos con otros comandos de Unix. Por ejemplo, vamos a encontrar las
líneas que contienen la palabra “the”. Podemos combinar la opción
-w
para encontrar las líneas que contienen la palabra “the”
con -n
para numerar las líneas que coinciden:
SALIDA
2:Is not the true Tao, until
6:and the presence of absence:
Ahora queremos usar la opción -i
para hacer que nuestra
búsqueda sea insensible a 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 usar la opción -v
para invertir nuestra
búsqueda, es decir, queremos que obtener 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.
grep
tiene muchas otras opciones. Para averiguar cuáles
son, podemos escribir:
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:
... ... ...
Utilizando grep
Haciendo referencia a haiku.txt
presentado al inicio de
este tema, qué comando resultaría en la salida siguiente:
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
Caracteres especiales o comodines
La verdadera ventaja de grep
no proviene de sus
opciones; viene del hecho de que los patrones pueden incluir comodines.
(El nombre técnico para estos son expresiones
regulares, que es lo que significa el “re” en “grep”.) Las
expresiones regulares son complejas y poderosas. Nota: Si quieres
realizar búsquedas complejas, y quieres saber más detalles mira la
lección de expresiones
regulares. Como prueba, podemos encontrar líneas que tienen una ‘o’
en la segunda posición, por ejemplo:
SALIDA
You bring fresh toner.
Today it is not working
Software is like that.
Utilizamos el indicador -E
y ponemos el patrón entre
comillas para evitar que el shell intente interpretarlo. (Si el patrón
contiene un *
, por ejemplo, el shell intentaría expandirlo
antes de ejecutar grep.) El ^
en el patrón de búsqueda
indica que la coincidencia debe ser al inicio de la línea. El
.
coincide con un solo carácter (como ?
en el
shell), mientras que el o
coincide con una ‘o’ real que en
este caso viene a ser el segundo caracter.
Seguimiento de una especie
Leah tiene varios cientos de archivos de datos guardados en un directorio, cada uno de los cuales tiene el formato siguiente:
2013-11-05,deer,5
2013-11-05,rabbit,22
2013-11-05,raccoon,7
2013-11-06,rabbit,19
2013-11-06,deer,2
Quiere escribir un script de shell que tome un
directorio y una especie como parámetros de línea de comandos y que le
devuelva un archivo llamado species.txt
que contenga una
lista de fechas y el número de individuos de esa especie observados en
esa fecha, como este archivo de números de conejos:
2013-11-05,22
2013-11-06,19
2013-11-07,18
Escribe estos comandos y pipes en el orden correcto para lograrlo:
Sugerencia: usa man grep
para buscar cómo seleccionar
texto recursivamente en un directorio usando grep y man cut
para seleccionar más de un campo en una línea. Encuentra un ejemplo en
el siguiente archivo
data-shell/data/animal-counts/animals.txt
Mujercitas
Tú y tu amigo tienen una desacuerdo, después de terminar de leer
Little Women de Louisa May Alcott. De las cuatro hermanas en el
libro, Jo, Meg, Beth, y Amy, tu amigo piensa que Jo era la más
mencionada. Tú, sin embargo, estás segura de que era Amy.
Afortunadamente tu tienes un archivo llamado
LittleWomen.txt
en data/, que contiene el texto completo de
la novela. Utilizando un bucle for
, ¿cómo tabularías el
número de veces que cada una de las cuatro hermanas es mencionada?
Sugerencia: una solución sería emplear los comandos grep
y wc
y un |
, mientras que otra solución podría
utilizar opciones de grep
. Normalmente hay más de una forma
para solucionar una tarea de programación. Cada solución es seleccionada
de acuerdo a la combinación entre cuál es la que produce el resultado
correcto, con mayor elegancia, de la manera más clara y con mayor
velocidad.
for sis in Jo Meg Beth Amy
do
echo $sis:
grep -ow $sis LittleWomen.txt | wc -l
done
Alternativa, menos eficiente:
for sis in Jo Meg Beth Amy
do
echo $sis:
grep -ocw $sis LittleWomen.txt
done
Esta solución es menos eficiente porque grep -c
solamente reporta el número de las líneas. El número total de
coincidencias que es reportado por este método va a ser más bajo si hay
más de una coincidencia por línea.
Mientras grep
encuentra líneas en los archivos, El
comando find
busca los archivos. Una vez más, tienes muchas
opciones. Para mostrar cómo funcionan las más simples, utilizaremos el
árbol de directorios que se muestra a continuación.
El directorio writing
de Nelle contiene un archivo
llamado haiku.txt
y tres subdirectorios:
thesis
(que contiene un archivo tristemente vacío,
empty-draft.md
), data
(que contiene dos
archivos LittleWomen.txt
, one.txt
y
two.txt
), un directorio tools
que contiene los
programasformat
y stats
, y un subdirectorio
llamado old
, con un archivo oldtool
.
Para nuestro primer comando, ejecutemos find .
.
SALIDA
.
./data
./data/one.txt
./data/LittleWomen.txt
./data/two.txt
./tools
./tools/format
./tools/old
./tools/old/oldtool
./tools/stats
./haiku.txt
./thesis
./thesis/empty-draft.md
Como siempre, el .
por sí mismo significa el directorio
de trabajo actual, que es donde queremos que empiece nuestra búsqueda.
La salida de find
es el nombre de cada archivo
y directorio dentro del 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 vamos a
descubrir algunas de ellas.
La primera opción en nuestra lista es -type d
que
significa “encontrar directorios”. Por supuesto que la salida de
find
serán los nombres de los cinco directorios en nuestro
pequeño árbol (incluyendo .
):
SALIDA
./
./data
./thesis
./tools
./tools/old
Si cambiamos -type d
por -type f
, recibimos
una lista de todos los archivos:
SALIDA
./haiku.txt
./tools/stats
./tools/old/oldtool
./tools/format
./thesis/empty-draft.md
./data/one.txt
./data/LittleWomen.txt
./data/two.txt
Ahora tratemos de buscar por nombre:
SALIDA
./haiku.txt
Esperábamos que encontrara todos los archivos de texto, Pero sólo
imprime ./haiku.txt
. El problema es que el shell amplía los
caracteres comodín como *
antes de ejecutar los
comandos. Dado que *.txt
en el directorio actual se expande
a haiku.txt
, El comando que ejecutamos era:
find
hizo lo que pedimos, pero pedimos la cosa
equivocada.
Para conseguir lo que queremos, vamos a hacer lo que hicimos con
grep
: escribe * txt
entre comillas simples
para evitar que el shell expanda el comodín *
. De esta
manera, find
obtiene realmente el patrón
*.txt
, no el nombre de archivo expandido
haiku.txt
:
SALIDA
./data/one.txt
./data/LittleWomen.txt
./data/two.txt
./haiku.txt
Listar vs. Buscar
ls
yfind
se pueden usar para hacer cosas
similares dadas las opciones correctas, pero en circunstancias normales,
ls
enumera todo lo que puede, mientras que
find
busca cosas con ciertas propiedades y las muestra.
Como mencionamos anteriormente, el poder de la línea de comandos
reside en la combinación de herramientas. Hemos visto cómo hacerlo con
pipes, veamos otra técnica. Como acabamos de ver,
find . -name '*.txt'
nos da una lista de todos los archivos
de texto dentro o más abajo del directorio actual. ¿Cómo podemos
combinar eso con wc -l
para contar las líneas en todos esos
archivos?
La forma más sencilla es poner el comando find
dentro de
$()
:
SALIDA
11 ./haiku.txt
300 ./data/two.txt
21022 ./data/LittleWomen.txt
70 ./data/one.txt
21403 total
Cuando el shell ejecuta este comando, lo primero que hace es ejecutar
lo que esté dentro del $()
. Luego reemplaza la expresión
$()
con la salida de ese comando. Dado que la salida de
find
son los cuatro nombres de directorio
./data/one.txt
, ./data/LittleWomen.txt
,
./data/two.txt
, y ./haiku.txt
, el shell
construye el comando:
que es lo que queríamos. Esta expansión es exactamente lo que hace el
shell cuando se expanden comodines como *
y ?
,
pero nos permite usar cualquier comando que deseemos como nuestro propio
“comodín”.
Es muy común usar find
y grep
juntos. El
primero encuentra archivos que coinciden con un patrón; el segundo busca
líneas dentro de los archivos que coinciden con otro patrón. Aquí, por
ejemplo, podemos encontrar archivos PDB que contienen átomos de hierro
buscando la cadena “FE” en todos los archivos .pdb
por
encima del directorio actual:
SALIDA
../data/pdb/heme.pdb:ATOM 25 FE 1 -0.924 0.535 -0.518
Buscando coincidencias y restando
-v
es una opción de grep
que invierte la
coincidencia de patrones, de modo que sólo las líneas que no
coinciden con el patrón se imprimen. Dado esto ¿cuál de los siguientes
comandos encontrarán todos los archivos en /data
cuyos
nombres terminan en s.txt
(por ejemplo,
animals.txt
or planets.txt
), pero no
contienen la palabra net
? Después de pensar en tu
respuesta, puedes probar los comandos en el directorio
data-shell
.
find data -name '*s.txt' | grep -v net
find data -name *s.txt | grep -v net
grep -v "temp" $(find data -name '*s.txt')
- Ninguna de las anteriores.
La respuesta correcta es 1. Poniendo la expresión entre comillas,
evita que el shell la expanda, y luego lo pasa al comando
find
.
La opción 2 es incorrecta porque el shell expande *s.txt
en lugar de mandar el nombre correcto al find
.
La opción 3 es incorrecta porque busca en los contenidos de los archivos las líneas que no coinciden con “temp”, en lugar de buscar los nombres de los archivos.
Archivos binarios
Nos hemos centrado exclusivamente en encontrar cosas en archivos de
texto. ¿Y si sus datos se almacenan como imágenes, en bases de datos, o
en algún otro formato? Una opción sería extender herramientas como
grep
para manejar esos formatos. Esto no ha sucedido, y
probablemente no se hará, porque hay demasiados formatos
disponibles.
La segunda opción es convertir los datos en texto, o extraer los bits
de texto de los datos. Este es probablemente el enfoque más común, ya
que sólo requiere generar una herramienta por formato de datos (para
extraer información). Por un lado, facilita realizar las cosas simples.
Por el contrario, las cosas complejas son generalmente imposibles. Por
ejemplo, es bastante fácil escribir un programa que extraiga las
dimensiones X, Y de los archivos de imagen para que podamos después usar
grep
, pero ¿cómo escribir algo para encontrar valores en
una hoja de cálculo cuyas celdas contienen fórmulas?
La tercera opción es reconocer que laterminal y el procesamiento de texto tiene sus límites, y en caso necesario debes utilizar otro lenguaje de programación. Sin embargo, el shell te seguirá siendo útil para realizar tareas de procesamiento diarias.
La terminal de Unix es más antiguo que la mayoría de las personas que lo usan. Ha sobrevivido tanto tiempo porque es uno de los ambientes de programación más productivos jamás creados — quizás incluso el más productivo. Su sintaxis puede ser críptica, pero las personas que lo dominan pueden experimentar con diferentes comandos interactivamente, y luego utilizar lo que han aprendido para automatizar su trabajo. Las interfaces gráficas de usuario pueden ser más sencillas al principio, pero el shell sigue invicto en segunda instancia. Y como Alfred North Whitehead escribió en 1911, “La civilización avanza extendiendo el número de operaciones importantes que podemos realizar sin pensar en ellas.”
- Encuentra todos los archivos con una extensión
.dat
en el directorio actual - Cuenta el número de líneas que contiene cada uno de estos archivos
- Ordena la salida del paso 2. numéricamente
Búsqueda de archivos con propiedades diferentes
El comando find
puede recibir varios otros criterios
conocidos como “tests” para localizar archivos con atributos
específicos, como tiempo de creación, tamaño, permisos o dueño. Explora
el manual con man find
y luego escribe un solo comando para
encontrar todos los archivos en o debajo del directorio actual que han
sido modificados por el usuario ahmed
en las últimas 24
horas.
Sugerencia 1: necesitarás utilizar tres tests:
-type
,-mtime
y -user
.
Sugerencia 2: El valor de -mtime
tendrá que ser negativo
— ¿por qué?
Puntos Clave
-
find
encuentra archivos con propiedades específicas que coinciden con los patrones especificados. -
grep
selecciona líneas en archivos que coinciden con los patrones especificados. -
--help
es un indicador usado por muchos comandos bash y programas que se pueden ejecutar desde dentro de Bash, se usa 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. -
$(comando)
contiene la salida de un comando.