Mail list statistics

Thursday, June 15th, 2006 - Español English

An ancient script which I used to compute some stats on who posted how much in mailing lists. Get it here, browse the files and examples, or see the code… (sorry comments in spanish)

  1.  
  2. #!/bin/bash
  3. #
  4. # Miguel de Benito <nonick AT 8027 DOT org>
  5. #
  6. # [ MBD Wed Jan 15 03:59:51 CET 2003 ]
  7. # Computa una lista de las direcciones de correo dentro de un 'mailbox', e
  8. # imprime el número de veces que aparecen y el nombre junto a la dirección.
  9. #
  10. # [ MBD Thu Jan 16 04:28:02 CET 2003 ]
  11. # Un poquito de html, un poquito de css, un par de chorradas y... :-P
  12. #
  13. # [ MBD Fri Jan 17 02:37:32 CET 2003 ]
  14. # Muchas modificaciones, todo lo de los timestamps. Funciones.
  15. # FIXME: MUY lento! 'tabla_top 20' tarda 1m10s !!
  16. # posibles causas: la conversión a timestamp por medio de date en el bucle
  17. # de tabla_top(). El uso de read en el mismo bucle.
  18. #
  19. # [ MBD Fri Jan 17 15:41:58 CET 2003 ]
  20. # Aún más modificaciones. Los arrays 'a_froms' y 'a_tstamps' evitan el uso de
  21. # 'read' en el bucle de tabla_top (que era lo que ralentizaba todo). Ahora
  22. # termina en 5 segundos. ¿Habrá problemas con arrays muy grandes? De momento
  23. # 'memusage' indica cerca de 390k empleados.
  24. # En teoría no hay problema de memoria, pero recuerdo haber leído algo de que
  25. # el entorno de una shell era reducido. ¿?
  26.  
  27. DEBUG=0
  28. MBOX=~/Mail/.listas.directory/suse-linux-s
  29. MAX=20 #valor por defecto.
  30. #HEAD=html/head.html
  31. #FOOT=html/foot.html
  32. HEAD=~/work/bash/rank/html/head2.html
  33. FOOT=~/work/bash/rank/html/foot2.html
  34.  
  35. # Ficheros temporales. f_res guarda los resultados.
  36. # TODO: podría usar otro array sin problemas.
  37. f_tmp=$(mktemp /tmp/rank.sh.XXXXXX)
  38. f_uniq=$(mktemp /tmp/rank.sh.XXXXXX)
  39.  
  40. # Arrays.
  41. declare -a a_froms
  42. declare -a a_tstamps
  43.  
  44. ###############################################################################
  45. # Esto borra los fichero temporales al finalizar o pulsar Control-C,
  46. # por si nos aburrimos durante el proceso.
  47. # $FUNCNAME y $SECONDS son variables especiales de bash.
  48. ###############################################################################
  49. function trap_exit ()
  50. {
  51. echo -n "$FUNCNAME(): Borrando ficheros temporales... " 1>&2
  52. rm -f $f_tmp
  53. rm -f $f_uniq
  54.  
  55. echo "Saliendo." 1>&2
  56. echo "Tiempo de ejecución: $SECONDS segundos." 1>&2
  57.  
  58. logger -p user.info "$0: terminado ($SECONDS s)"
  59. exit 0
  60. }
  61. ###############################################################################
  62. # Depurar en bash es muy complicado, mejor algunos mensajes informativos. ;-)
  63. ###############################################################################
  64. function debug ()
  65. {
  66. if [ "$DEBUG" == "1" ]
  67. then
  68. echo $1 1>&2
  69. else
  70. return
  71. fi
  72. }
  73.  
  74. ###############################################################################
  75. # Modifica las direcciones de correo para que no puedan ser capturadas por
  76. # bots de spammers.
  77. ###############################################################################
  78. function antispam ()
  79. {
  80. echo $1 | sed -e 's/@/ AT /g' -e 's/\./ DOT /g'
  81. }
  82.  
  83. ###############################################################################
  84. # Construye los arrays $a_froms y $a_tstamps (remitentes y fechas de todos los
  85. # mensajes)
  86. # NOTA: a_froms no tiene por qué ser un array, pero lo dejo así por claridad
  87. # (podría ser simplente una gran cadena)
  88. ###############################################################################
  89. function init_arrays ()
  90. {
  91. cat /dev/null > $f_tmp
  92.  
  93. # 1. Buscamos las líneas From_ .
  94. # 2. Esto lo agrega la lista de suse.
  95. # 3. Nos quedamos sólo con las direcciones.
  96. # 4. Algunas tienen comillas, las quitamos.
  97. # 5. Todo en minúsculas.
  98. a_froms=($(grep "^From .*$" $MBOX |\
  99. grep -v "suse-linux-s-return-.*" |\
  100. cut -d" " -f2-2 |\
  101. tr -d '"' |\
  102. tr [:upper:] [:lower:]))
  103.  
  104. # Listamos las fechas de los mensajes
  105. grep "^From .*$" $MBOX |\
  106. grep -v "suse-linux-s-return-.*" |\
  107. cut -d" " -f3- > $f_tmp
  108.  
  109. # Esto convierte la fecha a un timestamp.
  110. # idiota de mí, me dí cuenta después de escribir 'tstamp.c' para
  111. # lo mismo.
  112. # Formato: Thu Jan 17 00:21:55 2003
  113. # a_tstamps es declarado como un array gracias a los paréntesis.
  114. a_tstamps=( $(date -f"$f_tmp" +"%s") )
  115.  
  116. debug "$FUNCNAME(): fin."
  117. }
  118.  
  119. ###############################################################################
  120. # Computa el número de gente que escribió desde una fecha determinada y
  121. # lo escribe en el fichero temporal $f_uniq.
  122. #
  123. # - $1: Máximo de direcciones escritas al fichero. (def: todas)
  124. # - $2: Si está presente, timestamp con la fecha inicial deseada
  125. # (def: 0 ==> desde el principio)
  126. #
  127. # Escribe el total de mensajes procesados y el número que cumple el requisito
  128. # indicado, en la salida estándar.
  129. # TODO: (sencillo) incluir un tercer parámetro con otra fecha para poder
  130. # escoger rangos.
  131. ###############################################################################
  132. function ultimos ()
  133. {
  134. local tmp
  135. local max
  136. local cnt=0
  137. local cnt2=0
  138.  
  139. # Borramos los ficheros con los que vamos a trabajar
  140. cat /dev/null > $f_tmp
  141. cat /dev/null > $f_uniq
  142.  
  143. # Forma alternativa de hacer lo de abajo, pero bastante menos clara.
  144. # max=${1-$MAX}
  145. # cmp=${2-"0"}
  146. if [ -n "$1" ]; then max=$1; else max=$MAX; fi
  147. if [ -n "$2" ]; then cmp=$2; else cmp=0; fi
  148.  
  149. debug "$FUNCNAME(): buscando max=$max, desde $cmp"
  150.  
  151. # Preparamos el fichero con el formato de línea "timestamp direccion"
  152. tmp=${#a_froms[*]} # número de elementos en el array
  153. for (( cnt=0; cnt < tmp; cnt++))
  154. do
  155. # la construcción (( )) evalúa expresiones con sintáxis casi de C
  156. if (( a_tstamps[cnt] >= cmp ))
  157. then
  158. echo "${a_tstamps[$cnt]} ${a_froms[$cnt]}" >> $f_tmp
  159. let cnt2++
  160. fi
  161. done
  162.  
  163. # a.k.a "return", ;-)
  164. echo -n "$cnt $cnt2"
  165.  
  166. debug "$FUNCNAME(): cnt=$cnt, cnt2=$cnt2"
  167.  
  168. # 1. Ordenamos (según el segundo campo)para poder contar.
  169. # El orden es inverso para que las fechas empiecen por la más reciente.
  170. # 2. Contamos las apariciones y las ponemos.
  171. # 3. Ordenamos de mayor a menor número.
  172. # 4. Tomamos sólo los $max primeros
  173. cat $f_tmp |\
  174. sort -r -k2,3|\
  175. uniq --count --skip-fields 1 |\
  176. sort -r |\
  177. head -$max > $f_uniq
  178.  
  179. # Ya no necesitamos estos arrays.
  180. unset a_froms
  181. unset a_tstamps
  182.  
  183. debug "$FUNCNAME(): fin."
  184. }
  185.  
  186.  
  187. function tabla_top ()
  188. {
  189. local linea
  190. local i
  191. local mail
  192. local remite
  193. local num
  194. local cmp
  195. local tstamp
  196. local ret
  197. declare -a ret
  198.  
  199. # Preparamos la lista.
  200. init_arrays
  201.  
  202. # Queremos los $1 primeros desde la fecha $2
  203. if [ -n "$1" ]; then num=$1; else num=20; fi
  204. if [ -n "$2" ]; then cmp=$(date -d"$2" +"%s"); else cmp=0; fi
  205.  
  206. debug "$FUNCNAME(): cmp=$cmp"
  207.  
  208. # ${var-blabla}: si var es nula, usa blabla por defecto.
  209. # la primera línea del archivo es una "From", con la fecha.
  210. echo "<h3>Las $num personas que más han escrito desde ${2-$(head -1 $MBOX|cut -d" " -f3-)}:</h3>"
  211.  
  212. ret=( $(ultimos $num $cmp) )
  213.  
  214. debug "$FUNCNAME(): ret=${ret[*]}"
  215. # ultimos() escribe en la salida estándar el número de mensajes.
  216. echo "<p>${ret[0]} mensajes procesados en el archivo. ${ret[1]} cumplen el requisito.</p>"
  217. echo "<table class=\"rank\" cellspacing=\"0\">"
  218. echo "<tr><th>Posición</th> <th>Nombre</th> <th>Mensajes</th></tr>"
  219.  
  220. # Truquillo para colorear las líneas...
  221. i=1
  222.  
  223. # Buscamos los nombres asociados a las direcciones de correo.
  224. # Para eso necesitamos las líneas "From:" (nótense los dos puntos)
  225. while read linea
  226. do
  227. # Las líneas tienen el formato: <num> <tstamp> <mail>
  228. num=$(echo $linea | cut -d" " -f1)
  229. tstamp=$(echo $linea | cut -d" " -f2)
  230. mail=$(echo $linea | cut -d" " -f3)
  231.  
  232. # 'mkdate' es un cortísimo programa en C que convierte un timestamp
  233. # a una fecha usando la función ctime() ( <time.h> )
  234. tstamp=$(~/bin/mkdate "$tstamp")
  235. # Tomamos sólo la primera vez que aparece el nombre.
  236. # FIXME: MMMMUUUUUYYYYY lento... ¿podría parar grep en el primer
  237. # resultado? ¿Construir un fichero una sola vez y luego buscar en él?
  238. remite=$(grep "^From:.*${mail}.*" $MBOX | head -1 | cut -d":" -f2)
  239.  
  240. # Nos quedamos con el nombre, sin la dirección.
  241. remite=$(echo $remite | tr -d '"' | cut -d"<" -f1)
  242.  
  243. mail=$(antispam $mail)
  244.  
  245. # Si en el From: no había nombre, sólo el email, $remite contiene a lo
  246. # sumo un espacio (debido al cut -d"<")
  247. if [[ "$remite" < " " ]]
  248. then
  249. remite=$mail
  250. fi
  251.  
  252. # Coloreamos las líneas (según la hoja de estilo)
  253. # pares:rank1. impares:rank2.
  254. if (( i % 2 ))
  255. then
  256. echo "<tr class=\"rank1\">"
  257. else
  258. echo "<tr class=\"rank2\">"
  259. fi
  260. cat <<-EOF
  261. <td class="rankpos"><b>$i</b></td>
  262. <td class="ranknombre"><a href="mailto:$mail">$remite</a><br /><div class="rankfecha">Último mensaje: $tstamp</div></td>
  263. <td class="ranknum"><b>$num</b></td>
  264. </tr>
  265. EOF
  266. let i++
  267. done < $f_uniq
  268.  
  269. echo "<tr><td class=\"rankpie\" colspan=\"3\">Archivo actualizado el $(find $MBOX -printf "%t")</td></tr>"
  270. echo "</table>"
  271.  
  272. debug "$FUNCNAME(): fin."
  273. }
  274.  
  275. #######################
  276. ## main() ;-)
  277. #######################
  278.  
  279. trap trap_exit EXIT
  280. trap trap_exit SIGINT
  281.  
  282. cat $HEAD
  283. debug "$HEAD incluido."
  284.  
  285. # Dos ejemplos
  286. #tabla_top 20
  287.  
  288. #tabla_top 20 "Wed Jan 1 00:00:01 2003"
  289.  
  290. cat $FOOT
  291. debug "$FOOT incluido."
  292.  

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>