<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>tail -f &#124; sysadmin &#187; BASH</title>
	<atom:link href="http://www.tail-f.com.ar/tag/bash/feed" rel="self" type="application/rss+xml" />
	<link>http://www.tail-f.com.ar</link>
	<description>Noticias y recursos para sysadmins Unix</description>
	<lastBuildDate>Mon, 28 Nov 2011 21:44:41 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Backups con tar + rsync + rotación</title>
		<link>http://www.tail-f.com.ar/programacion/bash/backups-con-tar-rsync-rotacion.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/backups-con-tar-rsync-rotacion.html#comments</comments>
		<pubDate>Thu, 02 Sep 2010 14:29:02 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[rsync]]></category>
		<category><![CDATA[tar]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=416</guid>
		<description><![CDATA[En estos días tuve que configurar unos backups para un cliente. Algo bastante sencillo. Tenía dos servidores y necesitaba hacer backups de distintas cosas en cada equipo y copiarlos al otro. Por supuesto, la herramienta clave para esto es rsync. Googleando un poco vi que existe esta página donde se puede ver cómo generar snapshots [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.tail-f.com.ar/wp-content/uploads/Backup.jpg"><img class="alignright size-medium wp-image-417" title="Backup" src="http://www.tail-f.com.ar/wp-content/uploads/Backup-300x225.jpg" alt="" width="300" height="225" /></a>En estos días tuve que configurar unos backups para un cliente. Algo bastante sencillo. Tenía dos servidores y necesitaba hacer backups de distintas cosas en cada equipo y copiarlos al otro. Por supuesto, la herramienta clave para esto es <a href="http://www.samba.org/rsync/">rsync</a>. Googleando un poco vi que existe <a href="http://www.mikerubel.org/computers/rsync_snapshots/">esta página</a> donde se puede ver cómo generar snapshots incrementales automáticos usando hard links y rsync. Realmente es una solución muy interesante, pero a mí no me terminaba de convencer porque yo quería tener archivos comprimidos que fueran rotando.</p>
<p>Por lo tanto lo que hice fue, seguramente, reinventar la rueda una vez más. Hice una pequeña librería en BASH que luego reutilicé en los distintos scripts de backup que hice. La clave aquí es la función &#8220;create_archive&#8221; que toma un path, un nombre de archivo base y una lista de archivos, y se ocupa de ir al path, ver todos los archivos que haya con ese patrón de nombre básico, rotarlos y generar un nuevo tar.gz. Después agregué un par de funciones más que me venían bien, &#8220;log&#8221; para registrar todo lo que pasaba en un archivo, &#8220;mail_notification&#8221; para mandarme mails y &#8220;sync_backup_servers&#8221; para ejecutar rsync.</p>
<p>El código:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re3">#!/bin/bash</span><br />
<span class="re2">CONF_FILE=</span><span class="st0">&quot;/home/sysbackup/scripts/backup.conf&quot;</span><br />
<span class="kw1">if</span> <span class="br0">&#91;</span> -f <span class="re1">$CONF_FILE</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; . <span class="re1">$CONF_FILE</span><br />
<span class="kw1">else</span><br />
&nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Configuration file $CONF_FILE not found&quot;</span><br />
&nbsp; &nbsp; <span class="kw3">exit</span> <span class="nu0">1</span><br />
<span class="kw1">fi</span></p>
<p><span class="re2">RM_BIN=</span><span class="st0">&quot;/bin/rm&quot;</span><br />
<span class="re2">TAR_BIN=</span><span class="st0">&quot;/bin/tar&quot;</span><br />
<span class="re2">MAIL_BIN=</span><span class="st0">&quot;/bin/mail&quot;</span><br />
<span class="re2">RSYNC_BIN=</span><span class="st0">&quot;/usr/bin/rsync&quot;</span></p>
<p><span class="kw1">function</span> log<br />
<span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;`date`: $*&quot;</span> &gt;&gt; <span class="re1">$LOG_FILE</span><br />
<span class="br0">&#125;</span></p>
<p><span class="kw1">function</span> mail_notification<br />
<span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re2">subject=</span><span class="st0">&quot;$1&quot;</span><br />
&nbsp; &nbsp; <span class="re2">message=</span><span class="st0">&quot;$2&quot;</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="st0">&quot;$subject&quot;</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">subject=</span><span class="st0">&quot;Backup Notification&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="st0">&quot;$NOTIFICATIONS_MAILS&quot;</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;mail_notification: empty recipients list&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">return</span> <span class="nu0">1</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="st0">&quot;$message&quot;</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;mail_notification: empty message&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">return</span> <span class="nu0">1</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="re2">sysdata=</span><span class="st0">&quot;Date: `date`<span class="es0">\n</span>Host: `hostname`&quot;</span></p>
<p>&nbsp; &nbsp; <span class="kw3">echo</span> -e <span class="st0">&quot;$message<span class="es0">\n</span><span class="es0">\n</span>$sysdata&quot;</span> | <span class="re1">$MAIL_BIN</span> -s <span class="st0">&quot;$subject&quot;</span> <span class="st0">&quot;$NOTIFICATIONS_MAILS&quot;</span><br />
<span class="br0">&#125;</span></p>
<p><span class="kw1">function</span> sync_backup_servers<br />
<span class="br0">&#123;</span><br />
&nbsp; &nbsp; log <span class="st0">&quot;Running rsync from $LOCAL_DIR to $REMOTE_DIR as $RSYNC_USER&quot;</span><br />
&nbsp; &nbsp; <span class="kw2">su</span> -c <span class="st0">&quot;$RSYNC_BIN -aq &#8211;delete -e ssh $LOCAL_DIR/ $REMOTE_DIR/ 2&gt;&gt; $LOG_FILE&quot;</span> <span class="re1">$RSYNC_USER</span><br />
&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> $? -eq <span class="nu0">0</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;Syncronization complete successfuly&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">else</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;Syncronization finished with errors&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span><br />
<span class="br0">&#125;</span></p>
<p><span class="kw1">function</span> create_archive<br />
<span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re2">file_path=</span><span class="st0">&quot;$1&quot;</span><br />
&nbsp; &nbsp; <span class="re2">file_name=</span><span class="st0">&quot;$2&quot;</span><br />
&nbsp; &nbsp; <span class="re2">files=</span><span class="st0">&quot;${*:3}&quot;</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="re1">$file_path</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Missing file path&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">return</span> <span class="nu0">1</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="re1">$file_name</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Missing file name&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">return</span> <span class="nu0">1</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="re1">$files</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Missing file list&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">return</span> <span class="nu0">1</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> -z <span class="re1">$MAX_BKP_FILES</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">MAX_BKP_FILES=</span><span class="nu0">4</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; <span class="re2">archive_name=</span><span class="st0">&quot;${file_name}_`date +%Y%m%d`.tar.gz&quot;</span><br />
&nbsp; &nbsp; log <span class="st0">&quot;Preparing to create $archive_name archive&quot;</span></p>
<p>&nbsp; &nbsp; <span class="re3"># Begin to Rotate files</span><br />
&nbsp; &nbsp; <span class="re3"># Sort files by <span class="kw2">date</span> and keep the latest ones</span><br />
&nbsp; &nbsp; <span class="re3"># MAX_BKP_FILES will determine how many to store</span><br />
&nbsp; &nbsp; <span class="re2">dir=</span>`<span class="kw2">ls</span> -t <span class="re1">$file_path</span>/<span class="re0">$<span class="br0">&#123;</span>file_name<span class="br0">&#125;</span></span>_*.<span class="kw2">tar</span>.gz <span class="nu0">2</span>&gt; /dev/null`<br />
&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> $? -eq <span class="nu0">0</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">N=</span><span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">for</span> <span class="kw2">file</span> <span class="kw1">in</span> <span class="re1">$dir</span>; <span class="kw1">do</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> <span class="re1">$N</span> -lt <span class="re1">$MAX_BKP_FILES</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">let</span> N++<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">else</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;Removing old file $file&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="re1">$RM_BIN</span> -f <span class="re1">$file</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">fi</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">done</span><br />
&nbsp; &nbsp; <span class="kw1">else</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; log <span class="st0">&quot;No other archive found on $file_path&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span></p>
<p>&nbsp; &nbsp; log <span class="st0">&quot;Creating tar archive $file_path/$archive_name&quot;</span><br />
&nbsp; &nbsp; <span class="re1">$TAR_BIN</span> zcf <span class="st0">&quot;$file_path/$archive_name&quot;</span> <span class="re1">$files</span> <span class="nu0">2</span>&gt;&gt; <span class="re1">$LOG_FILE</span><br />
<span class="br0">&#125;</span><br />
&nbsp;</div>
<p>Algunas consideraciones importantes.</p>
<p><strong>Configuración</strong></p>
<p>Al principio del script lo que hago es incluir un archivo de configuración con algunas variables básicas que voy a utilizar. Este archivo debería ser algo similar a esto:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re3">#!/bin/bash</span><br />
<span class="re2">LOCAL_DIR=</span><span class="st0">&quot;/home/sysbackup/backup/server1.domain.com&quot;</span><br />
<span class="re2">REMOTE_DIR=</span><span class="st0">&quot;sysbackup@server1.domain.com:/home/sysbackup/backup/server1.domain.com&quot;</span><br />
<span class="re2">LOG_FILE=</span><span class="st0">&quot;/var/log/sysbackup.log&quot;</span><br />
<span class="re2">MAX_BKP_FILES=</span><span class="nu0">4</span><br />
<span class="re2">NOTIFICATIONS_MAILS=</span><span class="st0">&quot;admin@email.com&quot;</span><br />
<span class="re2">RSYNC_USER=</span><span class="st0">&quot;sysbackup&quot;</span><br />
&nbsp;</div>
<p>LOCAL_DIR es el directorio local base donde van a estar mis backups. REMOTE_DIR es el directorio equivalente en el otro servidor. Lo que hice fue crear un usuario &#8220;sysbackup&#8221; (un poco menos obvio que &#8220;backup&#8221;, pero también self-explanatory) en los dos servidores y generar llaves para que se puedan conectar por SSH entre los dos servidores. Luego en la /home de cada usuario creé una carpeta &#8220;backup&#8221; y adentro de ella una carpeta para cada host que iba a ser backupeado &#8220;server1.domain.com&#8221; y &#8220;server2.domain.com&#8221;.</p>
<p>LOG_FILE indica el archivo donde se van a guardar los logs del backup y MAX_BKP_FILES determina la cantidad máxima de archivos de backup que va a haber por cada elemento.</p>
<p><strong>Rotación</strong></p>
<p>Como dije, la función más importante es create_archive. La pensé para logs que se crean una vez por día (o cada más tiempo), por lo que si la quieren usar para rotar logs generados en intervalos de tiempo más cortos van a tener que modificarla. Lo que hace es generar un nombre de archivo con una raiz elegida por nosotros (digamos &#8220;db&#8221;) y la fecha actual (la función pone la fecha, pero si necesitan intervalos más cortos van a tener que poner fecha y hora). Luego hace un `ls -t` del directorio donde hay que generar el backup para ver si hay otros archivos que coincidan con el patrón del nombre correspondiente (por ejemplo, &#8216;db_*.tar.gz&#8217;). Si hay archivos, va a conservar los últimos 4 (en realidad la cantidad la determina MAX_BKP_FILES) y va a borrar los más viejos. Por último, va a generar el tar.gz con la lista de archivos que le hayamos pasado.</p>
<p>Ejemplo:</p>
<div class="dean_ch" style="white-space: wrap;">create_archive <span class="st0">&quot;$LOCAL_DIR/web&quot;</span> <span class="st0">&quot;web&quot;</span> <span class="st0">&quot;/var/www/html&quot;</span><br />
&nbsp;</div>
<p>Va a generar con un archivo de nombre como &#8220;web_20100902.tar.gz&#8221; en la carpeta $LOCAL_DIR/web con todos los contenidos de /var/www/html.</p>
<p><strong>Rsync</strong></p>
<p>La función sync_backup_servers hace la sincronización entre los dos servidores usando rsync por SSH. Una cosa a tener en cuenta es que en mi caso, yo necesitaba que el script de backup corriera como root (para poder acceder a todos los archivos que había que backupear) pero quería que el rsync se ejecutara con el usuario sysbackup (para mayor seguridad). Por lo tanto la función usa &#8220;su -c  sysbackup&#8221;. Si a uds. este enfoque no les sirve pueden poner en la función directamente:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re1">$RSYNC_BIN</span> -aq &#8211;delete -e <span class="kw2">ssh</span> <span class="re1">$LOCAL_DIR</span>/ <span class="re1">$REMOTE_DIR</span>/ <span class="nu0">2</span>&gt;&gt; <span class="re1">$LOG_FILE</span><br />
&nbsp;</div>
<p><strong>Notificaciones por mail</strong></p>
<p>Es muy útil poder tener notificaciones por mail, sobre todo cuando las cosas andan mal. El comando &#8220;mail&#8221; de Linux/Unix es muy útil para esto. La funcion mail_notification usa ese comando para mandar un mail con el mensaje que queramos y le agrega dos datos útiles: la fecha y el hostname desde el que se manda. En realidad son datos que siempre viajan en los headers del mail, pero a mí me parecía útil que estuvieran en el body.</p>
<p>Espero que les sirvan estos recursos. No son una solución completa y cerrada, sino herramientas que quizás les sirvan para implementar o al menos pensar cómo hacer sus propios sistemas de backup. Les recomiendo enfáticamente que lean este artículo sobre <a href="http://www.mikerubel.org/computers/rsync_snapshots/">cómo generar backups con rsync</a> porque explica muchas cosas fundamentales.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/backups-con-tar-rsync-rotacion.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Obtener listado de link rotos con wget</title>
		<link>http://www.tail-f.com.ar/programacion/bash/obtener-listado-de-link-rotos-con-wget.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/obtener-listado-de-link-rotos-con-wget.html#comments</comments>
		<pubDate>Mon, 28 Jun 2010 15:03:44 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[scripts]]></category>
		<category><![CDATA[wget]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=371</guid>
		<description><![CDATA[Esas cosas por las que uno ama Linux. Encontré este post en DiarioLinux que me pareció genial. $ wget --spider --no-parent -r -o log.txt http://tuweb.com Parámetros: –spider : recorrer la web que le digas, pero SIN descargar nada. Sólo recorrerla. - r : recursivo, como si fuera el robotito de Google - o fichero : [...]]]></description>
			<content:encoded><![CDATA[<p>Esas cosas por las que uno ama Linux. Encontré <a href="http://diariolinux.com/2010/06/11/receta-como-obtener-listado-de-links-rotos/">este post</a> en <a href="http://diariolinux.com">DiarioLinux</a> que me pareció genial.</p>
<pre>$  wget --spider  --no-parent -r -o log.txt http://tuweb.com</pre>
<p><a href="http://www.tail-f.com.ar/wp-content/uploads/broken-link.jpg"><img class="alignright size-medium wp-image-372" title="broken-link" src="http://www.tail-f.com.ar/wp-content/uploads/broken-link-300x224.jpg" alt="" width="300" height="224" /></a><br />
<strong>Parámetros:</strong></p>
<p>–spider : recorrer la web que le digas, pero SIN descargar nada. Sólo  recorrerla.<br />
- r : recursivo, como si fuera el robotito de Google <img src='http://www.tail-f.com.ar/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /><br />
- o fichero   :  la salida de ejecutar el comando que salga por pantalla<br />
–no-parent : si le pasamos como parámetro un nombre de directorio, no  queremos que suba hacia los directorios padre.</p>
<p>La lista de enlaces rotos estará en log.txt (parte final)</p>
<p>Tomado de <a href="http://diariolinux.com/2010/06/11/receta-como-obtener-listado-de-links-rotos/">DiarioLinux</a>.</p>
<p>Y agrego otro muy útil. Cómo descargar todos los contenidos de un sitio FTP con wget.</p>
<pre>wget -r -q -b -P /home/myuser/destination ftp://mydomain.com --ftp-user=myuser --ftp-password=mypass</pre>
<p><strong>Parámetros:</strong></p>
<p>-r: recursivo<br />
-q: quiet/silencioso, no imprime mensajes en la consola<br />
-b: background, para que se ejecute en background y nos devuelva el prompt.<br />
-P: directorio donde queremos que se guarde todo lo que bajamos<br />
&#8211;ftp-user=: usuario FTP<br />
&#8211;ftp-password=: password FTP</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/obtener-listado-de-link-rotos-con-wget.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ver dominios de Apache que más transferencia consumen</title>
		<link>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/ver-dominios-de-apache-que-mas-transferencia-consumen.html</link>
		<comments>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/ver-dominios-de-apache-que-mas-transferencia-consumen.html#comments</comments>
		<pubDate>Wed, 10 Mar 2010 19:07:44 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[Apache]]></category>
		<category><![CDATA[BASH]]></category>
		<category><![CDATA[access_log]]></category>
		<category><![CDATA[logs]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=350</guid>
		<description><![CDATA[Frecuentemente me encuentro ante la necesidad de saber qué dominios de los que tengo alojados en mis servidores consumen más tráfico HTTP. Como éste es el tipo de tráfico más importante y que más incide en el desempeño general de mis servidores, saber qué dominios transfieren más datos por HTTP me orienta en la búsqueda [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.tail-f.com.ar/wp-content/uploads/nerd.jpg"><img class="alignright size-medium wp-image-354" title="nerd" src="http://www.tail-f.com.ar/wp-content/uploads/nerd-300x227.jpg" alt="" width="300" height="227" /></a>Frecuentemente me encuentro ante la necesidad de saber qué dominios de los que tengo alojados en mis servidores consumen más tráfico HTTP. Como éste es el tipo de tráfico más importante y que más incide en el desempeño general de mis servidores, saber qué dominios transfieren más datos por HTTP me orienta en la búsqueda de heavy users. Para ello hice un script hace algún tiempo, y ahora lo estuve revisando, retocando y simplificando, y decidí compartirlo.</p>
<p>En el entorno de <a href="http://www.tail-f.com.ar/tag/directadmin">Directadmin</a>, los logs de <a href="http://www.tail-f.com.ar/tag/apache">Apache</a> se encuentran en /var/log/httpd. Allí hay una carpeta &#8220;domains&#8221; que guarda los logs de cada dominio. Por cada dominio hay tres logs: dominio.com.log, dominio.com.error.log y dominio.com.bytes. El primero es el access_log, el segundo el error_log y el tercero solamente guarda la cantidad de bytes de cada request. La opción más sencilla es trabajar con este último tipo de logs, pero el problema que tienen es que no indican la fecha. Si bien se supone que los logs de Directadmin rotan una vez por día, alguna vez me ha pasado que por razones que nunca pude descubrir, algunos logs no rotaban y generaban ciertas confusiones. Por lo tanto, el script que dejo usa los access_logs. Además esta opción es reutilizable en otras implementaciones de Apache que no tengan archivos de bytes.</p>
<p>El código:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re3"># Access Log Parser</span><br />
<span class="re3">#</span><br />
<span class="re3"># Parses all the files <span class="kw1">in</span> a directory</span><br />
<span class="re3"># treating them <span class="kw2">as</span> access_log files</span><br />
<span class="re3"># and outputs the list of files sorted</span><br />
<span class="re3"># by transfered megabytes. Useful for</span><br />
<span class="re3"># identifying heavy <span class="kw2">users</span>.</span><br />
<span class="re3">#</span><br />
<span class="re3"># Usage:</span><br />
<span class="re3"># ./access_log_parser.<span class="kw2">sh</span> &lt;base_dir&gt;</span><br />
<span class="re3"># base_dir = directory where the access_log files are</span></p>
<p><span class="kw1">if</span> <span class="br0">&#91;</span> -z $<span class="nu0">1</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Usage: $0 &quot;</span><br />
&nbsp; &nbsp; <span class="kw3">exit</span> <span class="nu0">1</span><br />
<span class="kw1">fi</span></p>
<p><span class="re2">BASE_DIR=</span>$<span class="nu0">1</span><br />
<span class="re2">T1=</span>`<span class="kw2">date</span> +%s`<br />
<span class="kw1">for</span> <span class="kw2">file</span> <span class="kw1">in</span> <span class="re1">$BASE_DIR</span>/*;<br />
<span class="kw1">do</span><br />
&nbsp; &nbsp; <span class="re2">size=</span>`<span class="kw2">stat</span> -c %s <span class="re1">$file</span>`<br />
&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> <span class="re1">$size</span> -gt <span class="nu0">0</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">from=</span>`<span class="kw2">head</span> <span class="nu0">-1</span> <span class="re1">$file</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{print($4)}&#8217;</span> |sed <span class="st0">&#8216;s/^<span class="es0">\[</span>//&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">to=</span>`<span class="kw2">tail</span> <span class="nu0">-1</span> <span class="re1">$file</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{print($4)}&#8217;</span> |sed <span class="st0">&#8216;s/^<span class="es0">\[</span>//&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">bytes=</span>`<span class="kw2">cat</span> <span class="re1">$file</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{a+=$10}END{print a}&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">mbytes=</span>`<span class="kw3">echo</span> <span class="re1">$bytes</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{print $1 / 1048576}&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">echo</span> &nbsp;<span class="st0">&quot;$mbytes MB ($bytes bytes) | From: $from | To: $to | ${file:${#BASE_DIR}+1}&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span><br />
<span class="kw1">done</span> \<br />
| <span class="kw2">sort</span> -nb<br />
<span class="re2">T=</span>`<span class="kw2">date</span> +%s`</div>
<p>Un detalle importante: el script levanta todos los archivos de un directorio que se especifica por parámetro. Esto es así porque yo lo invoco desde otro script:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re3">#!/bin/bash</span><br />
<span class="re2">LOG_DIR=</span>/var/log/httpd/domains/<br />
<span class="re2">TMP=</span>/tmp/domain_logs<br />
<span class="kw1">if</span> <span class="br0">&#91;</span> ! -d <span class="re1">$TMP</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; <span class="kw2">mkdir</span> -p <span class="re1">$TMP</span><br />
<span class="kw1">fi</span></p>
<p><span class="kw2">rm</span> -f <span class="re1">$TMP</span>/*</p>
<p><span class="kw3">cd</span> <span class="re1">$LOG_DIR</span><br />
<span class="kw1">for</span> i <span class="kw1">in</span> `<span class="kw2">ls</span> *.log |grep -v error`; <span class="kw1">do</span><br />
&nbsp; &nbsp; <span class="kw2">ln</span> <span class="re1">$i</span> <span class="re1">$TMP</span>/<span class="re1">$i</span><br />
<span class="kw1">done</span><br />
/root/access_log_parser.<span class="kw2">sh</span> <span class="re1">$TMP</span></div>
<p>Este último script lo que hace es crear un directorio temporal (si no existe). Vaciar su contenido. Y luego generar hardlinks a todos los access_logs de /var/log/httpd/domains. De esta forma, no me tengo que preocupar por si los logs son modificados mientras yo estoy ejecutando el script. Por último, ejecuta mi primer script (access_log_parser.sh) y le pasa como parámetro el directorio temporal donde están los access_logs.</p>
<p>Por último, la salida del script sería algo así:</p>
<pre>...
186.995 MB (196078184 bytes) | From: 10/Mar/2010:00:27:47 | To: 10/Mar/2010:16:51:09 | dominio1.com.ar.log
187.096 MB (196184596 bytes) | From: 10/Mar/2010:03:24:55 | To: 10/Mar/2010:16:51:24 | dominio2.com.ar.log
245.692 MB (257626221 bytes) | From: 10/Mar/2010:02:53:50 | To: 10/Mar/2010:16:39:54 | dominio3.com.log
273.46 MB (286743390 bytes) | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:14:59:29 | dominio4.com.ar.log
306.344 MB (321224473 bytes) | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:16:51:20 | dominio5.com.ar.log
444.097 MB (465669066 bytes) | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:16:51:20 | dominio6.com.ar.log
Generated in 6 seconds</pre>
<p>Les dejo, de yapa, la opción del script utilizando los logs de bytes. Como dije, esta opción es un poco más sencilla y veloz, pero no nos dice las fechas de los logs.</p>
<div class="dean_ch" style="white-space: wrap;"><span class="re3">#!/bin/bash</span></p>
<p><span class="kw1">if</span> <span class="br0">&#91;</span> -z $<span class="nu0">1</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; <span class="kw3">echo</span> <span class="st0">&quot;Usage: $0 &quot;</span><br />
&nbsp; &nbsp; <span class="kw3">exit</span> <span class="nu0">1</span><br />
<span class="kw1">fi</span></p>
<p><span class="re2">BASE_DIR=</span>$<span class="nu0">1</span><br />
<span class="re2">T1=</span>`<span class="kw2">date</span> +%s`<br />
<span class="kw1">for</span> <span class="kw2">file</span> <span class="kw1">in</span> <span class="re1">$BASE_DIR</span>/*.bytes;<br />
<span class="kw1">do</span><br />
&nbsp; &nbsp; <span class="re2">size=</span>`<span class="kw2">stat</span> -c %s <span class="re1">$file</span>`<br />
&nbsp; &nbsp; <span class="kw1">if</span> <span class="br0">&#91;</span> <span class="re1">$size</span> -gt <span class="nu0">0</span> <span class="br0">&#93;</span>; <span class="kw1">then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">bytes=</span>`<span class="kw2">cat</span> <span class="re1">$file</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{a+=$1}END{print a}&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="re2">mbytes=</span>`<span class="kw3">echo</span> <span class="re1">$bytes</span> | <span class="kw2">awk</span> <span class="st0">&#8216;{print $1 / 1048576}&#8217;</span>`<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw3">echo</span> &nbsp;<span class="st0">&quot;$mbytes MB ($bytes bytes) ${file:${#BASE_DIR}+1}&quot;</span><br />
&nbsp; &nbsp; <span class="kw1">fi</span><br />
<span class="kw1">done</span> \<br />
| <span class="kw2">sort</span> -nb<br />
<span class="re2">T=</span>`<span class="kw2">date</span> +%s`<br />
<span class="kw3">echo</span> <span class="st0">&quot;Generated in $(($T-$T1)) seconds&quot;</span></div>
<p>Y la salida sería así:</p>
<pre># ./parser.sh /var/log/httpd/domains
...
181.716 MB (190543470 bytes) dominio1.com.bytes
187.115 MB (196203973 bytes) dominio2.com.ar.bytes
189.093 MB (198278283 bytes) dominio3.com.ar.bytes
245.692 MB (257626221 bytes) dominio4.com.bytes
273.472 MB (286756234 bytes) dominio5.com.ar.bytes
314.32 MB (329588717 bytes) dominio6.com.ar.bytes
444.516 MB (466108759 bytes) dominio7.com.ar.bytes
Generated in 2 seconds</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/ver-dominios-de-apache-que-mas-transferencia-consumen.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Sacar una foto con la webcam y enviarla por mail desde BASH</title>
		<link>http://www.tail-f.com.ar/programacion/bash/sacar-una-foto-con-la-webcam-y-enviarla-por-mail-desde-bash.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/sacar-una-foto-con-la-webcam-y-enviarla-por-mail-desde-bash.html#comments</comments>
		<pubDate>Tue, 08 Dec 2009 03:21:16 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[Eee PC]]></category>
		<category><![CDATA[ffmpeg]]></category>
		<category><![CDATA[mutt]]></category>
		<category><![CDATA[Webcam]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=290</guid>
		<description><![CDATA[Esta noche tenía un ratito y me puse a jugar con la Eee PC que tanto me costó configurar. Lo que hice fue un simple script en BASH que toma una foto con la webcam, la guarda en un archivo temporal y la envía por mail a un destinatario. El script utiliza ffmpeg y mutt. [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignright size-medium wp-image-291" title="WEBCAM480K_1" src="http://www.tail-f.com.ar/wp-content/uploads/WEBCAM480K_1-300x300.jpg" alt="WEBCAM480K_1" width="180" height="180" />Esta noche tenía un ratito y me puse a jugar con la Eee PC que <a href="http://www.tail-f.com.ar/2009/12/07/sistemas-operativos/gnu-linux/ubuntu-netbook-remix-en-eee-pc-1101ha.html">tanto me costó configurar</a>. Lo que hice fue un simple script en BASH que toma una foto con la webcam, la guarda en un archivo temporal y la envía por mail a un destinatario.</p>
<p>El script utiliza ffmpeg y mutt. Pueden instalarlos poniendo</p>
<pre>sudo apt-get install ffmpeg mutt</pre>
<p>Para que Mutt funcione se tiene que usar con un MTA, por default postfix. Se puede instalar con Exim o con SSMTP. Este último es una especie de wrapper que nos permite configurar un MTA externo para enviar los mails. <a href="https://help.ubuntu.com/community/mutt">Esta guía</a> explica bien cómo hacerlo.</p>
<pre>#!/bin/bash
IMG_SIZE="320x240"
CAM_DEV="/dev/video0"

IMG_NAME="`mktemp -u /tmp/img.XXXXXX`.jpg"

mutt=/usr/bin/mutt
ffmpeg=/usr/bin/ffmpeg
date=`date`
hostname=`hostname`

echo "Taking picture and saving it on $IMG_NAME"

$ffmpeg -f video4linux2 \
	-s $IMG_SIZE \
	-r 5 \
	-vframes 1 \
	-i $CAM_DEV \
	-f mjpeg \
	$IMG_NAME &gt; /dev/null 2&gt;&amp;1

if [ ! -z $1 ]; then
	echo "Attempting to email the picture to $1"
	echo "Picture taken on $hostname at $date" | \
		$mutt -s "A picture was sent to you" -a $IMG_NAME -- $1
fi</pre>
<p>Para ejecutarlo, copiamos el contenido en un archivo, por ejemplo &#8220;cam.sh&#8221;. Luego le damos permisos de escritura y lo ejecutamos.</p>
<pre># chmod +x cam.sh
# ./cam.sh "myaddress@domain.com"</pre>
<p>Entre las utilidades de este script podríamos pensar en sacar una foto de la persona en frente de la máquina cuando se produce un login incorrecto o en algún otro evento que pudiera darnos la pauta de un uso diferente al usual. Ideal para paranóicos y nerds obsesivos.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/sacar-una-foto-con-la-webcam-y-enviarla-por-mail-desde-bash.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Netstat con colorcitos</title>
		<link>http://www.tail-f.com.ar/programacion/bash/netstat-con-colorcitos.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/netstat-con-colorcitos.html#comments</comments>
		<pubDate>Sun, 11 Oct 2009 19:58:08 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[colores]]></category>
		<category><![CDATA[consola]]></category>
		<category><![CDATA[netstat]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=277</guid>
		<description><![CDATA[Luego del post de ayer, pensando en que vengo posteando poco, me puse a buscar a ver si tenía algún otro script útil que pudiera compartir por este medio&#8230; pero no encontré nada. Algo debo tener por ahí, pero nada a mano. Lo que encontré es este script (medio feo) que hice hace bastante tiempo, [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_162" class="wp-caption alignright" style="width: 84px"><img class="size-thumbnail wp-image-162" title="big-tux" src="http://www.tail-f.com.ar/wp-content/uploads/big-tux-150x150.png" alt="GNU/Linux" width="74" height="74" /><p class="wp-caption-text">GNU/Linux</p></div>
<p>Luego del <a href="http://www.tail-f.com.ar/2009/10/10/noticias/backup-de-todas-las-bases-de-datos-mysql-en-archivos-separados.html">post de ayer</a>, pensando en que vengo posteando poco, me puse a buscar a ver si tenía algún otro script útil que pudiera compartir por este medio&#8230; pero no encontré nada. Algo debo tener por ahí, pero nada a mano. Lo que encontré es este script (medio feo) que hice hace bastante tiempo, que muestra un netstat (concretamente netstat -natp) con distintos colores según el estado de cada conexión (established, listen, syn_sent, fyn_wait, etc.). Claramente no es un script muy útil (ni muy bien hecho), pero lo publico. Quizás a alguno le sirve de disparador para hacer algo verdaderamente útil.</p>
<pre>#!/bin/bash
cyan="\E[1;36m\E[1m";
normal="\E[m";
blue="\E[34m\E[1m";
violet="\E[35m\E[1m";
red="\E[31m\E[1m";
yellow="\E[33m\E[1m";
green="\E[37m\E[32m\E[1m";
text="\E[1;37m\E[1m";

if [ "$UID" != "0" ]; then
	echo -e "$red$0: You will get more information if you have root privileges. Try sudo $0$normal"
fi

netstat -natp | \
while read line; do

	if [ `echo $line | awk '{print($1)}'` = "Proto" ]; then
		echo -e "$yellow=====================================================================================================$normal"
		echo -e "$text$line$normal"
		echo -e "$yellow=====================================================================================================$normal"
	else

		state=`echo $line | awk '{print($6)}'`
		color=$normal
		case $state in
			"ESTABLISHED")
				color=$green;;
			"SYN_SENT" | "SYN_RECV")
				color=$yellow;;
			"FIN_WAIT1" | "FIN_WAIT2" |"TIME_WAIT")
				color=$violet;;
			"CLOSE" | "CLOSE_WAIT" | "LAST_ACK" | "CLOSING" )
				color=$blue;;
			"LISTEN")
				color=$cyan;;
			"UNKNOWN")
				color=$red;;
			*)
		esac
		echo -e "$color$line$normal"

	fi
done;</pre>
<p>Nota: el script utiliza el comando netstat con los parámetros &#8220;natp&#8221;. Estos parámetros de esta forma solamente funcionan en GNU/Linux. Para otros sistemas Unix (Solaris, FreeBSD, etc.) será necesario adaptar los parámetros y ver si la salida del netstat es igual.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/netstat-con-colorcitos.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Backup de todas las bases de datos MySQL en archivos separados</title>
		<link>http://www.tail-f.com.ar/servicios/db/mysql-db-servicios/backup-de-todas-las-bases-de-datos-mysql-en-archivos-separados.html</link>
		<comments>http://www.tail-f.com.ar/servicios/db/mysql-db-servicios/backup-de-todas-las-bases-de-datos-mysql-en-archivos-separados.html#comments</comments>
		<pubDate>Sat, 10 Oct 2009 15:30:58 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[BASH]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=271</guid>
		<description><![CDATA[Antes que nada es menester, nobleza bloggera obliga, disculparme porque hace mucho (MUCHO!) que no posteo nada en el blog. Ando con poco tiempo y pocas cosas interesantes para decir. Pero yendo a lo que nos ocupa&#8230; En estos días necesitaba hacer un backup de todas las bases de datos de un servidor MySQL, pero [...]]]></description>
			<content:encoded><![CDATA[<p>Antes que nada es menester, nobleza bloggera obliga, disculparme porque hace mucho (MUCHO!) que no posteo nada en el blog. Ando con poco tiempo y pocas cosas interesantes para decir. Pero yendo a lo que nos ocupa&#8230;<img class="alignright size-medium wp-image-84" title="mysql_logo" src="http://www.tail-f.com.ar/wp-content/uploads/mysql_logo-300x199.gif" alt="mysql_logo" width="210" height="139" /></p>
<p>En estos días necesitaba hacer un backup de todas las bases de datos de un servidor MySQL, pero no me servía hacer mysqldump &#8211;all-databases, porque necesitaba tenerlo en archivos separados (uno para cada DB). Seguramente es algo que ya debe haber sido hecho mil veces, pero no tenía tampoco muchas ganas de perder mucho tiempo en Google (una búsqueda rápida no me dio resultados útiles), así que hice el siguiente script muuy sencillo que obtiene todas las DB de un server y hace un dump de cada una. Se los dejo por si les sirve, capaz los saque de un apuro en alguna ocasión.</p>
<pre>#!/bin/bash
PORT="3306"
USER="root"
PASSWORD="yourpassword"
mysql=/usr/bin/mysql
mysqldump=/usr/bin/mysqldump

TABLES=`$mysql -P $PORT -u $USER --password=$PASSWORD --execute="SHOW DATABASES;" |awk '{print($1)}' |grep -v "Database" |grep -v "information_schema"`
for table in $TABLES; do
	echo "Backing up $table..."
	file=$table.backup_`date +%Y%m%d`.sql
	$mysqldump -P $PORT -u $USER --password=$PASSWORD $table &gt; $file
	echo "Compressing backup..."
	gzip $file
done</pre>
<p>Nota: la definición del puerto es innecesaria, pero yo la tuve que hacer porque en mi caso el servidor no estaba corriendo en el puerto default (3306) sino en otro.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/servicios/db/mysql-db-servicios/backup-de-todas-las-bases-de-datos-mysql-en-archivos-separados.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Daemon en BASH</title>
		<link>http://www.tail-f.com.ar/programacion/bash/daemon-en-bash.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/daemon-en-bash.html#comments</comments>
		<pubDate>Sun, 01 Mar 2009 02:49:11 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[daemon]]></category>
		<category><![CDATA[procesos]]></category>
		<category><![CDATA[servicios]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=255</guid>
		<description><![CDATA[A decir verdad esto es un poco inutil, pero estuve jugando recientemente con BASH (con un script que espero poder terminar en cuanto tenga tiempo, y que si funciona bien publicaré aquí), y en algún momento de delirio se me ocurrió que podría usar una especie de daemon en BASH. La verdad que no es [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_162" class="wp-caption alignright" style="width: 160px"><img class="size-thumbnail wp-image-162" title="big-tux" src="http://www.tail-f.com.ar/wp-content/uploads/big-tux-150x150.png" alt="GNU/Linux" width="150" height="150" /><p class="wp-caption-text">GNU/Linux</p></div>
<p>A decir verdad esto es un poco inutil, pero estuve jugando recientemente con BASH (con un script que espero poder terminar en cuanto tenga tiempo, y que si funciona bien publicaré aquí), y en algún momento de delirio se me ocurrió que podría usar una especie de <a href="http://es.wikipedia.org/wiki/Demonio_(inform%C3%A1tica)">daemon</a> en BASH. La verdad que no es una muy buena alternativa ni un gran uso del lenguaje, seguramente usando otro lenguaje tendría un daemon mejor y para usar BASH probablemente sería más sencillo usar un cron. Pero estaba aburrido y se me ocurrió hacerlo. Por lo tanto, no lo tomen como algo particularmente útil, sino como un ensayo que me sirvió para practicar un poco con este lenguaje que lo tenía medio dejado de lado.</p>
<p>Este daemon no es más que un proceso que corre (&#8220;desatachandose&#8221; de la consola), con un while(1) y va imprimiendo un texto. En un uso normal, adentro del while iría alguna tarea repetitiva definida por la finalidad del demonio. El script recibe un parámetro opcional (que puede ser &#8220;start&#8221; o &#8220;shutdown&#8221;) para iniciar y detener el daemon. Cuando se inicia llama a la función &#8220;start&#8221; que hace un sanity check para ver si hay otra instancia del demonio corriendo, y si no la hay ejecuta la función &#8220;main&#8221; que es donde está el while. Otra cosa importante a notar: para que el script se &#8220;desatachee&#8221; de la consola, el while está dentro de un bloque delimitado por llaves ({}) con un ampersand al final (&amp;). En todo caso también se podría agregar ahí una redirección del output y el error stream.</p>
<p>Sin más introducción, les dejo el script.</p>
<pre>#!/bin/bash

function sanityCheck {
    q=`ps -ef |grep $0 |grep -v "grep"|grep -v $$| wc -l`
    if [ $q != "0" ]; then
        echo "Another instance of $0 running..."
        exit 1
    fi
}

function start {
    sanityCheck
    echo "Starting daemon..."
    main
}

function shutdown {
    echo "Shutting down daemon..."
    kill `ps -ef |grep $0|grep -v $$ |grep -v "grep"|awk '{print($2)}'`
}

function main {
    {
        while [ 1 ]; do
            echo "Test..."
            sleep 2
        done
    } &amp;
}

case $1 in
    "start")
       start
    ;;
    "shutdown")
        shutdown
    ;;
     *)
         start
     ;;
esac</pre>
<p>Uso:</p>
<pre># chmod +x ./daemon.sh
# ./daemon.sh
Starting daemon...
Test...
# Test...Test...Test...
# ./daemon.sh shutdown
Shutting down daemon...
#</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/daemon-en-bash.html/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>BASH 4.0</title>
		<link>http://www.tail-f.com.ar/noticias/bash-40.html</link>
		<comments>http://www.tail-f.com.ar/noticias/bash-40.html#comments</comments>
		<pubDate>Tue, 24 Feb 2009 02:24:17 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[Noticias]]></category>
		<category><![CDATA[BASH 4.0]]></category>
		<category><![CDATA[releases]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=247</guid>
		<description><![CDATA[Leo en Slashdot que ya está disponible la versión 4.0 de BASH (Bourne-Again Shell), el shell Unix desarrollado por GNU. Además de solucionar bugs de releases previos, se introducen nuevas funcionalidades. Entre las más notables se encuentran los arrays asociativos, mejoras en la funcionalidad de completado programable, expresiones regulares para modificar mayúsculas/minúsculas, co-procesos, agregados a [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_162" class="wp-caption alignright" style="width: 160px"><img class="size-thumbnail wp-image-162" title="big-tux" src="http://www.tail-f.com.ar/wp-content/uploads/big-tux-150x150.png" alt="GNU/Linux" width="150" height="150" /><p class="wp-caption-text">GNU/Linux</p></div>
<p>Leo en <a href="http://linux.slashdot.org/article.pl?sid=09/02/23/1957244&amp;from=rss">Slashdot</a> que ya está disponible la <a href="http://tiswww.case.edu/php/chet/bash/bashtop.html#CurrentStatus">versión 4.0 de BASH (Bourne-Again Shell)</a>, el <a href="http://es.wikipedia.org/wiki/Shell_de_Unix">shell Unix </a>desarrollado por <a href="http://www.gnu.org/home.es.html">GNU</a>.</p>
<p>Además de solucionar bugs de releases previos, se introducen nuevas funcionalidades. Entre las más notables se encuentran los arrays asociativos, mejoras en la funcionalidad de completado programable, expresiones regulares para modificar mayúsculas/minúsculas, co-procesos, agregados a la sintaxis, redirecciones, etc.  Según indica el artículo de Slashdot, se modificó el shell para que sea más riguroso al parsear sustituciones de comandos, corrigiendo una parte que no era compatible con POSIX.</p>
<p>Para los que no quieran esperar a que las distros actualicen sus repositorios pueden obtener el <a href="http://ftp.gnu.org/gnu/bash/">código fuente de aquí</a>.</p>
<p>Para más detalles sobre las novedades pueden ver el <a href="http://tiswww.case.edu/php/chet/bash/NEWS">anuncio en inglés</a>.</p>
<p>Editado 24/02/2009: Este <a href="http://www.mail-archive.com/cygwin@cygwin.com/msg94439.html">anuncio</a> es más completo (también en inglés).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/noticias/bash-40.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Contar requests por IP de un access_log de Apache en BASH</title>
		<link>http://www.tail-f.com.ar/programacion/bash/contar-requests-por-ip-de-un-access_log-de-apache-en-bash.html</link>
		<comments>http://www.tail-f.com.ar/programacion/bash/contar-requests-por-ip-de-un-access_log-de-apache-en-bash.html#comments</comments>
		<pubDate>Tue, 10 Feb 2009 23:15:38 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[access_log]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[scripts]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=237</guid>
		<description><![CDATA[Bueno, en mi último post publiqué un script muy sencillo en Python para contar la cantidad de requests por IP en un access_log de Apache. Como sospechaba, había una forma quizás más sencilla y más óptima de hacer lo mismo en BASH, así que hace un rato jugando un poco me salió y se los [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_37" class="wp-caption alignright" style="width: 160px"><a href="http://www.tail-f.com.ar/wp-content/uploads/apache1.gif"><img class="size-thumbnail wp-image-37" title="apache1" src="http://www.tail-f.com.ar/wp-content/uploads/apache1-150x150.gif" alt="Apache Web Server" width="150" height="150" /></a><p class="wp-caption-text">Apache Web Server</p></div>
<p>Bueno, en <a href="http://www.tail-f.com.ar/2009/02/09/programacion/python/script-para-contar-requests-por-ip-de-un-access_log-de-apache.html">mi último post</a> publiqué un script muy sencillo en Python para contar la cantidad de requests por IP en un access_log de Apache. Como sospechaba, había una forma quizás más sencilla y más óptima de hacer lo mismo en BASH, así que hace un rato jugando un poco me salió y se los muestro aquí.</p>
<pre># cat /var/log/httpd/access_log |awk '{print($1)}'|sort |uniq -c |sort</pre>
<p><strong>¿Cómo funciona?</strong></p>
<p>Lo primero que hacemos es abrir el archivo con cat, el contenido se pasa a awk con el cual obtenemos la primera columna (lo mismo lo podríamos hacer con cut, pero estoy más acostumbrado a awk). Eso nos devuelve la lista de las IPs. Eso lo pasamos a sort que ordena las IPs antes de poder pasarselo a uniq. Éste último comando, con el parámetro -c se ocupa de contar las líneas repetidas en un listado ordenado (por eso el sort anterior). Y por último le volvemos a pasar todo a sort para que lo ordene en forma ascendente de acuerdo a la cantidad de ocurrencias de cada IP.</p>
<p>En realidad el script de Python lo ordenaba al revés (en forma descendente), para ello deberíamos pasarle el parámetro &#8220;-r&#8221; al último sort.</p>
<p>Espero que les sea de utilidad. La versión de Python queda sobre todo como base por si es necesario hacer algo un poco más interesante jeje.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/programacion/bash/contar-requests-por-ip-de-un-access_log-de-apache-en-bash.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Script en BASH para comparar tráfico con los access_logs de Apache</title>
		<link>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/script-en-bash-para-comparar-trafico-con-los-access_logs-de-apache.html</link>
		<comments>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/script-en-bash-para-comparar-trafico-con-los-access_logs-de-apache.html#comments</comments>
		<pubDate>Wed, 03 Dec 2008 02:02:45 +0000</pubDate>
		<dc:creator>elbarto</dc:creator>
				<category><![CDATA[Apache]]></category>
		<category><![CDATA[BASH]]></category>
		<category><![CDATA[scripts]]></category>
		<category><![CDATA[tráfico http]]></category>

		<guid isPermaLink="false">http://www.tail-f.com.ar/?p=86</guid>
		<description><![CDATA[Antes que nada debo decir que este script fue algo que hice en un rato tratando de solucionar un problema, y tiene varios errores y cosas mejorables. De más está decir que no cuenta con ninguna garantía, sino que lo comparto porque quizás pueda ayudar a alguien que enfrente un problema similar. Mi problema es [...]]]></description>
			<content:encoded><![CDATA[<p>Antes que nada debo decir que este script fue algo que hice en un rato tratando de solucionar un problema, y tiene varios errores y cosas mejorables. De más está decir que no cuenta con ninguna garantía, sino que lo comparto porque quizás pueda ayudar a alguien que enfrente un problema similar.</p>
<p>Mi problema es que necesitaba saber cuáles eran los dominios de un servidor que alojaba más de 300 que estaban teniendo mayor tráfico. Como no tenía ninguna herramienta en tiempo real que me pudiera reportar esta información para el Apache, lo que hice es un script que revisa los access_log de cada dominio y obtiene la cantidad de bytes traficados por cada uno.</p>
<p>En este caso, cada VirtualHost define un archivo de logs diferente para cada dominio. Los access_log usan el formato combined (que incluye User-Agent, etc.) y no especifican nada más. El script no hace ninguna validación al respecto, así que podría no funcionar con logs en otros formatos.</p>
<p>Debe tenerse en cuenta que si los archivos de logs rotan en base a su tamaño, es probable que se presente el siguiente caso: El dominio web1.com tiene gran cantidad de tráfico y el dominio web2.com todo lo contrario. El access_log correspondiente al primer dominio puede reportar el tráfico de 1 Gb en un día y el access_log del segundo puede reportar tráfico también de 1 Gb pero en 6 meses. Por lo tanto, es importante mantener referencia de las fechas extremas de los logs, para poder dar cuenta de estas circunstancias.</p>
<p>Hechas estas aclaraciones, pasamos a la explicación del script.</p>
<p><strong>¿Cómo funciona?</strong></p>
<p>Muy sencillo. El script recorre todos los archivos de logs (1 por dominio). Por cada uno obtiene la fecha inicial (la del primer request registrado) y la final (la del último). Luego suma la cantidad de bytes de todos los requests registrados, e imprime una fila con esa cifra en megabytes, kilobytes, bytes y el rango de fechas que abarca. El conjunto de todas esas líneas es enviada al sort de GNU para ordenar las líneas de menor a mayor cantidad de tráfico.</p>
<p>Quizás suene medio complejo, pero se entenderá mejor viendo el código.</p>
<p><strong>El Script</strong></p>
<pre>BASE_DIR=/tmp/domain_logs

dateToStamp ()
{
    if [ ${#1} -eq 19 ]; then
        date --utc --date "$1" +%s;
    else
        echo 0
    fi
}

formatDate ()
{
    echo $1 | awk 'BEGIN { \
                    dict["Jan"]=1; \
                    dict["Feb"]=2; \
                    dict["Mar"]=3; \
                    dict["Apr"]=4; \
                    dict["May"]=5; \
                    dict["Jun"]=6; \
                    dict["Jul"]=7; \
                    dict["Aug"]=8; \
                    dict["Sep"]=9; \
                    dict["Oct"]=10; \
                    dict["Nov"]=11; \
                    dict["Dec"]=12 \
                    } { \
                        split(substr($1, 2), a, /[\\\/:]+/); \
                        print a[3] "-" dict[a[2]] "-" a[1] " " a[4] ":" a[5] ":" a[6] \
                    }'
}

for file in `ls $BASE_DIR/*`;
do
    from=$(formatDate `head -1 $file | awk '{print($4)}'`)
    to=$(formatDate `tail -1 $file | awk '{print($4)}'`)
    stamp_from=$(dateToStamp "$from")
    stamp_to=$(dateToStamp "$to")
    total_time=$(($stamp_to-$stamp_from))
    bytes=`cat $file | awk '{a+=$10}END{print a}'`
    kbytes=$((bytes/1024))
    mbytes=$((kbytes/1024))
    echo  "$mbytes MB | \
           $kbytes KB | \
           $bytes Bytes | \
           From: $from ($stamp_from) | \
           To: $to ($stamp_to) | \
           $total_time secs | \
           ${file:${#BASE_DIR}+1}"
done \
| sort -nb</pre>
<p>Como se puede ver, al principio se define la variable BASE_DIR, que es el directorio base donde el script va a buscar los access logs. Cuando ejecuto el script, para evitar problemas de cambios en tiempo real, creo un snapshot con todos los archivos de logs y los guardo en una carpeta (en este caso /tmp/domain_logs).</p>
<pre># cp /var/log/httpd/domains/*.access.log /tmp/domain_logs/</pre>
<p>Luego, trabajo directamente sobre el snapshot. Entonces, como se observa en el script, defino dos funciones que utilizo más abajo, dateToStamp y formatDate. Como sus respectivos nombres indican, la primera convierte una fecha en un unix timestamp y la segunda cambia el formato de la fecha que figura en el archivo de logs por el que a mi me sirve para pasarle a date. Esto seguramente es mucha complicación para algo que podría hacer usando mejor el comando date, pero fue lo que más rápido me resultó hacer.</p>
<p>Y luego está el for, que recorre los archivos y por cada uno obtiene las fechas límite ($from y $to), de allí obtiene los timestamps ($stamp_from, $stamp_to), el tiempo total en segundos y la cantidad de bytes (la cual sumo con awk).</p>
<p>Por último, verán que la salida del for tiene un pipe a sort, a quien le paso los parámetros &#8220;n&#8221; y &#8220;b&#8221;. Para que haga un ordenamiento numérico ignorando los espacios en blanco.</p>
<p><strong>Ejemplo</strong></p>
<pre># ./access_log_parser.sh
97 MB | 99901 KB | 102299281 Bytes | From: 2008-11-28 02:25:54 (1227839154) | To: 2008-11-28 15:34:12 (1227886452) | 47298 secs | domain1.com.ar.log
115 MB | 118260 KB | 121098817 Bytes | From: 2008-11-28 02:27:45 (1227839265) | To: 2008-11-28 15:34:26 (1227886466) | 47201 secs | domain2.com.ar.log
123 MB | 126652 KB | 129691792 Bytes | From: 2008-11-28 02:27:53 (1227839273) | To: 2008-11-28 15:33:01 (1227886381) | 47108 secs | domain3.com.ar.log
145 MB | 149279 KB | 152862681 Bytes | From: 2008-11-28 02:30:29 (1227839429) | To: 2008-11-28 15:34:31 (1227886471) | 47042 secs | domain4.com.ar.log
189 MB | 194517 KB | 199185477 Bytes | From: 2008-11-28 04:35:20 (1227846920) | To: 2008-11-28 15:32:58 (1227886378) | 39458 secs | domain5.com.log
197 MB | 202034 KB | 206883560 Bytes | From: 2008-11-28 02:35:55 (1227839755) | To: 2008-11-28 15:34:29 (1227886469) | 46714 secs | domain6.com.ar.log
499 MB | 511816 KB | 524099937 Bytes | From: 2008-11-28 02:25:44 (1227839144) | To: 2008-11-28 15:34:29 (1227886469) | 47325 secs | domain7.com.ar.log
#</pre>
<p><strong>Comentarios</strong></p>
<p>Por última vez, aclaro que es algo que hice con cierto apuro y que es ampliamente mejorable. Estoy abierto a sus comentarios y sugerencias para mejorar el script. También, si conocen alguna forma de monitorear esto en tiempo real, también me sería de gran utilidad.</p>
<p><strong>Descargar</strong></p>
<p>Pueden descargar el script de <a href="/files/access_log_parser.sh.txt">aquí</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tail-f.com.ar/servicios/httpd/apache-httpd-servicios/script-en-bash-para-comparar-trafico-con-los-access_logs-de-apache.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

