Procesando archivos: Parte I

Empezar bajando la Clase 3:

traer clase3.sh

Comando básicos

Ya vimos unos cuantos comandos que nos permitían navegar por el filesystem y crear/copiar/mover/borrar archivos y/o directorios así como cambiar sus permisos o quién es el dueño:

pwd, ls, cd, mkdir, rmdir, cp, mv, rm, rename, touch, chown, chmod, ln, file

También vimos cómo podíamos usar re-direccionamiento para crear archivos donde almacenar la información generada por los programas que corremos. Si bien muchos programas generan datos binarios, es muy común que los programas generen salidas que son simples archivos de texto (esencialmente datos codificados como ASCII o UTF-8).

El manejo de datos en formato texto es esencial y central para el procesamiento de información en el ámbito científico. Hay muchos ejemplos de archivos de datos derivados de este tipo de codificación básica: PDB, XYZ, MOL, JSON, XML, HTML, etc.

En esta clase vamos a ver un conjunto de comandos sencillos que nos permiten operar o modificar archivos de datos. En la siguiente clase veremos comandos que nos permiten transformar estos archivos de texto de maneras más sofisticadas.

ConCATenando archivos

Ya vimos en clase algún ejemplo del comando cat, pero lo vimos en su versión más simple: volcar a pantalla el contenido de un archivo de texto para ver su contenido.

En realidad el comando cat (nombre que proviene de concatenate) permite concatenar el contenido de uno o más archivos de texto y volcarlos a la pantalla (o redirigir la salida a otro archivo)

Ejemplo: si queremos ver el contenido de los archivo arch1 y arch2

cat arch1
cat arch2
cat arch3

Si ahora queremos concatenar los contenidos de esos dos archivos en un único archivo:

cat arch1 arch2 >arch3
cat arch3

Es un comando extremadamente simple, que permite hacer una operación muy básica pero muy útil: unir datos de texto de varios archivos en un único archivo.

Ojo: al usar > para redirigir la salida del comando cat, perdimos la información que contenía arch3 y la sustituimos por los datos concatenados de arch1 y arch2. Recordemos que si queremos agregar datos al final de un archivo que existe, tenemos que usar doble >

cat arch1 arch2 arch3 >>arch3
cat arch3
Alvesre: tac

Como curiosidad, mencionaremos el comando tac, que funciona como cat pero publica las líneas en orden inverso: desde la última a la primera. Otro comando exótico es rev que invierte el orden de los caracteres en una línea de texto.

¿Qué contiene realmente un archivo?

Si volcamos a pantalla el contenido del archivo hola.txt vemos que aparece lo siguiente:

cat hola.txt
Hola

O sea que tiene 4 letras. Pero al listar el archivo, vemos que su tamaño es 5 bytes:

ls -la hola.txt
-rw-r--r-- 1 linux linux 5 Sep 23 11:59 hola.txt

¿Por qué esa diferencia? ¿donde está el quinto byte?

Recordemos que los archivos digitales sólo contienen bytes (números binarios de 8 bits) y no todos esos números binarios corresponden a códigos de caracteres imprimibles. Una manera de realmente ver qué contiene un archivo en su interior (o de examinar archivos que no sean de texto, sino archivos binarios de otra naturaleza) es usar el comando od (octal dump):

od -c hola.txt
0000000   H   o   l   a  \n
0000005

Ahí podemos comprobar que al final de la línea aparece un carácter de control (no imprimible), que corresponde a un fin de línea (EOL) o new line o line feed y que suele representarse así: \n.

Otros caracteres de control que suelen aparece son:

  • \t: tabulador
  • \r: carriage return o retorno de carro

Hay que tener en cuenta que mucha de esta notación proviene de la época en que el dispositivo de salida era una impresora, de ahí la terminología new line (o avance de carro) y carriage return (o retorno de carro).

En Unix se adoptó la convención de que las líneas de texto terminan con \n. Linux y Mac adhieren a esta convención. Obviamente Microsoft no :-(. Así que en Windows las líneas de texto terminan en \r\n, lo cual puede acarrear algunos problemas en ocasiones.

Hay dos comandos: dos2unix y unix2dos que permiten convertir un formato de archivo en el otro y de esa manera ayudan a resolver el dilema.

En archivos codificados ASCII o ISO-8859-1 (ISO Latin 1), es bastante habitual encontrar este tipo de caracteres de control. En otro tipo de codificaciones, como UTF-8, es habitual que ciertos caracteres especiales estén codificados con 2 bytes (particularmente las vocales con tildes).

file hola.txt utf8.txt
od -c utf8.txt

Con od también podemos examinar el contenido de otro tipo de archivos binarios, como por ejemplo imágenes:

od -c pepe.png|head -6
0000000 211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
0000020  \0  \0 004  \0  \0  \0 004  \0  \b 006  \0  \0  \0 177 035   +
0000040 203  \0  \0  \0  \t   p   H   Y   s  \0  \0 347 303  \0  \0 347
0000060 303 001 367   d 240 217  \0  \0  \n   O   i   C   C   P   P   h
0000100   o   t   o   s   h   o   p       I   C   C       p   r   o   f
0000120   i   l   e  \0  \0   x 332 235   S   g   T   S 351 026   = 367

En este volcado en pantalla del comienzo de ese archivo de imagen, vemos algo que caracteriza a este tipo de archivos binarios: la firma que identifica al archivo como un archivo de imagen en formato PNG. En este caso además hay un comentario que identifica el programa usado para procesar o crear esa imagen.

Contando y Numerando

El comando wc permite contar cuantas líneas, palabras y caracteres tiene un archivo

wc quijote.txt

El comando nl agrega un número de línea delante de cada línea

nl quijote.txt

Más o menos

A veces lidiamos con archivos de texto muy largos (muchas líneas), y el usar el comando cat para listar el contenido no resulta muy práctico. Sería preferible contar con algún comando que nos permitiera navegar por el contenido de los archivos, avanzar/retroceder o incluso buscar las líneas de texto que nos interesan.

Ahí es donde entran los comandos more y less. Básicamente ambos hacen lo mismo: despliegan el contenido de un archivo de texto en pantalla, pero lo hacen una pantalla por vez.

Uno puede usar el teclado para avanzar/retroceder (PgUp/PgDn, flechas), o hacer búsquedas en el texto (/,?,N,n).

Ejemplo:

more quijote.txt
less quijote.txt

Consultar el manual para ver las diferencias entre ambos y las opciones particulares a cada uno. Elegir uno y usar siempre ese…

Cabeza o Cola

En ocasiones puede que lo único que nos interesa es ver el comienzo o el final de un archivo. Para eso podemos usar los comandos head o tail:

head system.log
tail system.log

Por defecto ambos comandos muestran sólo 10 líneas del comienzo/final del archivo. Pero eso se puede cambiar con la opción -N donde N es la cantidad de líneas que queremos desplegar.

head -20 system.log
tail -20 system.log

El comando tail también tiene una opción muy útil: -f. Cuando lo que queremos ver es el contenido de un archivo que está siendo generado por un programa que está corriendo, al usar la opción -f el comando tail no termina al llegar al final del archivo, sino que queda esperando que se agregue más texto al archivo y lo va desplegando en pantalla.

Es muy útil para monitorear el avance de un programa que estemos corriendo en background.

Ejemplo:

publicador > publicador.log &
tail -f publicador.log

Otro ejemplo de uso de head y tail es combinar su uso para extraer un rango determinado de líneas de un archivo. Supongamos que tenemos un archivo largo y queremos el tramo que va de la línea 100 a las 200. Podemos combinar ambos comandos de la siguiente manera:

head -200 quijote.txt | tail -100

El script listar es un ejemplo de cómo se pueden combinar comandos básicos para escribir scripts que extiendan las posibilidades de estos comandos simples y así crear programas que hacen otras cosas.

./listar 12 15 qui*

¡Orden!

En ocasiones puede resultar necesario ordenar las líneas de un archivo de acuerdo a algún criterio (numérico o alfabético). Para eso se puede emplear el comando sort:

sort words

sort tiene varias opciones para definir criterios de ordenación. Si el archivo tiene un formato tabular, podemos elegir qué columna usar para ordenar el archivo, empleando la opción -k. Por defecto, sort ordena alfabéticamente, así que para ordenar numéricamente debemos especificar la opción -n. Usualmente las columnas de datos están separadas por espacios en blanco o tabuladores, pero si el separador de columnas es otro carácter, se puede especificar con la opción -t.

Ejemplos:

sort -n words3
sort -k2 -n words3
sort -k3 words3

Con la opción -r podemos invertir el orden.

Otro ejemplo de uso de sort:

cd
du -sh * | sort -h

El comando du (disk use) nos permite ver cuanto disco ocupan los archivos y directorios que especifiquemos como argumentos. En el ejemplo anterior nos permite obtener la información de cuanto ocupan los archivos y directorios debajo de nuestro homedir, y usamos el comando sort para ordenar esos resultados y así saber cuales son los archivos y/o directorios que ocupan más disco. La opción -h es para que los datos de ocupación salgan en unidades más fáciles de leer (por los humanos), de lo contrario los valores salen en bytes y resulta más engorroso de interpretar.

¡Desorden!

Como contrapartida a sort, tenemos el comando shuf (shuffle), que mezcla aleatoriamente las líneas de un archivo.

Ejemplo de posible uso de este comando: extraer aleatoriamente líneas de un archivo:

shuf words3 | head -1

Únicas

Al ordenar líneas de archivos, puede ocurrir que algunas líneas aparezcan repetidas y puede interesarnos eliminar esas repeticiones. Para eso podemos usar el comando uniq:

sort archivo | uniq

O también puede interesarnos saber cuantas veces se repite una línea, con la opción -c (count):

sort archivo | uniq -c 

que antepone el contador de cuantas veces esa línea se repitió. Si además queremos ordenar por número de repeticiones de cada línea, podemos volver a ordenar numéricamente:

sort archivo | uniq -c | sort -n

Cambiar caracteres (traducir)

El comando tr (translate) toma texto de stdin y transforma o traduce o elimina ciertos caracteres (o conjuntos de caracteres) y devuelve el resultado en stdout. Ya vimos algún ejemplo sencillo:

fortune  | tr "a-z" "A-Z"

En este ejemplo, toma el rango de minúsculas y las traduce a mayúsculas.

Otros ejemplos para ver qué hace:

tr "\t" " " <archivo1 >archivo2

Supongamos que tenemos en el archivo quijote.txt el texto del Quijote de Cervantes. ¿Cuál es el resultado del siguiente comando?:

cat quijote.txt | tr "A-Z" "a-z" | tr -cs "a-z" "\n" | sort | uniq -c | sort -nr > archivo.txt

Cortar, Pegar y Unir

Cut

El comando cut permite cortar columnas seleccionadas de un archivo. Las columnas deben estar separadas entre sí por delimitadores (usualmente espacios en blanco o tabuladores). La opción -d permite especificar otros delimitadores. La opción -f permite seleccionar los campos (columnas) que queremos cortar.

Ejemplo:

cat words3
cut -f1,3 words3
cut -f3 words3

Paste

El comando paste permite unir en columnas el contenido de dos o más archivos. Ejemplo:

paste words words3

Join

El comando join es muy parecido a paste pero permite pegar datos en columnas de manera más inteligente que paste. La operación que join hace es muy parecida a la que hace el JOIN en una base de datos relacional. Ejemplo:

join juntar1 juntar2

Column

Comando que permite ordenar contenidos de archivos por columnas. Ejemplos:

column words2
column -t words3

Comparar:

paste words words2 words3

con

paste words words2 words3|column -t 

Otro ejemplo:

join juntar1 juntar2 | column -t

Colrm

El comando colrm remueve columnas de un archivo de texto, pero las columnas están determinadas por los caracteres de cada línea. Este comando lleva dos argumentos: start y end y sólo lee de stdin y vuelca resultado a stdout, así que es ideal para usar con pipes. Ejemplo:

colrm 1 4 <words3

Comparar archivos

Aquí podemos catalogar programas que comparan archivos de texto plano entre sí, para ver en qué se parecen y en qué se diferencian, o programas que generan una suerte de firma digital, y permiten verificar si dos archivos son idénticos o no. O si el contenido de un archivo fue adulterado o permanece incambiado.

Comparación de archivos de texto
  • diff: lista diferencias entre dos archivos de texto, muestra las líneas donde difieren
  • cmp: compara dos archivos, byte a byte
  • comm: compara dos archivos de texto que estén ordenados alfabética/numéricamente

El más usado es diff, especialmente en programación, pues permite comparar archivos fuentes (archivos de programas) y la salida que genera puede ser usada por programas como patch que permiten aplicar esas diferencias a archivos fuente (emparchar programas).

Hashes

Las funciones hash son funciones de un sólo sentido tales que, dado un conjunto de datos (archivo) generan un número que se supone es único para ese conjunto de datos. Resultan una suerte de fingerprint. No es que dos conjuntos de datos no puedan genera un mismo valor de hash, pero la probabilidad es muy baja. En particular alcanza con cambiar un bit del archivo original, para que su valor hash cambie completamente, por lo que resultan muy prácticos para verificar la identidad de dos archivos o asegurar la integridad de un archivo.

  • sum y cksum: cuentan bytes y generan un CRC (Cyclic Redundancy Check) de un archivo
  • crc32: computa un CRC-32 para un archivo (un hash de 32 bits)
  • md5sum: es un checksum de 128 bits. Mucho mejor que crc-32

El más empleado es md5sum:

md5sum arch*

cp arch3 arch3clon
md5sum arch*

Podemos comprobar que si dos archivos son idénticos, generan el mismo valor de hash y archivos muy parecidos pero que difieren un poco entre sí, generan valores de hash muy diferentes.

Comprimir archivos

Linux ofrece una variedad muy amplia de opciones de compresión de archivos. Mencionaremos algunos:

formato comando
.gz gzip, gunzip
.zip zip, unzip
.bz2 bzip2, bunzip2
.xz lzma, xz, unxz
.rar unrar

Archivar

El comando tar (Tape Archive) es un viejo comando que originalmente se usaba para guardar/grabar (archivar) a cinta magnética conjuntos de archivos.

Hoy en día se sigue usando para armar archivos donde acumular conjuntos de directorios y archivos a los efectos de hacer backups, o para copiar o enviar a terceros. Es un formato estándar de almacenamiento, que incluso es reconocido por Windows. El tar o tgz (tar comprimido) es un formato de archivo muy utilizado para distribuir software. Muchos paquetes científicos vienen empaquetados en este formato.

Crear

supongamos que queremos armar un archivo con todo el contenido de un determinado directorio:

 tar -cf archivo.tar  ./directorio

En este ejemplo, el directorio y todo su contenido es empacado en archivo.tar y luego ese archivo se puede respaldar, copiar o enviar a terceros, para que lo desempaquen. La opción -c es para crear el archivo. si quisiéramos agregar o adjuntar archivos a un tar ya existente, usaríamos la opción -r. La opción -f es para indicarle el nombre del archivo tar que queremos crear.

El comando tar también almacena y preserva la información de usuario/grupo y los permisos de archivos y directorios, si se usa la opción -p.

Comprimir

También tiene opciones para comprimir los archivos empacados, de manera que el archivo tar resultante ocupe menos espacio de disco. En esos casos se recomienda usar la extensión .tgz o .tar.gz para que quede claro que es un tar comprimido. Se usa la opción -z para indicar que se desea comprimir (zipear) el archivo resultante:

tar -czf archivo.tgz ./directorio

La opción -z comprime en formato gzip, pero tar incluye otras opciones de compresión/descompresión:

  • -j: formato bzip2
  • -J: formato xz
  • –lzip: formato lzip
  • -a: auto: elije en qué formato comprimir de acuerdo a la extensión que se use para el archivo tar
Listar contenido

Si quiero saber cuál es el contenido de un archivo tar, sin necesidad de desempacarlo, puedo usar la opción -t:

tar -tzf archivo.tgz
Extraer contenido

Para extraer (destarear) el contenido de un archivo tar, uso la opción -x:

tar -xzf archivo.tgz

Vayamos por partes... dijo Jack

Los comandos split y csplit permiten cortar archivos muy grandes en un conjunto de archivos más chicos. Esto puede resultar muy útil si tenemos que transferir un archivo muy grande (varios Gb) y no lo podemos hacer o bien porque el pendrive no admite archivos tan grandes (los pendrives usualmente tienen un límite de 4Gb) o porque el servidor de email no admite enviar archivos adjuntos de más de cierto tamaño.

Ejemplo: usando el comando dd (disk dump), vamos a generar un archivo binario de 51Mb (lleno de datos aleatorios) y luego a partirlo cuando split en varios archivos de más 10Mb cada uno:

dd if=/dev/urandom of=random count=100000
split -b 10000000 random

Para reconstruir el archivo original podemos usar el comando cat:

cat x* > random2 

Podemos verificar que el archivo fue reconstruido correctamente, comparando el hash md5 de los archivos:

md5sum ran*

Enviar por email

El programa sendemail es muy práctico para enviar emails desde la línea de comando (o desde algún script que hayamos programado). Éste comando no está instalado habitualmente en Linux, hay que instalarlo explícitamente:

sudo apt-get install sendEmail

La sintaxis básica de este comando es:

sendemail -f yo@mimail.com -t vos@tumail.com -s servidor_smtp -u "tema del email" -a attach_file -m "texto del cuerpo del email"

Como vemos, lleva unos cuantos argumentos, pero básicamente permite hacer lo que cualquier cliente de email permite, incluyendo el envío de attachments. Si se van a enviar varios attachments, se pone un -a por cada attachment. Si se quiere enviar a varios destinatarios, se pone un -t por cada uno.

Para que este comando sea de utilidad suele ser necesario contar con un servidor SMTP que acepte nuestro email y lo redirija a su destino. No siempre es posible o está disponible. Si el servidor requiere autenticación (usuario y contraseña), se deben usar las opciones -xu y -xp

Un ejemplo de cómo usar este comando desde la red de FQ:

sendemail -f micuenta@fq.edu.uy -t tucuenta@fq.edu.uy -s msa.fq.edu.uy -o tls=no -a archivo.tgz -u "tema" -m "Ahí va el archivo"

Para ver todas las opciones, consultar sendemail –help

aliases

En ocasiones resulta muy engorroso escribir comandos tan largos. Con tantas opciones es incluso probable que nos equivoquemos, así que en ocasiones es práctico contar con algún truco que nos ayude a resumir mucha de las opciones que usamos habitualmente. Par eso podemos usar el mando alias del shell:

Por ejemplo:

alias email='sendemail -f micuenta@fq.edu.uy -s msa.fq.edu.uy -o tls=no'

Entonces el ejemplo que pusimos más arriba se reduce a esto:

email -t tucuenta@fq.edu.uy -a archivo.tgz -u "tema" -m "Ahí va el archivo"

Si corremos el comando alias, vemos el listado de todos los aliases que tenemos definidos. Para remover un alias usamos el comando unalias:

unalias email

Como la definición de un alias desaparece una vez que abandonamos el shell, los aliases suelen declararse en los archivos de inicialización del shell, algo que veremos más adelante.

Restricciones

Los servidores smtp de FQ tienen un límite de envío de hasta 25 Mb por email, pero hay que tener en cuenta que al enviar attachments, éstos ocupan más espacio, pues para poder ser enviados por email deben re-codificarse a un formato BASE64.

Editores de texto

Esto es un capítulo aparte. Más que eso… es un Universo Aparte.

¿Qué editor de texto usar en la consola de Linux?

Advertencia: este tema es capaz de desatar Guerras Religiosas

Voy a resumir este punto con las siguientes referencias:

  • vi (o sus variantes: vim y neovim): muy poderoso, curva de aprendizaje muy empinada, retorcido como Ricardo III (“…dogs bark at me as I halt by them;”)
  • emacs: una experiencia psicodélica extrasensorial transdimensional y holística, además de un estilo de vida
  • nano (o su equivalente pico): muy choto, muy pobre… pero muy choto… pero muy pobre… :-(
  • ne (nice editor): es un lindo editor (dijera Piolín)
  • jed: editor de texto para programadores, con emulación de emacs, EDT, Wordstar y con un lenguaje que permite extenderlo.

Observación: los principiantes suelen usar nano, los niños chicos suelen usar triciclos…

Ojo: vim y nano suelen venir instalados por defecto en la mayoría de las distribuciones de Linux. Los demás hay que instalarlos explícitamente. Hay muchas alternativas más, pero recomiendo que elijan uno, aprendan a usarlo bien y que la fuerza les acompañe… :-)