agosto 31, 2022
~ 11 MIN
La Terminal - Manejo de Datos
< Blog RSSLa Terminal - Manejo de Datos
Este post estará enfocado en el manejo de datos a través de la terminal. Esto significa convertir cualquier conjunto de datos (imágenes, texto, etc) de un formato a otro (no sólo convertir una imagen de jpg a png, por ejemplo, sino también extraer información de fuentes de datos, estadÃsticos, etc). En posts anteriores ya vimos algún ejemplo de manejo de datos, al usar el operador |
para usar la salida de un programa como entrada de otro y asà procesar los datos de manera consecutiva para obtener el resultado deseado.
Fuente de datos
Lo primero que necesitamos para empezar es una fuente de datos, que puede ser texto, imágenes o cualquier otro tipo de datos binario. En nuestro caso, usaré el registro del sistema, o system log, de un servidor remoto al cual puedo acceder con el comando ssh harley journalctl
. El comando ssh
permite conectarse a servidores remotos, harley
es el nombre de mi servidor (cuyas credenciales de acceso tengo configuradas en mi ordenador) y journalctl
es el comando para recuperar el registro del sistema. Si ejecutas este comando obtendrás un error, ya que no tienes acceso a este servidor. Si quieres seguir el ejemplo puedes usar otra fuente de datos diferente.
Este blog está basado en
jupyter notebook
y, cómo ya vimos en posts anteriores, podemos ejecutar comandos en notebooks usando el prefijo!
. Si quieres probar estos comandos en tu terminal, recuerda quitar este sÃmbolo del principio de cada lÃnea.
!ssh harley journalctl | head -n10
-- Logs begin at Mon 2022-05-02 13:20:52 CEST, end at Wed 2022-08-31 12:09:41 CEST. --
May 02 13:20:52 harley systemd[2243]: Started Pending report trigger for Ubuntu Report.
May 02 13:20:52 harley systemd[2243]: Reached target Paths.
May 02 13:20:52 harley systemd[2243]: Reached target Timers.
May 02 13:20:52 harley systemd[2243]: Starting D-Bus User Message Bus Socket.
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG network certificate management daemon.
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent and passphrase cache (access for web browsers).
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent and passphrase cache (restricted).
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent and passphrase cache.
Manejo de datos
En nuestro ejemplo vamos a analizar el uso del servidor: cuántos usuarios se han conectado, con qué frecuencia, etc. Para ello vamos a utilizar varios comandos muy interesantes que deberÃas conocer para poder aplicar a tus casos de usos. ¡Empecemos!
Primero, vamos a filtrar el registro del sistema para quedarnos con todas aquellas lÃneas que correspondan a conexiones al servidor. Esto lo conseguimos conectando el comando anterior al programa grep
(que ya vimos en posts anteriores) de la manera siguiente:
!ssh harley journalctl | grep ssh | head -n10
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:22:53 harley sshd[2442]: Received disconnect from 192.168.0.10 port 58506:11: disconnected by user
May 02 13:22:53 harley sshd[2442]: Disconnected from user juan 192.168.0.10 port 58506
May 02 13:23:03 harley systemd[2243]: gpg-agent-ssh.socket: Succeeded.
May 02 13:23:03 harley systemd[2243]: Closed GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:30:34 harley systemd[3115]: Listening on GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:31:27 harley sshd[3222]: Received disconnect from 192.168.0.10 port 59396:11: disconnected by user
May 02 13:31:27 harley sshd[3222]: Disconnected from user juan 192.168.0.10 port 59396
May 02 13:31:38 harley systemd[3115]: gpg-agent-ssh.socket: Succeeded.
May 02 13:31:38 harley systemd[3115]: Closed GnuPG cryptographic agent (ssh-agent emulation).
^C
En el ejemplo anterior, el servidor está enviándonos toda la información que luego procesamos en local. En ocasiones esto puede ser costoso, por lo que es recomendable ejecutar directamente en el servidor los comandos para el manejo de datos y devolver directamente los resultados.
!ssh harley 'journalctl | grep ssh ' | head -n10
May 02 13:20:52 harley systemd[2243]: Listening on GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:22:53 harley sshd[2442]: Received disconnect from 192.168.0.10 port 58506:11: disconnected by user
May 02 13:22:53 harley sshd[2442]: Disconnected from user juan 192.168.0.10 port 58506
May 02 13:23:03 harley systemd[2243]: gpg-agent-ssh.socket: Succeeded.
May 02 13:23:03 harley systemd[2243]: Closed GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:30:34 harley systemd[3115]: Listening on GnuPG cryptographic agent (ssh-agent emulation).
May 02 13:31:27 harley sshd[3222]: Received disconnect from 192.168.0.10 port 59396:11: disconnected by user
May 02 13:31:27 harley sshd[3222]: Disconnected from user juan 192.168.0.10 port 59396
May 02 13:31:38 harley systemd[3115]: gpg-agent-ssh.socket: Succeeded.
May 02 13:31:38 harley systemd[3115]: Closed GnuPG cryptographic agent (ssh-agent emulation).
Alternativamente, puedes copiarte el registro en tu máquina para posteriores procesados (en este caso, con solo aquellas lÃneas correspondientes a conexiones de usuarios).
!ssh harley 'journalctl | grep ssh | grep "Disconnected from"' > ssh.log
!cat ssh.log | head -n10
May 02 13:22:53 harley sshd[2442]: Disconnected from user juan 192.168.0.10 port 58506
May 02 13:31:27 harley sshd[3222]: Disconnected from user juan 192.168.0.10 port 59396
May 02 13:36:01 harley sshd[3930]: Disconnected from user juan 192.168.0.10 port 59869
May 02 15:36:56 harley sshd[6807]: Disconnected from user juan 192.168.0.10 port 54340
May 02 15:36:58 harley sshd[6862]: Disconnected from user juan 192.168.0.10 port 54343
May 02 15:37:01 harley sshd[6917]: Disconnected from user juan 192.168.0.10 port 54350
May 02 15:37:01 harley sshd[6971]: Disconnected from user juan 192.168.0.10 port 54353
May 02 15:37:02 harley sshd[7025]: Disconnected from user juan 192.168.0.10 port 54354
May 02 15:37:15 harley sshd[7080]: Disconnected from user juan 192.168.0.10 port 54357
May 02 15:44:25 harley sshd[3467]: Disconnected from user juan 192.168.0.10 port 55038
cat: stdout: Broken pipe
Como puedes ver tenemos el nombre de los usuarios, fechas de conexión y demás información en el registro. ¿Cómo podemos extraer esta información para su uso? La respuesta es el comando sed
(stream editor) que nos permite editar el contenido del archivo.
!cat ssh.log | sed 's/.*Disconnected from //' | head -n10
user juan 192.168.0.10 port 58506
user juan 192.168.0.10 port 59396
user juan 192.168.0.10 port 59869
user juan 192.168.0.10 port 54340
user juan 192.168.0.10 port 54343
user juan 192.168.0.10 port 54350
user juan 192.168.0.10 port 54353
user juan 192.168.0.10 port 54354
user juan 192.168.0.10 port 54357
user juan 192.168.0.10 port 55038
sed: stdout: Broken pipe
cat: stdout: Broken pipe
Lo que hemos usado como argumento al comando sed
se conoce como regular expression, o regex, una manera muy potente para encontrar patrones concretos de texto y hacer cosas con ellos. En este caso s/.*Disconnected from //
sustituirá el resultado de evaluar la regex .*Disconnected from
por un espacio en blanco. Esta es una herramienta muy importante a la hora de manejar datos con la terminal, por lo que vamos a aprender un poco más sobre ella.
Regular Expressions
Empezando por el regex anterior, .*Disconnected from
encontrará todo el texto que empiece por 0 o más caracteres seguidos de Disconnected from
(el sÃmbolo .
significa cualquier carácter y *
significa 0 o más). Existen otros sÃmbolos, como +
que significa 1 o más, o []
que permiten encontrar uno o varios caracteres.
!echo abc | sed 's/[ab]//'
bc
Por defecto, la regex se usa una sola vez (el primer patrón que encuentra). Podemos usarlo para sustituir todas las ocurrencias de la manera siguiente
!echo abc | sed 's/[ab]//g'
c
En los ejemplos anteriores, querÃamos sustituir los caracteres a
y b
. Para sustituir la ocurrencia ab
podemos usar ()
. Para que funcione deberemos usar la opción -E
en sed
que permite el uso de opciones más avanzadas con regex.
!echo babac | sed -E 's/(ab)//'
bac
Podemos usar modificadores para encontrar diferentes variaciones.
!echo babac | sed -E 's/(ab|ac)//g'
b
Vamos a aplicar estos conceptos para encontrar el nombre de usuario en el registro anterior. Para ello, podemos usar el siguiente comando
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | head -n10
juan
juan
juan
juan
juan
juan
juan
juan
juan
juan
sed: stdout: Broken pipe
cat: stdout: Broken pipe
Los sÃmbolos ^
y $
indican que el patrón debe coincidir con el inicio o final de la lÃnea. Los grupos [0-9.]+
y [0-9]+
nos permiten encontrar grupos de números. Por último, podemos sustituir la lÃnea por el nombre de usuario usando el primer grupo de captura, (.*)
, poniendo \3
en vez del valor vacÃo que usábamos antes (de manera similar puedes usar \1
, \2
, ..., para otros grupos de captura en tu regex).
Como puedes ver, trabajar con regex se complica mucho rápidamente, por lo que es muy aconsejable probar y testear tus regex antes de usarlas en herramientas como https://regex101.com/.
Análisis de datos
Una vez hemos procesado nuestro registro y ya hemos extraÃdo la información que querÃamos, podemos seguir con el análisis para calcular estadÃsticos interesantes. Podemos empezar sabiendo cuantas entradas tenemos en el registro, para ello podemos usar el comando wc
.
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | wc -l
390642
Podemos también ordenar la lista de usuarios y encontrar los valores únicos con los comandos sort
y uniq
respectivamente.
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | tail -n10
1 zzidc123
3 zzk
3 zzkr
1 zzq
1 zzstat
2 zzw
29 zzy
30 zzz
5 zzzz
14 ~#$%^&*(),.;
Y ahora reordenar la lista por ocurrencias, usando de nuevo sort
con el argumento -nk1,1
para hacer una ordenación numérica en la primera columna.
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | sort -nk1,1 | tail -n10
1210 git
1652 ftpuser
1675 oracle
1824 postgres
2628
2771 ubuntu
4843 user
5726 test
11244 admin
205766 root
Podemos generar una lista con los 10 usuarios más activos con el comando, que podemos usar para restringir permisos o cualquier cosa.
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | sort -nk1,1 | tail -n10 | awk '{print $2}' | paste -sd, -
git,ftpuser,oracle,postgres,,ubuntu,user,test,admin,root
Para ello hemos usado el comando awk
, que permite editar texto en formato tabular (similar a sed
pero para trabajar con columnas), y el comando paste
, que permite junta varias lÃneas de texto en una sola con el delimitador indicado (en este caso, la coma). awk
es un lenguaje de programación en sà mismo, que permite llevar a cabo multitud de tareas, por lo que te recomiendo que le eches un vistazo para hacerte una idea de las cosas que puedes hacer con este potente programa.
Podemos calcular el número total de conexiones sumando todos los valores de la primera columna. Para ello podemos usar el comando bc
!echo '1+2' | bc
3
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | sort -nk1,1 | tail -n10 | awk '{print $1}' | paste -sd+ -
1210+1652+1675+1824+2628+2771+4843+5726+11244+205766
!cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | sort -nk1,1 | tail -n10 | awk '{print $1}' | paste -sd+ - | bc
239339
Y también hacer cálculos estadÃsticos o gráficas con programas que acepten entradas a través de la terminal, como R
o gnuplot
respectivamente. El siguiente comando genera el histograma de la figura: cat ssh.log | sed -E 's/^.*Disconnected from (invalid |authenticating )?(user)?(.* )?[0-9.]+ port [0-9]+( \[preauth\])?$/\3/' | sort | uniq -c | sort -nk1,1 | tail -n10 | gnuplot -p -e 'set boxwidth 0.5; plot "-" using 1:xtic(2) with boxes'
Resumen
En este post hemos visto algunos de los comandos más útiles que tenemos a nuestra disposición a la hora de manejar datos y procesarlos. Por el camino también hemos aprendido sobre expresiones regulares y su potencia a la hora de encontrar patrones de texto en grandes archivos. Si bien los ejemplos que hemos visto han sido con texto, también podemos trabajar con imágenes o estructuras de texto muy utilizadas como html
o json
. Sin embargo, si has estado atento a mis otros posts, verás que tenemos a nuestra disposición herramientas en el ecosistema Python
para automatizar todas estas tareas de manera más sencilla y con mucha más funcionalidad. Aun asÃ, está bien saber que podemos usar la terminal para hacer operaciones sencillas que podemos automatizar en funciones o scripts reutilizables.