viernes, 19 de octubre de 2012

Histogramas

Uso análisis de frecuencia muy seguido. Una especie de

SELECT COUNT(*), campo FROM cosa GROUP BY campo

se logra en shell scripts con

sort | uniq -c

(siempre que consideremos que 'campo' es toda la línea)

El comando uniq toma el input y lo repite en el output, removiendo duplicados. La opción -c indica la cantidad de repeticiones. Como no ordena, tenemos que hacerlo previamente.

Si hiciéramos simplemente uniq -c, nos podrían quedar varios bloques para el mismo valor.

Pero si nuestro input tiene grandes bloques de valores repetidos, un sort de todos contra todos los valores gastaría tiempo en ordenar internamente esos bloques, cuando uniq -c los reduciría rápidamente.

Hoy me soprendí una vez más de la potencia de awk. Mi idea fue hacer un uniq -c inicial, sin sort, y resolver la duplicación de bloques con awk, así:


uniq -c | awk '{h[$2]+=$1} END {for (i in h) print h[i],i}'

O si queremos emular EXACTA y coquetamente el resultado del uniq:

uniq -c | awk '{h[$2]+=$1} END {for (i in h) printf "%7d %d\n", h[i],i}'

Explico:

Para cada línea del uniq -c que es de forma:
Metemos en un array asociativo indexado por "valor" la frecuencia (sumando al valor que ya tenemos).
Cuando llegamos al final (END), mostramos el array.

Hice cuentas con un archivo de 7.9Gb con grandes bloques, y para mi asombro sort no la pasó taaan mal. Sospecho que sort está usando alguna clase de mergeSort para ordenamiento en memoria secundaria.

Comparo los tiempos de hacer un simple uniq -c (que no resuelve nuestro porblema), un sort | uniq -c y mi sugerencia. Asombrosamente, el mío anda más rápido que el uniq -c a solas. Creo que eso está dentro de la tolerancia de la medición, o hubieron factores externos en el server.

uniq.time
real    5m40.97s
user    6m25.65s
sys     0m2.47s

sort.time
real    7m4.77s
user    7m26.90s
sys     0m4.57s

mio.time
real    5m37.56s
user    6m20.91s
sys     0m2.35s

miércoles, 3 de agosto de 2011

Menú loco v0.1

El comando select de shell es muy copado (lean el man).
Por ejemplo:

select file in main.c functions.c config.h
do
vim $file
done

Pero tiene la desgracia de
  1. Usar números para las opciones
  2. Necesitar ^C para salir (a menos que pongamos una opción que tendrá un número para elegirla)
Entonces, les presento un minitruquín the groncher, the better para evitar ambos problemas:

function menu {
keyword=$1
echo
echo "Elija opción: "
grep "# $keyword" $0 | fgrep -v grep | sed "s/;;.*//"
}

while :
do
menu File
read -n1 op
case $op in
f) file=main.c ;; # File
c) file=functions.c ;; # File
r) file=config.h ;; # File
q) echo ; exit ;; # File
*) continue;;
esac

vim $file
done

Sí... la presentación de las opciones del menú son comandos unix, no son nombrecitos.
Se invita a los señores lectores a proponer la v0.2

lunes, 1 de agosto de 2011

Sacame lo' yuyo'

vim: "Eliminá todas las líneas que digan 'GO' desde el renglón actual hasta el renglón que dice 'end'"

:.,/end/g/GO/d

Les dejo el abracadabra y el que tenga curiosidad que lo investigue.

jueves, 9 de junio de 2011

Estupidez svn

function svnInfo {
att="$*"
svn info | grep "$att" | sed "s/$att: //g"
}

lastrev=$(svnInfo "Last Changed Rev")
rev=$(svnInfo "Revision")

miércoles, 18 de mayo de 2011

Sum by

Para calcular cuántas horas por día trabajo, necesité una especie de sum(horas) GROUP BY día a partir del siguiente input de horas por tarea:

$ donetasks
2011-05-17 1.00 FrancesGO2 -> Schedule
2011-05-17 4.50 FrancesGO2 -> Deployment
2011-05-17 1.50 FrancesGO2 -> Sysadmin, environments
2011-05-17 0.50 FrancesGO2 -> Analisis Funcional
2011-05-17 0.50 FrancesGO2 -> Meetings
2011-05-17 1.00 FrancesGO2 -> Tooling
2011-05-18 0.50 FrancesGO2 -> Deployment

Decidí que el Aho, Weinberger y Kernighan vendrían al rescate. Hice un script que llamé sumby que toma los índices de dos columnas: por cuál agrupar, y cuál sumar:

awk -v group=$1 -v sum=$2 '
{ bag[$group]+=$sum }
END { for (val in bag) { print val ": " bag[val] } }
'

Lo uso así:

$ donetasks | sumby 1 2
2011-05-17: 9
2011-05-18: 0.5

viernes, 20 de agosto de 2010

Defól

Sintaxis poco conocida de bash.
Si tu escrí toma un parámertro y querés que pueda tener un defól, hacé así.

$TIMEOUT=${1:-5}
watch -n $TIMEOUT -d ls -l

En este caso, tu escrí va a tomar el TIMEOUT del primer parámetro ($1) pero si $1 es nulo, tomará valor 5.

man bash:

${parameter:-word}
Use Default Values. If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.

Más info en la parte Parameter Substitution del bash.

Enconchalo

Truco feliz:

Normalmente uno quiere ver lo que hará un script antes de ejecutarlo.
Normalmente uno hace:

$ echo rm *partial*
(revisa)
$ rm *partial*

pero hay una forma que a mí me da mucho más placer:

$ echo rm *partial*
(reviso)
$ echo rm *partial*|sh

Y ahorro muchos keystrokes!