====== 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 con**cat**enate) 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" " " 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 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...** :-)