jueves, 19 de agosto de 2010

Barajitas premiadas (#17) - encoding

Encodings, encodings, problemáticos todo lo que tenga que ver con ellos...

Típico que tenemos un archivo en ASCII y lo necesitamos en UTF-8, o UTF-16 o ISO8859-X, etc, etc.

Aquí les dejo 3 métodos que pueden utilizar para cambiar el encoding de un archivo:
  1. Abriendo el archivo con un bloc de notas (Ej. gedit) y seleccionando explícitamente el encoding (codificación) del archivo al "guardar como..."
  2. Utilizando el comando iconv, Ej. iconv -f ASCII -t UTF-8 -o UTF_FILE ASCII_FILE
  3. Utilizando VIM (en modo comando con el archivo abierto), la secuencia:
    :set bomb
    :set fileencoding=utf-8
    :wq


De todas las formas mencionadas especialmente me gusta la tercera. Y luego para verificar si todo se hizo: file IS_IT_REALLY_AN_UTF_FILE

Replicación Maestro->Esclavo1->Esclavo2 en mySQL

A continuación les dejo una chuleta (cheat sheet) para configurar bases de datos mySQL de forma Maestro->Esclavo1->Esclavo2.

Configuración inicial

Maestro: host localhost, port 17050
Esclavo1: host localhost, port 17051
Esclavo2: host localhost, port 17052

Configurando Maestro-Esclavo1

Primero: Configuración del Maestro
  1. Debe tener log-bin (bajo [mysqld] en my.cnf)
  2. Debe tener un usuario para replicación
    GRANT REPLICATION SLAVE ON *.* TO 'repl'@'localhost' IDENTIFIED BY '123456';
  3. Debe extraer los parámetros de configuración para Esclavo1
    SHOW MASTER STATUS;

Segundo: Configuración del Esclavo1
  1. Debe tener log-bin (bajo [mysqld] en my.cnf)
  2. Debe tener log-slave-updates (bajo [mysqld] en my.cnf)
  3. Debe tener un usuario para replicación
    GRANT REPLICATION SLAVE ON *.* TO 'repl'@'localhost' IDENTIFIED BY '123456';
  4. Debe configurar Esclavo1 como esclavo de Maestro, para lo cual deberá introducir los parámetros obtenidos arriba en el paso 3.
    CHANGE MASTER TO MASTER_HOST='localhost', MASTER_PORT=17050, MASTER_USER='repl', MASTER_PASSWORD='123456', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=202
  5. Debe comenzar la replicación
    START SLAVE;

Incorporando al Esclavo2

Una vez que el esquema Maestro-Esclavo1 se encuentre funcionando correctamente, se procederá a configurar el Esclavo2.

Primero: Extracción del dump (a partir de Esclavo1)
  1. Debe detener la replica como esclavo y liberar sus logs (en Esclavo1):
    STOP SLAVE;
    FLUSH LOGS;
  2. Debe extraer un dump (de Esclavo1)
    mysqldump -u root --password=msandbox --port=17051 --host=127.0.0.1 --master-data=2 test > dump1.sql
  3. Reiniciar la replica como esclavo (en Esclavo1 - volviendo a la normalidad):
    START SLAVE;

Segundo: Configuración del Esclavo2
  1. Restaurar el dump (en Esclavo2)
    mysql -u root --password=msandbox --port=17051 --host=127.0.0.1 test < dump1.sql
  2. Extraer parámetros de configuración del maestro. Para ello deberá buscar en el dump (dump1.sql) una línea como la siguiente:
    -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=106
  3. Configurar Esclavo2 como esclavo de Esclavo1. Deberá copiar los parámetros extraidos en el paso 2 e introducirlos en este comando:
    CHANGE MASTER TO MASTER_HOST='localhost', MASTER_PORT=17051, MASTER_USER='repl', MASTER_PASSWORD='123456', MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=106
  4. Iniciar el esclavo (en Esclavo2)
    START SLAVE;

NOTA: La configuración inicial Maestro-Esclavo se asume desde el inicio, antes de que la base de datos tuviese registro alguno.

Para este ejercicio utilicé MySQL Sandbox

Y para realizar inserciones -sin parar- en las tablas del maestro utilicé el siguiente script:

#!/bin/bash

x=0;     # initialize x to 0
#while [ "$x" -le 10 ]; do
while true; do
    mysql -u root --password=msandbox --port=17050 --host=127.0.0.1 -e "INSERT INTO test.a VALUES($x)"   
    # increment the value of x:
    x=$(expr $x + 1)
    sleep 2
done

OJO: La tabla que uso es muy sencilla: CREATE TABLE a (a int); dentro del esquema test que instala por defecto MySQL Sandbox.


jueves, 12 de agosto de 2010

Cómo escapar caracteres para manipularlos en una consola

Esto me dio bastantes problemas la verdad. Tenía un archivo con comandos SQL y otras cosas, entonces era común conseguir comillas simples y dobles por todos lados. Por cuestiones que no vienen al caso, necesitaba manipular cada línea con funciones AWK; el cual colapsaba con todos estos caracteres especiales.

Aquí la solución, primero el comando que me hizo ver la luz:

$ echo $(printf '%q' $line)

La función mostrada arriba escapa todos los caracteres especiales. Ahora sólo me quedaba depurar las líneas del archivo y ejecutar un comando para cada una de ellas:

#!/bin/bash
cat PROBLEMATIC_FILE |
while read line; do
  echo $(printf '%q' $line) |
  awk -v _SQ="'" '{
   # ... process
    print $0
  }' 
done 


miércoles, 11 de agosto de 2010

Cómo calcular el md5 para cada línea de un flujo de bytes

Necesitaba calcular el md5 para cada línea de un flujo de bytes y así obtener un identificador; claro, siempre y cuando siempre existiese algo que hacía cada línea diferente.

Pensé en el md5 de cada línea. Aquí les dejo la barajita...

$ cat FILE | awk -v _SQ="'" '{ system("echo " _SQ $0 _SQ " | md5sum") }'

Enjoy!

PD. Si se fijan, podría decidir que partes de cada línea consideraré para mi identificador... utilizando $1, $2, $3, etc... Este es realmente el sentido de esta barajita... porque sino podría obviar el AWK.


martes, 10 de agosto de 2010

Cómo obtener los privilegios (grants) de todos los usuarios de una BD MySQL

MySQL ofrece un comando para ver los privilegios (grants - o más bien concesiones) de un usuario, éste es: show grants for user@machine. Recuerde que el comodín para máquinas es: %.

Luego, me preguntaba cómo hacer para obtener todos los privilegios para todos los usuarios y de una vez en un formato que pudiese llevar a otro manejador (para restaurar), pues casualmente conseguí el dato aquí y lo comparto con ustedes porque lo considero de gran utilidad.

$  mysql -u root --batch --skip-column-names -e "SELECT user, host FROM user" mysql | sed 's,\t,"@",g;s,^,show grants for ",g;s,$,";,g;' | mysql --batch --skip-column-names | sed 's,$,;,g'

La salida estará lista para ingresarla en otro manejador. Ahora, si lo que se desea es simplemente ver el desglose de los permisos de un usuario, puede ejecutar:
$ mysql -u root -e "SELECT * FROM information_schema.USER_PRIVILEGES"


domingo, 8 de agosto de 2010

Barajitas premiadas (#16) - shell scripts y php

Los shell scripts son súper útiles. Luego, hay veces en las que necesitamos apoyarnos en algún otro lenguaje script para realizar ciertas tareas, entre los más comunes: perl, python y ruby.

Como he trabajado bastante con PHP y me gusta este lenguaje, pues lo prefiero sobre los mencionados anteriormente. Aquí les dejo un ejemplo de PHP y BASH donde muestro algo de aritmética de fechas, espero sea de utilidad!

date1='2010-08-01'
date2='2010-08-20'

result=`echo '<?php
  $date = "'$date1' - 2 day";
  $dateTime = new DateTime($date);
  echo $dateTime->format("Y-m-d");
?>' | php` 

echo "$date1 - 2 day = $result"

result=`echo '<?php
  $date = "'$date1' + 2 week 4 hour 2 min 1 sec";
  $dateTime = new DateTime($date);
  echo $dateTime->format("Y-m-d H:i:s");
?>' | php` 

echo "$date1 + 2 week 4 hour 2 min 1 sec = $result"

result=`echo '<?php
  $date1 = "'$date1'";
  $date2 = "'$date2'";
  $days = (strtotime($date2) - strtotime($date1)) / (60 * 60 * 24); // msec(*60)->sec(*60)->hour(*24)->day
  echo $days;
?>' | php` 

echo "$date2 - $date1 = $result days"


sábado, 7 de agosto de 2010

Ejecutando comandos sobre un programa en ejecución y capturando sus salidas (en PHP)

En este artículo les dejo un código que me causo varios dolores de cabeza, para el que tuve que hacer un montón de pruebas hasta lograrlo.

Para mi proyecto phpautoconf estaba necesitando, en PHP, abrir un proceso (programa en ejecución), ejecutar comandos y tras cada uno, capturar su salida. Parece sencillo, pero el problema es que PHP por defecto maneja los flujos de forma bloqueante, según lo explicado aquí, e intentar manejarlos de forma diferente requiere de algunas "cosillas interesantes".

La solución:

<?php
// file: proc.php

// proc_open parameters
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from    1 => array("pipe", "w"),  // stdout is a pipe that the child will write to    2 => array("pipe", "w") // stderr is a file to write to
); $cwd = '/tmp'; $process = proc_open('bash', $descriptorspec, $pipes, $cwd); // stream_select parameters (waits until something can be read from pipe) $read = array($pipes[1]); $write = null; $except = null; $readTimeout = 5; if (is_resource($process)) { stream_set_blocking($pipes[1], FALSE); fwrite($pipes[0], "echo 'hola'\n"); fflush($pipes[0]); stream_select($read, $write, $except, $readTimeout); $output = fgets($pipes[1]); echo "output = $output\n"; fwrite($pipes[0], "echo 'chao'\n"); fflush($pipes[0]); stream_select($read, $write, $except, $readTimeout); $output = fgets($pipes[1]); echo "output = $output\n"; fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); $return_value = proc_close($process); echo "return_value = $return_value\n"; } ?>

Las "cosillas interensantes":
  1. stream_set_blocking($pipes[1], FALSE);. Define la tubería de salida como no bloqueante. En términos prácticos, permitirá obtener la salida sin necesidad de cerrar el flujo (fclose).
  2. fflush($pipes[0]). Libera el flujo de entrada (flush).
  3. \n al final de cada comando escrito (fwrite). Le indica al proceso, en este caso la consola bash, que finalizó el comando.

Como dato interesante, conseguí en varios foros que mucha gente utilizaba la función sleep entre escritura y lectura para dar tiempo a que se llenara el flujo de salida. Sugiero mejor utilizar la función stream_select, que espera un máximo de tiempo por la respuesta, pero si ésta se recibe antes, se libera el bloqueo. Para ejecutar:

$ php proc.php


jueves, 5 de agosto de 2010

Cambiando fechas en ficheros

Este artículo va de algo que tuve que resolver hoy... Tenemos un proceso que genera archivos con fecha, del tipo: archivo_YYYYMMDD.gz. El problema: estos archivos debían tener la fecha del día anterior.

Me explico mejor, el siguiente archivo: mi_archivo_20100805.gz, debía llamarse en realidad: mi_archivo_20100804.gz, como pueden ver debía restar 1 al día que era impreso como fecha y cambiar el nombre de cada archivo.

Inicialmente ataqué el problema con AWK, pero me di por vencido cuando me enfrenté a los archivos con día 01, no podía colocar día 00, debía ser el último día del mes anterior y con esto, otro de los más grandes problemas... Qué mes? año bisiesto? etc, etc...

Entonces, obviamente necesitaba de algún lenguaje de programación que me permitiera hacer operaciones aritméticas con fechas, por ejemplo: PHP.

Aquí les dejo el script que creé, espero sea de utilidad para alguien:

# file: replace_name_-1day.sh
#!/bin/bash

if [ $# -lt 3 ] # validating parameters
then
  echo "Usage: replace_name_-1day.sh prefix date dir"
  echo "date - must be in Y-m-d format"
  exit
fi

prefix=$1
date=$2
dir=$3

fDate=`echo '<?php 
  $date = "'$date'";
  $dateTime = new DateTime($date);
  echo $dateTime->format("Ymd");
?>' | php` # file actual date

fNewDate=`echo '<?php 
  $date = "'$date' - 1 day";
  $dateTime = new DateTime($date);
  echo $dateTime->format("Ymd");
?>' | php` # file new date

mv $dir/$prefix\_$fDate.xml.gz $dir/$prefix\_$fNewDate.xml.gz

Para correr esto pueden seguir el siguiente ejemplo:
$ date=`date +%Y-%m-%d` # sets today's date in format YYYYMMDD
$ dateFile=`date +%Y%m%d` # sets today's date in format YYYY-MM-DD
$ touch mio_$dateFile.gz # generates an empy file with the specified name
$ ./replace_name_-1day.sh mio $date dir 

Se preguntarán por qué manejar la fecha con diferentes formatos (20100805 ó 2010-08-05), pues resulta que es un parámetro que necesita otro programa que no está en este artículo...



miércoles, 4 de agosto de 2010

Buscar y reemplazar una cadena de caracteres por otra

Buscar y reemplazar una cadena de caracteres (string) por otra es pan nuestro de cada día!

La cuestión es cuando tenemos algún archivo muy grande, de esos que no podemos abrir ni siquiera con un bloc de notas (unos 2GB?) y deseamos realizar esta operación... Alguien familiarizado con sistemas *nix podría decir... Ah, pero abro el archivo con VI y listo... Quisiera ver si tenemos tanta suerte trabajando en un computador de sólo 1GB de RAM y abriendo un archivo de unos 10GB. Claro siempre podríamos tirar a la papelera ese viejo computador y comprar otro ;D

Pero eso no es necesario, siempre podemos valernos de una de las mejores herramientas que *nix ha parido, SED (Stream EDitor). Este programa toma un flujo de salida y lo procesa, línea por línea, según alguna tarea definida.

Por ejemplo, si deseo reemplazar en un archivo todas las coincidencias de la cadena "NOTEPAD mola" por "mi NOTEPAD no mola tanto", puedo utilizar el siguiente comando:

$ sed 's:NOTEPAD mola:mi NOTEPAD no mola tanto:g' MI_ARCHIVO

Fíjese que utilizo ":" para separar: el comando "s" (busca y reemplaza), de la cadena a buscar (NOTEPAD mola), de la cadena que utilizaremos para reemplazar las coincidencias (mi NOTEPAD no mola tanto) y el comando "g" (global - para todas las coincidencias en la línea). NOTA: Puedo reeemplazar los ":" por "/".


martes, 3 de agosto de 2010

Barajitas premiadas (#15) - ssh proxy

Si tenemos acceso restringido en nuestra máquina hacia la Internet, pero tenemos acceso SSH a otra máquina que no tiene estas restricciones, o estamos detrás de alguna red y queremos acceder a otra... Siempre podemos iniciar un túnel SSH que nos sirva como suerte de Proxy (utilizando la opción -D).

Voy a dar un ejemplo concreto... Hay una máquina que tiene un servidor Apache HTTP en una red diferente a la mia que ofrece un recurso que deseo, como tengo acceso SSH a una máquina con vista a ambas redes, puedo iniciar un túnel:

$ ssh -D 1234 user@server

Esto definirá un túnel (SOCKS) en mi máquina a través del puerto 1234, al que puedo conectarme para tener acceso a la otra red... Entonces, coloco en mi navegador Web como proxy SOCKS (máquina: localhost, puerto: 1234) y listo... Algo como http://DIRECCION_IP_OTRA_RED/RECURSO tendría sentido.


Barajitas premiadas (#14) - bc

Aquí les dejo un método veloz para convertir números en notación decimal a binaria, octal o hexadecimal... (También puede utilizar gcalctool para hacer esto en modo gráfico)

Vamos a convertir 192 (decimal) a binario, octal y hexadecimal...
$ echo 'obase=2;192' | bc
$ echo 'obase=8;192' | bc
$ echo 'obase=16;192' | bc

Puede hacer la operación inversa con la opción ibase (Ej. ibase=2;1010 => 10).


lunes, 2 de agosto de 2010

domingo, 1 de agosto de 2010

Procesando los archivos allCountries.txt (ciudades) y countryInfo.txt (países) de GeoNames

Hace unos días les dejé una barajita en la que comentaba de dónde se pueden sacar ciudades y países del mundo en un formato actualizado. Ver el siguiente enlace.

Estuve intentando llevar esta info a mi BD. Descargué del siguiente enlace los archivos allCountries.zip y countryInfo.txt que contienen las ciudades y países respectivamente.

El problema se presentó al llevar esta info a una BD, debía procesar los archivos, que tienen un montón de data que no me interesaba, y convertirla a queries.

Aquí les dejo un script que creé que extrae información de ambos archivos y genera los scripts city.sql y country.sql para PostgreSQL.

#!/bin/bash

####################
# List interest fields (cities - city.sql)
# $1 - geonameid         : integer id of record in geonames database
# $2 - name              : name of geographical point (utf8) varchar(200)
# $9 - country code      : ISO-3166 2-letter country code, 2 characters
# $18 - timezone          : the timezone id (see file timeZone.txt)

# DDL
echo "CREATE TABLE city(geonameid int, name varchar(200), country character(2), timezone varchar(100));" > city.sql
echo >> city.sql 

# INSERTS
# 1. Search ' and replace for ''
# 2. Construct the query (look field separator \t and single quote variable _SQ)
sed "s:':'':g" allCountries.txt | awk -F "\t" -v _SQ="'" '{
  print "INSERT INTO city(geonameid, name, country, timezone) VALUES(" \
  $1 ", " _SQ $2 _SQ ", " _SQ $9 _SQ ", " _SQ $18 _SQ ");"
}' >> city.sql


####################
# List interest fields (countries - country.sql)
# $1 - ISO
# $5 - Country
# $6 - Capital
# $9 - Continent
# $16 - Languages
# $17 - geonameid

# DDL
echo "CREATE TABLE country(iso character(2), country varchar(200), capital varchar(200), continent character(2), languages varchar(200), geonameid int);" > country.sql
echo >> country.sql

# INSERTS
# 1. Discarting rows that begins with #
# 2. Search ' and replace for ''
# 3. Construct the query (look field separator \t and single quote variable _SQ)

grep -v "^#" countryInfo.txt | sed "s:':'':g" | awk -F "\t" -v _SQ="'" '{
  print "INSERT INTO country(iso, country, capital, continent, languages, geonameid) VALUES(" \
  _SQ $1 _SQ ", " _SQ $5 _SQ ", " _SQ $6 _SQ ", " _SQ $9 _SQ ", " _SQ $16 _SQ ", " $17 ");"
}' >> country.sql 

####################
# List continents

# grep -v "^#" countryInfo.txt | awk -F "\t" '{ print $9 }' | sort | uniq -d 

Se puede ver que el script se vale de las herramientas por excelencia para trabajar con flujos de datos en *nix, a saber: GREP, SED y AWK. Espero sacar varias barajitas de este mismo artículo, como hice con Chat p2p Netcat cifrado con OpenSSL.

De momento creo que es importante mencionar que los archivos fuente poseen la info en líneas, donde cada campo es separado por un TAB (\t). Para procesar esto utilicé AWK y para cada línea extraigo el campo de interés a través de su índice (su posición en la línea según cada TAB). Es muy común realizar procesos como este valiéndose de hojas de cálculo (Ej. Excel)... Se coloca toda la info en una hoja de cálculo y luego se arman los queries utilizando la función de concatenación. El problema está cuando se tiene un archivo muy pesado (como en este caso setecientos y pico de megas) y al abrir el mismo con Excel o Calc, el computador se cuelga.