<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>noroute2host.com</title><link href="https://noroute2host.com/" rel="alternate"></link><link href="https://noroute2host.com/feeds/all.atom.xml" rel="self"></link><id>https://noroute2host.com/</id><updated>2025-04-06T19:00:00+02:00</updated><entry><title>Arranque automático de contenedores con docker-compose y systemd</title><link href="https://noroute2host.com/docker-compose-al-arranque-con-systemd.html" rel="alternate"></link><published>2025-04-06T19:00:00+02:00</published><updated>2025-04-06T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2025-04-06:/docker-compose-al-arranque-con-systemd.html</id><summary type="html">&lt;p&gt;Regresamos a vueltas con contenedores y docker. En este nuevo post vamos a ver como generar un servicio de systemd para nuestros ficheros docker compose o docker-compose.yml. Así que sí, después de la larga espera para hablar un poco de contenedores, vamos a encadenar un par de articulos consecutivos este&amp;nbsp;tema.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Regresamos a vueltas con contenedores y docker. En este nuevo post vamos a ver como generar un servicio de systemd para nuestros ficheros docker compose o docker-compose.yml. Así que sí, después de la larga espera para hablar un poco de contenedores, vamos a encadenar un par de artículos consecutivos sobre este&amp;nbsp;tema.&lt;/p&gt;
&lt;h2 id="iniciando-tus-contenedores-automaticamente-al-inicio-con-docker-compose-y-systemd"&gt;Iniciando tus contenedores automáticamente al inicio con docker compose y&amp;nbsp;systemd&lt;/h2&gt;
&lt;p&gt;Nuestro objetivo es hacer que nuestros servicios y contenedores definidos en un fichero docker-compose.yml se inicien automáticamente con el arranque del sistema. Para ello&amp;nbsp;necesitaremos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Un fichero docker-compose.yml con la definición de los servicios y&amp;nbsp;contenedores.&lt;/li&gt;
&lt;li&gt;Un fichero de unidad o &lt;em&gt;unit file&lt;/em&gt; de systemd donde se define el servicio asociado a nuestro fichero&amp;nbsp;compose.&lt;/li&gt;
&lt;li&gt;Probar el inicio y parada de nuestros servicios contenerizados con&amp;nbsp;systemd.&lt;/li&gt;
&lt;li&gt;Habilitar nuestro servicio para que se inicie al arranque del&amp;nbsp;sistema.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="fichero-docker-composeyml-base-para-integrar-con-systemd"&gt;Fichero docker-compose.yml base para integrar con&amp;nbsp;systemd&lt;/h3&gt;
&lt;p&gt;Como parece lógico, lo primero que necesitamos para poder gestionar nuestro fichero docker compose mediante systemd es un fichero&amp;nbsp;docker-compose.yml.&lt;/p&gt;
&lt;p&gt;Como ejemplo para este artículo vamos a usar un fichero docker compose bastante simple. Solo contendrá un servicio &lt;em&gt;web&lt;/em&gt; que será un servidor web nginx. Si sientes curiosidad por manejar ficheros docker-compose.yml más complejos puedes echarle un ojo a &lt;a href="https://noroute2host.com/gluetun-contenedor-vpn-proxy.html"&gt;nuestro artículo sobre Gluetun&lt;/a&gt; y los ejemplos &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0031_gluetun-vpn-proxy?ref_type=heads"&gt;en nuestro repositorio&lt;/a&gt; que en él se muestran, o visitar la &lt;a href="https://docs.docker.com/compose/"&gt;documentación oficial de Docker Compose&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nuestro fichero docker-compose.yml será&amp;nbsp;este:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx_noroute2host&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;8080:80&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y lo vamos a colocar, por ejemplo, en el siguiente directorio: &lt;em&gt;/store/docker-compose-nginx&lt;/em&gt;. Pero tú puedes colocarlo donde prefieras. Eso sí, recuerda donde lo haces porque lo vamos a necesitar más&amp;nbsp;adelante.&lt;/p&gt;
&lt;h3 id="unit-file-o-fichero-de-unidad-de-systemd"&gt;Unit File o fichero de unidad de&amp;nbsp;systemd&lt;/h3&gt;
&lt;p&gt;Una vez que tenemos el fichero docker compose, el siguiente paso es generar un servicio de systemd asociado a nuestro fichero&amp;nbsp;docker-compose.yml.&lt;/p&gt;
&lt;p&gt;Llegados a este punto habrá alguno que se pregunte ¿qué es systemd? Aunque muchos de los que estéis leyendo esto ya lo sabréis, systemd es un gestor de sistemas y de servicios para Linux. Y esto se traduce en que es el sistema de inicialización por defecto en muchas distribuciones Linux, incluidas Debian, Redhat y sus derivadas. Así que se puede decir que hoy en día es el sistema de arranque de muchas distribuciones&amp;nbsp;Linux.&lt;/p&gt;
&lt;p&gt;Systemd funciona en base a unidades, y estas unidades se definen en ficheros de unidad. En concreto, en la ruta &lt;em&gt;/etc/systemd/system/&lt;/em&gt; se incluyen los ficheros de unidad personalizados del usuario entre&amp;nbsp;otros.&lt;/p&gt;
&lt;p&gt;Además, estas unidades pueden ser de diferente tipo, pero la unidad que nos interesa para llevar a cabo nuestra tareas es la de &lt;em&gt;servicio&lt;/em&gt; o &lt;em&gt;service&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;Así que vamos a crear un &lt;em&gt;fichero de unidad&lt;/em&gt; o &lt;em&gt;unit file&lt;/em&gt; en la siguiente ruta y, por ejemplo, con el siguiente nombre: &lt;strong&gt;/etc/systemd/system/docker-compose-nginx.service&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Docker compose de NGINX como servicio&lt;/span&gt;
&lt;span class="na"&gt;Requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.service&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.service&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="na"&gt;RemainAfterExit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;adrimcgrady&lt;/span&gt;
&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;[TU/DIRECTORIO/QUE/CONTIENE/EL/FICHERO/DOCKER/COMPOSE]&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;[/RUTA/ABSOLUTA/A/TU/COMANDO]/docker-compose up -d --remove-orphans&lt;/span&gt;
&lt;span class="na"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;[/RUTA/ABSOLUTA/A/TU/COMANDO]/docker-compose down&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En el fichero de unidad de arriba hay varios parámetros que aparecen parametrizados. Para mayor claridad te dejo aquí abajo el fichero de unidad completo para nuestro&amp;nbsp;ejemplo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Docker compose de NGINX como servicio&lt;/span&gt;
&lt;span class="na"&gt;Requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.service&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.service&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="na"&gt;RemainAfterExit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;adrimcgrady&lt;/span&gt;
&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/store/docker-compose-nginx&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/docker-compose up -d --remove-orphans&lt;/span&gt;
&lt;span class="na"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/docker-compose down&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="usando-systemd-para-iniciar-los-contenedores-definidos-en-el-fichero-docker-composeyml"&gt;Usando systemd para iniciar los contenedores definidos en el fichero&amp;nbsp;docker-compose.yml&lt;/h3&gt;
&lt;p&gt;Antes de correr hay que aprender a andar. Pues siguiendo el símil, antes de arrancar los contenedores con el inicio del sistema hay que probar que se pueden arrancar y parar sin problemas con systemd, o más concretamente con &lt;code&gt;systemctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Como ya tenemos definido tanto nuestro fichero docker-compose.yml como nuestro &lt;em&gt;unit file&lt;/em&gt; de systemd, lo único que queda es&amp;nbsp;probarlo.&lt;/p&gt;
&lt;p&gt;El inicio de un servicio con &lt;code&gt;systemctl&lt;/code&gt; es tan sencillo&amp;nbsp;como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sytemctl&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;NOMBRE_DE_SEVICIO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por lo que si queremos arrancar el servicio que hemos definido como ejemplo, habrá que ejecutar&amp;nbsp;simplemente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@raspibox:~#&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;docker-compose-nginx.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para comprobar el estado del servicio lo podemos hacer vía &lt;code&gt;systemctl status&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@raspibox:~#&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;docker-compose-nginx.service
●&lt;span class="w"&gt; &lt;/span&gt;docker-compose-nginx.service&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;NGINX&lt;span class="w"&gt; &lt;/span&gt;como&lt;span class="w"&gt; &lt;/span&gt;servicio
&lt;span class="w"&gt;     &lt;/span&gt;Loaded:&lt;span class="w"&gt; &lt;/span&gt;loaded&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;/etc/systemd/system/docker-compose-nginx.service&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disabled&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vendor&lt;span class="w"&gt; &lt;/span&gt;preset:&lt;span class="w"&gt; &lt;/span&gt;enabled&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;Active:&lt;span class="w"&gt; &lt;/span&gt;active&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;exited&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;since&lt;span class="w"&gt; &lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;-04-06&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:37&lt;span class="w"&gt; &lt;/span&gt;CEST&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1min&lt;span class="w"&gt; &lt;/span&gt;21s&lt;span class="w"&gt; &lt;/span&gt;ago
&lt;span class="w"&gt;    &lt;/span&gt;Process:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25345&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;--remove-orphans&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/SUCCES&amp;gt;
&lt;span class="w"&gt;   &lt;/span&gt;Main&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25345&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/SUCCESS&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;CPU:&lt;span class="w"&gt; &lt;/span&gt;578ms

Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:24&lt;span class="w"&gt; &lt;/span&gt;raspibox&lt;span class="w"&gt; &lt;/span&gt;systemd&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Starting&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;NGINX&lt;span class="w"&gt; &lt;/span&gt;como&lt;span class="w"&gt; &lt;/span&gt;servicio...
Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:27&lt;span class="w"&gt; &lt;/span&gt;raspibox&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;25345&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt;  &lt;/span&gt;Container&lt;span class="w"&gt; &lt;/span&gt;nginx_noroute2host&lt;span class="w"&gt;  &lt;/span&gt;Created
Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:27&lt;span class="w"&gt; &lt;/span&gt;raspibox&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;25345&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt;  &lt;/span&gt;Container&lt;span class="w"&gt; &lt;/span&gt;nginx_noroute2host&lt;span class="w"&gt;  &lt;/span&gt;Starting
Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:37&lt;span class="w"&gt; &lt;/span&gt;raspibox&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;25345&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt;  &lt;/span&gt;Container&lt;span class="w"&gt; &lt;/span&gt;nginx_noroute2host&lt;span class="w"&gt;  &lt;/span&gt;Started
Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:25:37&lt;span class="w"&gt; &lt;/span&gt;raspibox&lt;span class="w"&gt; &lt;/span&gt;systemd&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Finished&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;NGINX&lt;span class="w"&gt; &lt;/span&gt;como&lt;span class="w"&gt; &lt;/span&gt;servicio
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También podemos ver el si el contenedor está funcionando vía &lt;code&gt;docker ps&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@raspibox:~#&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;--last&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
CONTAINER&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt;   &lt;/span&gt;IMAGE&lt;span class="w"&gt;     &lt;/span&gt;COMMAND&lt;span class="w"&gt;                  &lt;/span&gt;CREATED&lt;span class="w"&gt;       &lt;/span&gt;STATUS&lt;span class="w"&gt;         &lt;/span&gt;PORTS&lt;span class="w"&gt;                                   &lt;/span&gt;NAMES
c6d145c6e64a&lt;span class="w"&gt;   &lt;/span&gt;nginx&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/docker-entrypoint.…&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;weeks&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;   &lt;/span&gt;Up&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minutes&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0:8080-&amp;gt;80/tcp,&lt;span class="w"&gt; &lt;/span&gt;:::8080-&amp;gt;80/tcp&lt;span class="w"&gt;   &lt;/span&gt;nginx_noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O lo que es mejor, simplemente se puede hacer la prueba de conectar a nuestro servidor web nginx&amp;nbsp;contenerizado:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Contenedor Nginx funcionando" src="https://noroute2host.com/images/0032_contenedor_nginx_funcionando.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Llegados a este punto, toca probar la parada del servicio. De manera genérica esto puede hacerse de la siguiente&amp;nbsp;manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sytemctl&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;NOMBRE_DE_SEVICIO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por tanto, para parar el servicio de&amp;nbsp;ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@raspibox:~#&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;docker-compose-nginx.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y se puede comprobar que nuestro contenedor &lt;em&gt;nginx_noroute2host&lt;/em&gt; ya no está&amp;nbsp;corriendo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@raspibox:~#&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;--filter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx_noroute2host
CONTAINER&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt;   &lt;/span&gt;IMAGE&lt;span class="w"&gt;     &lt;/span&gt;COMMAND&lt;span class="w"&gt;   &lt;/span&gt;CREATED&lt;span class="w"&gt;   &lt;/span&gt;STATUS&lt;span class="w"&gt;    &lt;/span&gt;PORTS&lt;span class="w"&gt;     &lt;/span&gt;NAMES
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Así que si volvemos a repetir la prueba de conexión al servidor Nginx, ahora obtendremos un&amp;nbsp;error:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Contenedor Nginx parado" src="https://noroute2host.com/images/0032_contenedor_nginx_parado.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="ultimo-paso-habilitar-el-servicio-al-arranque"&gt;Último paso: Habilitar el servicio al&amp;nbsp;arranque&lt;/h3&gt;
&lt;p&gt;Ya está todo probado y definido así que nos queda simplemente el último paso, habilitar nuestro servicio de systemd. Esto es algo bastante sencillo y parecido a los comandos anteriores con &lt;code&gt;systemctl&lt;/code&gt;. La sintaxis es&amp;nbsp;esta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOMBRE_DE_SEVICIO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que aplicado al servicio que estamos usando de ejemplo es&amp;nbsp;simplemente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose-nginx.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y ya está, ya sabes todo lo necesario&amp;nbsp;para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Definir tus contenedores y servicios en un fichero&amp;nbsp;docker-compose.yml&lt;/li&gt;
&lt;li&gt;Integrar el fichero docker-compose.yml con systemd mediante un fichero de unidad o &lt;em&gt;unit file&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Iniciar tus contenedores del fichero compose mediante el servicio de systemd&amp;nbsp;definido.&lt;/li&gt;
&lt;li&gt;Habilitar este servicio al arranque del&amp;nbsp;sistema.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="antes-de-acabar-ficheros-de-ejemplo"&gt;Antes de acabar: Ficheros de&amp;nbsp;ejemplo&lt;/h3&gt;
&lt;p&gt;Además de la explicación disponible en este post, se han dejado los ficheros compose y de unidad en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0032_docker-compose-systemd?ref_type=heads"&gt;repositorio del blog&lt;/a&gt; a modo de referencia. Este es el acceso directo a cada&amp;nbsp;fichero:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0032_docker-compose-systemd/docker-compose.yml?ref_type=heads"&gt;Fichero docker-compose básico para servidor web&amp;nbsp;nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0032_docker-compose-systemd/docker-compose-nginx.service?ref_type=heads"&gt;Fichero de unidad o unit file de systemd para nuestro docker compose de&amp;nbsp;nginx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/"&gt;Documentación oficial de&amp;nbsp;docker-compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://systemd.io/"&gt;Documentación oficial de&amp;nbsp;systemd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/nginx"&gt;Imágenes oficiales de Nginx en&amp;nbsp;DockerHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0032_docker-compose-systemd?ref_type=heads"&gt;Ficheros docker-compose y service en el Repositorio&amp;nbsp;noroute2host-files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="contenedores"></category><category term="docker"></category><category term="compose"></category><category term="linux"></category><category term="systemd"></category></entry><entry><title>Guía de Gluetun: VPN y Proxy para tus contenedores o para cualquier dispositivo o aplicación en tu red</title><link href="https://noroute2host.com/gluetun-contenedor-vpn-proxy.html" rel="alternate"></link><published>2025-02-07T21:00:00+01:00</published><updated>2025-02-07T21:00:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2025-02-07:/gluetun-contenedor-vpn-proxy.html</id><summary type="html">&lt;p&gt;Ha sido necesario que llegue 2025 para que hablemos de algo relacionado con contenedores en este blog. En concreto, vamos a hablar de Gluetun. Tal y como sus mantendores lo definen es un &lt;em&gt;&amp;#8220;Cliente &lt;span class="caps"&gt;VPN&lt;/span&gt; en un contenedor Docker ligero para múltiples proveedores de &lt;span class="caps"&gt;VPN&lt;/span&gt;, escrito en Go y que utiliza OpenVPN o Wireguard, &lt;span class="caps"&gt;DNS&lt;/span&gt; sobre &lt;span class="caps"&gt;TLS&lt;/span&gt;, con algunos servidores proxy&amp;nbsp;integrados&amp;#8221;&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ha sido necesario que llegue 2025 para que hablemos de algo relacionado con contenedores en este blog. En concreto, vamos a hablar de Gluetun. Tal y como sus mantendores lo definen es un &lt;em&gt;&amp;#8220;Cliente &lt;span class="caps"&gt;VPN&lt;/span&gt; en un contenedor Docker ligero para múltiples proveedores de &lt;span class="caps"&gt;VPN&lt;/span&gt;, escrito en Go y que utiliza OpenVPN o Wireguard, &lt;span class="caps"&gt;DNS&lt;/span&gt; sobre &lt;span class="caps"&gt;TLS&lt;/span&gt;, con algunos servidores proxy&amp;nbsp;integrados&amp;#8221;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="gluetun-una-navaja-suiza-de-clientes-vpn-que-incluye-servidores-de-proxy"&gt;Gluetun: Una navaja suiza de clientes &lt;span class="caps"&gt;VPN&lt;/span&gt; que incluye servidores de&amp;nbsp;proxy&lt;/h2&gt;
&lt;p&gt;En realidad, con la pequeña introducción y el título de esta sección se puede entender que es Gluetun. Aún así, vamos a intentar explicarlo un poco mejor. Gluetun es una imagen de contenedor que consiste básicamente en una ingente cantidad de clientes &lt;span class="caps"&gt;VPN&lt;/span&gt; para que utilices el que más te convenga. A lo anterior hay que sumarle que en la imagen tienes a tu disposición servidores de&amp;nbsp;proxy.&lt;/p&gt;
&lt;p&gt;Y al juntar estas dos características tenemos un contenedor que puede hacer de proxy con &lt;span class="caps"&gt;VPN&lt;/span&gt; integrada para tus contenedores o para tus dispositivos dentro de la red &lt;span class="caps"&gt;LAN&lt;/span&gt;. Incluso, en el caso de los contenedores, sin ni siquiera hacer uso del servicio de proxy puedes hacer que el acceso a la red de tus contenedores sea a través de Gluetun. Así que tendrás tus servicios en contenedores a través de una &lt;span class="caps"&gt;VPN&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Por último, como tiene soporte tanto para &lt;span class="caps"&gt;ARM&lt;/span&gt; como para x86 en sus versiones de 32 y 64 bits, te permite montar este juguetito en cualquier Raspberry u otra &lt;span class="caps"&gt;SBC&lt;/span&gt;, así como cualquier  MiniPC a tu disposición de una manera rápida y&amp;nbsp;simple.&lt;/p&gt;
&lt;p&gt;Las características más importantes de Gluetun&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Imagen muy ligera basada en Alpine&amp;nbsp;Linux.&lt;/li&gt;
&lt;li&gt;Soporte para múltiples proveedores de &lt;span class="caps"&gt;VPN&lt;/span&gt;: AirVPN, Cyberghost, ExpressVPN, FastestVPN, Giganews, HideMyAss, IPVanish, &lt;span class="caps"&gt;IVPN&lt;/span&gt;, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN, ProtonVPN, PureVPN, SlickVPN, Surfshark, TorGuard, VPNSecure.me, VPNUnlimited, Vyprvpn, WeVPN,&amp;nbsp;Windscribe.&lt;/li&gt;
&lt;li&gt;Soporte para OpenVPN para todos los&amp;nbsp;servidores.&lt;/li&gt;
&lt;li&gt;Soporte para Wireguard. En función del proveedor &lt;span class="caps"&gt;VPN&lt;/span&gt; hay soporte para Kernel y espacio de usuario, soporte usando &lt;em&gt;custom provider&lt;/em&gt; o puede que el soporte en este caso no esté disponible. Este soporte esta en progreso y su avance puede consultarse &lt;a href="https://github.com/qdm12/gluetun/issues/134"&gt;aquí&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;DNS&lt;/span&gt; over &lt;span class="caps"&gt;TLS&lt;/span&gt;. Además permite elegir el proveedor de &lt;span class="caps"&gt;DOT&lt;/span&gt; e incluso indicar múltiples&amp;nbsp;proveedores.&lt;/li&gt;
&lt;li&gt;Bloqueo &lt;span class="caps"&gt;DNS&lt;/span&gt; de hostnames e IPs&amp;nbsp;maliciosas.&lt;/li&gt;
&lt;li&gt;Firewall&amp;nbsp;integrado.&lt;/li&gt;
&lt;li&gt;Servidores proxy integrados. Shadowsocks y &lt;span class="caps"&gt;HTTP&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Conexión de otros contenedores y otros dispositivos &lt;span class="caps"&gt;LAN&lt;/span&gt; a&amp;nbsp;Gluetun.&lt;/li&gt;
&lt;li&gt;Posibilidad de uso como contenedor sidecar en&amp;nbsp;Kubernetes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gluetun-como-vpn-para-tus-contenedores"&gt;Gluetun como &lt;span class="caps"&gt;VPN&lt;/span&gt; para tus&amp;nbsp;contenedores&lt;/h2&gt;
&lt;p&gt;La primera manera de usar Gluetun que vamos a ver es como &lt;span class="caps"&gt;VPN&lt;/span&gt; para tus contenedores. De este modo se puede hacer que tus contenedores usen el stack de red de tu contenedor Gluetun. Esto se traduce que los contenedores que quieras estarán usando una &lt;span class="caps"&gt;VPN&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="arrancando-tu-contenedor-gluetun"&gt;Arrancando tu contenedor&amp;nbsp;Gluetun&lt;/h3&gt;
&lt;p&gt;Todas las características definidas anteriormente se materializan en una &lt;a href="https://hub.docker.com/r/qmcgaw/gluetun"&gt;imagen de contenedor disponible en DockerHub&lt;/a&gt;. Por lo que en este apartado vamos a ver como arrancar nuestro contenedor de&amp;nbsp;Gluetun.&lt;/p&gt;
&lt;p&gt;Lo primero a tener en cuenta es que Gluetun soporta tanto OpenVPN como WireGuard. En este artículo nos centraremos en la opción de OpenVPN, pero puedes encontrar más información sobre como trabajar con WireGuard en &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/wireguard.md"&gt;esta página del Wiki de Gluetun&lt;/a&gt;. Así que, continuando con OpenVPN, puedes encontrar todas las opciones disponibles &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/openvpn.md"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Una vez elegido el protocolo OpenVPN como nuestra opción, el requisito fundamental es tener una cuenta en un servicio de &lt;span class="caps"&gt;VPN&lt;/span&gt; de los compatibles con Gluetun. En mi caso he estado probado las cuentas gratuitas de &lt;a href="https://protonvpn.com"&gt;ProtonVPN&lt;/a&gt; y de &lt;a href="https://privadovpn.com"&gt;PrivadoVPN&lt;/a&gt; y he detectado un requisito bastante importante en el caso de las cuentas gratuitas. No todos los proveedores permiten conexiones &lt;span class="caps"&gt;VPN&lt;/span&gt; con clientes &lt;em&gt;no oficiales&lt;/em&gt; en la versión gratuita. En mi caso, perdí bastante tiempo con PrivadoVPN porque no conseguí hacer funcionar la cuenta gratuita con Gluetun hasta que descubrí que solo puedes usar la cuenta gratuita desde su cliente &lt;em&gt;oficial&lt;/em&gt;. Pero el error que mostraba mi contenedor en el arranque es que las credenciales no eran correctas. Por lo que ante esta situación estaba claro que la configuración la iba a realizar con&amp;nbsp;ProtonVPN.&lt;/p&gt;
&lt;p&gt;Así que ya están decididos los dos requisitos fundamentales: El protocolo (OpenVPN) y el proveedor de &lt;span class="caps"&gt;VPN&lt;/span&gt; (ProtonVPN). Por lo que estamos listos para iniciar nuestro contenedor. Pero antes de continuar, y solo si el sistema donde vas a iniciar tu contenedor es de 32 bits, como en el caso de las primeras Raspberry Pi, debes tener en cuenta &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/prerequisites/32bit.md"&gt;los requisitos que indican en el wiki de Gluetun&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por tanto para arrancar nuestro contenedor Gluetun, este sería un buen&amp;nbsp;ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;gluetun_noroute2host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--cap-add&lt;span class="o"&gt;=&lt;/span&gt;NET_ADMIN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--device&lt;span class="w"&gt; &lt;/span&gt;/dev/net/tun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/store/gluetun:/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_SERVICE_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;protonvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_USER&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_USUARIO_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_PASSWORD_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FREE_ONLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Europe/Madrid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UPDATER_PERIOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;qmcgaw/gluetun
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En este comando hay dos tipos de opciones que paso a&amp;nbsp;explicar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Opciones propias de Docker:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt;: Opcional. Es la forma corta de las opciones &lt;code&gt;--interative&lt;/code&gt; y &lt;code&gt;--tty&lt;/code&gt;. Inicia el contenedor en modo interactivo y con un&amp;nbsp;pseudo-terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt;: Opcional. Para el ejemplo queremos que cuando se detenga el contenedor se elimine&amp;nbsp;automáticamente.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--name=gluetun_noroute2host&lt;/code&gt;: Simplemente sirve para indicar un nombre para nuestro contenedor. Lo establecemos porque después será necesario referenciarlo en los contenedores que hagan uso de&amp;nbsp;este.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cap-add=NET_ADMIN&lt;/code&gt;: Requerido por Gluetun. Por defecto un contenedor corre en un modo &amp;#8220;sin privilegios&amp;#8221;. Con esta opción se dan privilegios adicionales sobre el stack de&amp;nbsp;red.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--device /dev/net/tun&lt;/code&gt;: Requerido por Gluetun. Añade el dispositivo /dev/net/tun del sistema al contenedor. Este dispositivo es la interfaz virtual de &lt;em&gt;network TUNnel&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v /store/gluetun:/gluetun&lt;/code&gt;: Requerido por Gluetun. Mapea una ubicación del host a la ruta /gluetun del&amp;nbsp;contenedor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qmcgaw/gluetun&lt;/code&gt;: Requerido por Gluetun. Es el nombre de la imagen de contenedor oficial de&amp;nbsp;Gluetun.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Opciones de Gluetun. Estas opciones funcionan a través de variables de entorno con la opción de docker &lt;code&gt;-e&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;VPN_TYPE: En nuestro ejemplo la establecemos &lt;code&gt;openvpn&lt;/code&gt;. Pero también es la opción por defecto si no se indica este&amp;nbsp;parámetro.&lt;/li&gt;
&lt;li&gt;VPN_SERVICE_PROVIDER: Indica un proveedor de servicio válido. En este caso &lt;code&gt;protonvpn&lt;/code&gt;. Pero puede ser cualquier otro del que se disponga una cuenta. &lt;a href="https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers"&gt;Aquí&lt;/a&gt; está la lista de proveedores&amp;nbsp;válidos.&lt;/li&gt;
&lt;li&gt;OPENVPN_USER: Aquí se debe indicar el nombre de tu usuario en el proveedor&amp;nbsp;elegido.&lt;/li&gt;
&lt;li&gt;OPENVPN_PASSWORD: Igualmente que el parámetro anterior, pero en este caso para el&amp;nbsp;password.&lt;/li&gt;
&lt;li&gt;FREE_ONLY: Parámetro específico de protonvpn para indicar que solo use los servidores disponibles en el plan gratuito. &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/protonvpn.md"&gt;Aquí&lt;/a&gt; puedes ver todas las opciones específicas de&amp;nbsp;protonvpn.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;DOT&lt;/span&gt;: Habilita &lt;em&gt;&lt;span class="caps"&gt;DNS&lt;/span&gt; over &lt;span class="caps"&gt;TLS&lt;/span&gt;&lt;/em&gt;. Por defecto ya está&amp;nbsp;habilitado.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;TZ&lt;/span&gt;: Especifica un Timezone para ver correctamente los tiempos en los&amp;nbsp;logs.&lt;/li&gt;
&lt;li&gt;UPDATER_PERIOD: Opcional. Actualiza los servidores disponibles alojados en memoria en el intervalo especificado. En este caso 24&amp;nbsp;horas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La lista anterior es una propuesta para este artículo pero puedes comprobar por ti mismo &lt;a href="https://github.com/qdm12/gluetun-wiki/tree/main/setup/options"&gt;todas las opciones disponibles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Una vez ejecutado el comando, ya tendrás tu contenedor de Gluetun funcionando con las opciones indicadas. Como hemos arrancado el contenedor en modo interactivo puedes ver en tu terminal si todo hay ido bien en el&amp;nbsp;arranque.&lt;/p&gt;
&lt;h3 id="hacer-que-un-contenedor-este-en-el-mismo-namespace-de-red-que-otro"&gt;Hacer que un contenedor esté en el mismo namespace de red que&amp;nbsp;otro&lt;/h3&gt;
&lt;p&gt;Ahora que ya tenemos nuestro conenedor de Gluetun funcionando la pregunta es: ¿Y como hago para que otro de mis contenedores lo aproveche? Y la respuesta es: haciendo que tus otros contenedores usen el mismo namespace de red que ya tiene el contenedor de Gluetun. De esta manera tus contenedores asociados al mismo namespace de red estarán en el mismo stack de red que el de gluetun. Tanto es así, que podrás acceder a los servicios de cualquiera de los contenedores en el mismo espacio de red a través de &lt;em&gt;localhost&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pero ¡atención! Que como los mapeos de puertos son únicos en el namespace de red, no puedes usar los mismos puertos para diferentes contenedores dentro del mismo namespace de red. Esto último es algo con lo que hay que tener mucho&amp;nbsp;cuidado.&lt;/p&gt;
&lt;p&gt;La implementación de esta estrategia se hace con la opción &lt;code&gt;--network&lt;/code&gt; de docker. Esta opción indica a que red se conecta un contenedor. Por defecto, si no se indica esta opción, se conecta al bridge por defecto de docker. Pero la opción te permite cambiar este comportamiento. En concreto la vamos a especificar así: &lt;code&gt;--network:container:NOMBRE_DE_TU_CONTENEDOR&lt;/code&gt;. Por&amp;nbsp;ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--network:container:gluetun_noroute2host&lt;span class="w"&gt; &lt;/span&gt;hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La orden anterior ejecuta el contenedor hello-world de docker en la misma pila de red que nuestro contenedor&amp;nbsp;gluetun.&lt;/p&gt;
&lt;p&gt;Esta misma opción se puede configurar dentro de un docker-compose donde estén los dos contenedores, aunque siendo así la sintaxis es algo diferente. Además también es algo distinto según el&amp;nbsp;caso:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Si el contenedor que vaya a usar gluetun esta en el mismo fichero compose, la configuración análoga sería incluir en el servicio que vaya a usar gluetun la siguiente linea en el&amp;nbsp;docker-compose.yml:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;network_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;service:NOMBRE_DEL_SERVICIO_GLUETUN_EN_FICHERO_COMPOSE&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Si por el contrario, el contenedor que va a usar gluetun está en otro fichero compose la configuración incluir sería&amp;nbsp;distinta:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;network_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;container:gluetun&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Tienes más información sobre estas configuraciones en &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md"&gt;la siguiente página del wiki de&amp;nbsp;Gluetun&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="probando-el-acceso-vpn-para-otro-contenedor"&gt;Probando el acceso &lt;span class="caps"&gt;VPN&lt;/span&gt; para otro&amp;nbsp;contenedor&lt;/h3&gt;
&lt;p&gt;Con la explicación anterior, ya solo nos queda demostrar que podemos hacer que otro de nuestros contenedores o servicios contenerizados pueden usar este contenedor Gluetun para acceder a Internet a través del servicio &lt;span class="caps"&gt;VPN&lt;/span&gt; configurado en Gluetun. Para esta prueba vamos a usar una imagen básica de Alpine Linux y a seguir &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/test-your-setup.md"&gt;estas instrucciones&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En primer lugar, vamos a probar a arrancar este contenedor básico de Alpine sin pasar por nuestro contenedor de Gluetun. Además le pasaremos un comando con la opción &lt;code&gt;-c&lt;/code&gt; para que sea ejecutado en el arranque del contenedor. Este comando es &lt;code&gt;wget -qO- https://ipinfo.io&lt;/code&gt; y básicamente lo que hace es mostrar con que ip está saliendo el contenedor a&amp;nbsp;Internet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;alpine:latest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apk add wget &amp;amp;&amp;amp; wget -qO- https://ipinfo.io&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y el resultado es que actualmente el contenedor de manera pública sale a Internet con una ip de España. He ofuscado el resto de&amp;nbsp;datos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X.X.X.X&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;city&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Xxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;region&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Xxxxxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ES&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;loc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X,Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;org&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Xxxxx Xxxxxx XX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;postal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NNNNN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;timezone&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Europe/Madrid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;readme&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://ipinfo.io/missingauth&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y justo a continuación, vamos a ejecutar la misma orden, pero configurando nuestro contenedor para que use la red del contenedor de&amp;nbsp;gluetun.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--network&lt;span class="o"&gt;=&lt;/span&gt;container:gluetun_noroute2host&lt;span class="w"&gt; &lt;/span&gt;alpine:latest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;apk add wget &amp;amp;&amp;amp; wget -qO- https://ipinfo.io&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Es por ello que ahora el resultado es una dirección ip de Japón. Vuelvo a ofuscar algunas partes de la&amp;nbsp;salida.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X.X.X.X&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;hostname&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Xxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;city&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Osaka&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;region&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Osaka&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;JP&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;loc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X,Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;org&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Xxxxx Xxxxxx XX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;postal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NNNNN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;timezone&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Asia/Tokyo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;readme&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://ipinfo.io/missingauth&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="como-publicar-puertos-de-tus-contenedores-mientras-hacen-uso-de-gluetun"&gt;Como publicar puertos de tus contenedores mientras hacen uso de&amp;nbsp;Gluetun&lt;/h3&gt;
&lt;p&gt;Personalmente esta es una de las cosas que más me ha costado entender al principio. Ya se ha comentado anteriormente en este artículo, pero hay que tener en cuenta que nuestros contenedores están conectados a la red o al namespace de red del contenedor de gluetun. ¿Y que implica esto? Pues algunos detalles como los que enumero a&amp;nbsp;continuación:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dentro del mismo namespace de red, no puedes usar un mismo puerto para publicar servicios de varios contenedores. Esta publicación es única dentro del namespace y no se puede&amp;nbsp;repetir.&lt;/li&gt;
&lt;li&gt;Además, con respecto a esto, la publicación de puertos hay que hacerla en el contenedor base de la pila de red que estamos usando, es decir, nuestro contenedor de gluetun. Por tanto, si tienes un servidor web usando la red del contenedor de gluetun, la publicación de este servicio web hay que hacerla al iniciar el contenedor de&amp;nbsp;gluetun.&lt;/li&gt;
&lt;li&gt;Todos los contenedores haciendo uso del mismo stack de red pueden comunicarse entre ellos usando &lt;em&gt;localhost&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voy a intentar explicar esto con un ejemplo. En concreto voy a usar la imagen de contenedor &lt;a href="https://hub.docker.com/r/outlyernet/public-ip-monitor"&gt;public-ip-monitor&lt;/a&gt; para intentar aclarar esta casuística. La funcionalidad de esta imagen es similar a lo que hacíamos con la imagen de Alpine Linux y el comando para obtener nuestra ip en Internet. Pero en este caso, tenemos un servidor web (el que trae embebido el cli de php) que correrá en el puerto 80. Este servidor web simplemente muestra un registro de la dirección ip de Internet de nuestro&amp;nbsp;contendedor.&lt;/p&gt;
&lt;p&gt;Antes de nada, si no tienes arrancado el contenedor de gluetun, hay que volver a arrancarlo. Además, hay que tener en cuenta todo lo que te he contado antes y hacer la publicación del puerto para el servidor web del otro contenedor (el de public-ip-monitor) que arrancaremos después. Así que para vamos a iniciar nuestro contenedor de Gluetun y a la vez vamos a publicar el puerto 80 del futuro contenedor en el puerto 8080 con la opción &lt;code&gt;-p 8080:80&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;gluetun_noroute2host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--cap-add&lt;span class="o"&gt;=&lt;/span&gt;NET_ADMIN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--device&lt;span class="w"&gt; &lt;/span&gt;/dev/net/tun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/store/gluetun:/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;:80&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_SERVICE_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;protonvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_USER&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_USUARIO_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_PASSWORD_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FREE_ONLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Europe/Madrid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UPDATER_PERIOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;qmcgaw/gluetun
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora ya se puede arrancar el contenedor public-ip-monitor con el servidor web que muestra el histórico de nuestra ip&amp;nbsp;&amp;#8220;pública&amp;#8221;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--network&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;container:gluetun_noroute2host&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;public-ip-monitor&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;outlyernet/public-ip-monitor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y antes de probar a acceder a este contenedor, es necesario hacer un paso adicional. La imagen public-ip-monitor-tiene una particularidad. Es necesario forzar que se &amp;#8220;auto-descubra&amp;#8221; nuestra ip en Internet con un comando ejecutado desde el propio contenedor. No se hace de manera automática. Para ello tendremos que hacer uso del subcomando &lt;code&gt;docker exec&lt;/code&gt; tal que&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;public-ip-monitor&lt;span class="w"&gt; &lt;/span&gt;/update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este comando ejecuta el script &lt;em&gt;update.sh&lt;/em&gt; en la raíz del sistema de ficheros del contenedor. Y este script obtiene nuestra ip y la guarda en un log, que después se visualiza en el servidor&amp;nbsp;web.&lt;/p&gt;
&lt;p&gt;Ahora ya podemos ir a un navegador, acceder a la ip de nuestro host de docker y al puerto 8080, es decir, http://[TU_HOST_DOCKER]:8080. De este modo, estaremos accediendo al servidor web de nuestro contenedor public-ip-monitor. Y aquí esta el&amp;nbsp;resultado:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ejemplo public ip monitori" src="https://noroute2host.com/images/0031_ejemplo_public_ip_monitor.png" style="display:block"&gt;&lt;/p&gt;
&lt;h2 id="gluetun-como-servidor-proxy"&gt;Gluetun como servidor&amp;nbsp;proxy&lt;/h2&gt;
&lt;p&gt;El otro modo de funcionamiento de Gluetun es como proxy para cualquier servicio que pueda configurarse para funcionar a través de proxy. Puede ser un servicio contenerizado o simplemente el navegador web de cualquiera de los dispositivos de tu&amp;nbsp;red.&lt;/p&gt;
&lt;p&gt;Gluetun tiene disponibles dos tipos de servidor proxy: &lt;span class="caps"&gt;HTTP&lt;/span&gt; y Shadowsocks. Para nuestros ejemplos vamos a usar la opción de &lt;span class="caps"&gt;HTTP&lt;/span&gt;, pero puedes consultar toda la información para las ambas opciones &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="iniciando-tu-contenedor-gluetun-con-servidor-proxy-incluido"&gt;Iniciando tu contenedor gluetun con servidor proxy&amp;nbsp;incluido&lt;/h3&gt;
&lt;p&gt;Para arrancar gluetun con servicio de proxy incluido vamos a usar un comando similar a cuando lo lanzamos sin proxy pero añadiendo las respectivas opciones para activar el servicio de proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;gluetun_noroute2host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--cap-add&lt;span class="o"&gt;=&lt;/span&gt;NET_ADMIN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;--device&lt;span class="w"&gt; &lt;/span&gt;/dev/net/tun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;:8888&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/store/gluetun:/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VPN_SERVICE_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;protonvpn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_USER&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_USUARIO_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENVPN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_PASSWORD_DEL_PROVEEDOR&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FREE_ONLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Europe/Madrid&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UPDATER_PERIOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HTTPPROXY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;qmcgaw/gluetun
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Solo se han añadido un par de opciones. De estas opciones, la primera es propia de&amp;nbsp;docker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-p 8888:8888&lt;/code&gt;: Expone el puerto 8888 del contenedor de Gluetun en el puerto 8888 del host de Docker. El puerto 8888 es en el corre el servicio de proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; de&amp;nbsp;Gluetun.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y la segunda es específica de gluetun, por lo que está definida como variable de entorno con la opción &lt;code&gt;-e&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;HTTPPROXY&lt;/span&gt;: Como su propio nombre indica. Al establecer esta variable a &lt;code&gt;on&lt;/code&gt; habilita el servicio de proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; de&amp;nbsp;Gluetun.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="configurar-otros-dispositivos-para-que-funcionen-a-traves-del-proxy-de-gluetun"&gt;Configurar otros dispositivos para que funcionen a través del proxy de&amp;nbsp;Gluetun&lt;/h3&gt;
&lt;p&gt;Tras arrancar el contenedor de gluetun con el servicio de proxy, ya solo nos queda conectar alguno de nuestros servicios, dispositivos o programas a este proxy. ¿Que te parece si lo hacemos con&amp;nbsp;Firefox?&lt;/p&gt;
&lt;p&gt;En primer lugar, vamos a comprobar de que país es nuestra ip actual sin configurar ningún tipo de proxy. El servicio que usaremos para ello es &lt;a href="https://ipinfo.io/what-is-my-ip"&gt;IPinfo.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Firefox sin Proxy" src="https://noroute2host.com/images/0031_firefox_sin_proxy.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Como puedes ver, la ubicación &lt;em&gt;normal&lt;/em&gt; de mi ip es España. Pero ahora vamos a activar el proxy en Firefox y a ver que pasa. Para ello configuraremos dentro de Firefox la dirección de red de nuestro contenedor de Gluetun así como su puerto. En mi caso, tengo corriendo el contenedor en mi Raspberry Pi llamada &lt;em&gt;raspibox&lt;/em&gt;, y en cuanto al puerto a usar, solo hay que tener en cuenta que el puerto del proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; de Gluetun es el 8888. Además, si miras el comando de ejecución de docker ejecutado anteriormente verás que dicho puerto está expuesto en el 8888. Por tanto la configuración de proxy quedaría&amp;nbsp;así:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuracion proxy Firefox" src="https://noroute2host.com/images/0031_firefox_configuracion_proxy.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Si tienes dudas sobre como configurar un proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; en Firefox puedes ver como hacerlo en esta &lt;a href="https://support.mozilla.org/es/kb/ajustes-de-conexion-en-firefox"&gt;página de la documntación oficial de Firefox&lt;/a&gt;. En cualquier caso, tras la configuración del proxy en Firefox el resultado es el&amp;nbsp;siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Firefox con Proxy" src="https://noroute2host.com/images/0031_firefox_con_proxy.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Como puedes ver, mi ubicación ha cambiado y ahora ¡estamos navegando a través de un servicio proxy que además funciona sobre una &lt;span class="caps"&gt;VPN&lt;/span&gt;!&lt;/p&gt;
&lt;h2 id="bonus-track-algunas-cositas-mas"&gt;Bonus Track: Algunas cositas&amp;nbsp;más&amp;#8230;&lt;/h2&gt;
&lt;h3 id="ficheros-docker-compose-de-ejemplo"&gt;Ficheros docker-compose de&amp;nbsp;ejemplo&lt;/h3&gt;
&lt;p&gt;Aunque todos los ejemplos los hemos explicado mediante &lt;code&gt;docker run&lt;/code&gt;, he dejado ficheros &lt;em&gt;docker-compose.yml&lt;/em&gt; para cada uno de los ejemplos que hemos visto en esta guía en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0031_gluetun-vpn-proxy?ref_type=heads"&gt;repositorio del blog&lt;/a&gt;. La lista de ficheros de ejemplo es la&amp;nbsp;siguiente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0031_gluetun-vpn-proxy/docker-compose-gluetun-base.yml?ref_type=heads"&gt;Fichero docker-compose básico para arranque de Gluetun&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0031_gluetun-vpn-proxy/docker-compose-gluetun-base-protonvpn.yml?ref_type=heads"&gt;Fichero docker-compose básico para arranque de Gluetun con&amp;nbsp;ProtonVPN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0031_gluetun-vpn-proxy/docker-compose-gluetun-base-proxy.yml?ref_type=heads"&gt;Fichero docker-compose para arranque de Gluetun incluyendo proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0031_gluetun-vpn-proxy/docker-compose-gluetun-base-proxy-protonvpn.yml?ref_type=heads"&gt;Fichero docker-compose para arranque de Gluetun incluyendo proxy &lt;span class="caps"&gt;HTTP&lt;/span&gt; con&amp;nbsp;ProtonVPN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0031_gluetun-vpn-proxy/docker-compose-gluetun-public-ip-monitor-protonvpn.yml?ref_type=heads"&gt;Fichero docker-compose con Gluetun y Public-Ip-Monitor y ProtonVPN como&amp;nbsp;proveedor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="usar-gluetun-para-listar-los-servidores-disponibles-de-tu-proveedor-vpn"&gt;Usar Gluetun para listar los servidores disponibles de tu proveedor &lt;span class="caps"&gt;VPN&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Como ya hemos comentado al principio de este artículo, Gluetun está preparado para funcionar con un montón de proveedores de &lt;span class="caps"&gt;VPN&lt;/span&gt;. Y muchos de ellos tienen configuración específica adicional donde puedes definir donde se ubican los servidores de &lt;span class="caps"&gt;VPN&lt;/span&gt; a los que te quieres conectar. Dependiendo del proveedor puedes elegir la región, el país o incluso la ciudad. Es por ello que Gluetun provee un modo de ejecución donde te permite ejecutar el contenedor Gluetun solo para listar los servidores del proveedor elegido. Este modo lista los servidores disponibles, la información de sus características, y al acabar, simplemente finaliza su ejecución. Esto se explica &lt;a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Este modo de ejecución funciona lanzando el contenedor de gluetun&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/yourpath:/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;qmcgaw/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;format-servers&lt;span class="w"&gt; &lt;/span&gt;-yourprovider
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y si lo personalizamos con el volumen que ya usamos en los comandos anteriores, y para el proveedor ProtonVPN. El comando quedaría&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/store/gluetun:/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;qmcgaw/gluetun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;format-servers&lt;span class="w"&gt; &lt;/span&gt;-protonvpn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y la salida sería&amp;nbsp;esta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;| Country | Region | City | Hostname | VPN | Free | Port forwarding | Secure | Tor |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Albania |  | Tirana | `al-01.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Albania |  | Tirana | `al-01.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
| Albania |  | Tirana | `al-02.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Albania |  | Tirana | `al-02.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
| Algeria |  | Algiers | `dz-02.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Algeria |  | Algiers | `dz-02.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
| Algeria |  | Algiers | `dz-01.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Algeria |  | Algiers | `dz-01.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
| Angola |  | Luanda | `ao-02.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Angola |  | Luanda | `ao-02.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
| Argentina |  |  | `node-ar-03.protonvpn.net` | openvpn | ❌ | ❌ |  |  |
| Argentina |  |  | `node-ar-03.protonvpn.net` | wireguard | ❌ | ❌ |  |  |
| Argentina |  |  | `node-ar-04.protonvpn.net` | openvpn | ❌ | ❌ |  |  |
| Argentina |  |  | `node-ar-04.protonvpn.net` | wireguard | ❌ | ❌ |  |  |
| Argentina |  | Buenos Aires | `node-ar-03.protonvpn.net` | openvpn | ❌ | ❌ |  |  |
| Argentina |  | Buenos Aires | `node-ar-03.protonvpn.net` | wireguard | ❌ | ❌ |  |  |
| Argentina |  | Buenos Aires | `node-ar-04.protonvpn.net` | openvpn | ❌ | ✅ |  |  |
| Argentina |  | Buenos Aires | `node-ar-04.protonvpn.net` | wireguard | ❌ | ✅ |  |  |
...
...
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="elige-bien-tu-proveedor-vpn-para-gluetun"&gt;Elige bien tu proveedor &lt;span class="caps"&gt;VPN&lt;/span&gt; para&amp;nbsp;Gluetun&lt;/h3&gt;
&lt;p&gt;Esto es simplemente un consejo que puede parecer muy básico pero que es muy importante. No todos los servicios de &lt;span class="caps"&gt;VPN&lt;/span&gt; ofrecen todos los servicios ni siquiera en su plan de pago. Pero en el caso de los planes gratuitos cada uno pone sus limitaciones y pueden ser muy diferentes de un proveedor a otro. Te pongo algunos&amp;nbsp;ejemplos&amp;#8230;&lt;/p&gt;
&lt;p&gt;ProtonVPN funciona muy bien pero en el plan gratuito los países están limitados a solo unos pocos. Esto suele ser normal en los planes gratis. Pero también tiene limitaciones con el tráfico &lt;span class="caps"&gt;P2P&lt;/span&gt; que se pueden solventar en su plan de&amp;nbsp;pago.&lt;/p&gt;
&lt;p&gt;Otro ejemplo puede ser PrivadoVPN, he probado su aplicación Android y no tiene esas limitaciones en el tráfico &lt;span class="caps"&gt;P2P&lt;/span&gt;, pero si tiene límite de &lt;span class="caps"&gt;GB&lt;/span&gt; de tráfico al mes en su plan gratuito. Y no solo eso, tras estar alguna que otra hora intentando hacer funcionar Gluetun con PrivadoVPN en su plan gratuito, acabé dándome cuenta que PrivadoVPN no permite usar otras aplicaciones que no sean la oficial para el plan gratis. Pasaron horas hasta que me dí cuenta porque el error en Gluetun siempre era de credenciales&amp;nbsp;incorrectas.&lt;/p&gt;
&lt;p&gt;Así que para elegir tu proveedor &lt;span class="caps"&gt;VPN&lt;/span&gt;, además de tener en cuenta el coste, piensa en el uso que le vas a dar y evalúa cual es el mejor para este&amp;nbsp;uso.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/qdm12/gluetun"&gt;Gluetun en&amp;nbsp;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/qdm12/gluetun-wiki"&gt;Gluetun&amp;nbsp;Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/qmcgaw/gluetun"&gt;Imágenes oficiales de Gluetun en&amp;nbsp;DockerHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0031_gluetun-vpn-proxy?ref_type=heads"&gt;Ficheros docker-compose en el Repositorio&amp;nbsp;noroute2host-files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/outlyernet/public-ip-monitor"&gt;Imagen de public-ip-monitor en&amp;nbsp;DockerHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/outlyer-net/docker-public-ip-monitor"&gt;public-ip-monitor en&amp;nbsp;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="contenedores"></category><category term="gluetun"></category><category term="vpn"></category><category term="proxy"></category><category term="docker"></category></entry><entry><title>Bots en Telegram (II): Tu bot de Telegram con Bash</title><link href="https://noroute2host.com/bot-telegram-bash.html" rel="alternate"></link><published>2024-12-10T19:00:00+01:00</published><updated>2024-12-10T19:00:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2024-12-10:/bot-telegram-bash.html</id><summary type="html">&lt;p&gt;Ya era hora de traer la continuación del artículo de Bots en Telegram (I). En este nuevo capítulo vamos a centrarnos en como usar un bot de Telegram desde la shell bash de Linux. Como ya indiqué en la primera parte, estos bots son muy útiles para integrarlos con nuestros scripts. Con bash y un bot de Telegram podemos recibir la información que queremos en cualquier momento y&amp;nbsp;lugar.&lt;/p&gt;</summary><content type="html">&lt;h2 id="bots-en-telegram-ii-tu-bot-de-telegram-con-bash"&gt;Bots en Telegram (&lt;span class="caps"&gt;II&lt;/span&gt;): Tu bot de Telegram con&amp;nbsp;Bash&lt;/h2&gt;
&lt;p&gt;Ya era hora de traer la continuación del artículo de &lt;a href="https://noroute2host.com/creat-bot-telegram.html"&gt;Bots en Telegram (I) Creación de un bot de Telegram&lt;/a&gt;. En este nuevo capítulo vamos a centrarnos en como usar un bot de Telegram desde la shell bash de Linux. Como ya indiqué en la primera parte, estos bots son muy útiles para integrarlos con nuestros&amp;nbsp;scripts.&lt;/p&gt;
&lt;p&gt;Con bash y un bot de Telegram podemos recibir la información que queramos en cualquier momento y lugar. ¿Como lograrlo? Pues justo esto es lo que voy a explicar&amp;nbsp;hoy.&lt;/p&gt;
&lt;h2 id="curl"&gt;Curl&lt;/h2&gt;
&lt;p&gt;En este blog muchas veces nos centramos en las utilidades más básicas de nuestros terminales. Y &lt;code&gt;curl&lt;/code&gt; es sin duda una de ellas. En este caso, la relación con este post se basa en que la manera más simple de enviar mensajes desde tu terminal de linux a tu bot de Telegram es &lt;code&gt;curl&lt;/code&gt;. ¿Que no conoces curl? ¡Pues deberías! Citando directamente la definición de &lt;a href="https://en.wikipedia.org/wiki/CURL"&gt;Wikipedia&lt;/a&gt;, &lt;em&gt;cURL es un proyecto de software informático que proporciona una biblioteca (libcurl) y una herramienta de línea de comandos (curl) para transferir datos utilizando varios protocolos de red&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Y como indican directamente en la página oficial de &lt;code&gt;curl&lt;/code&gt;, &lt;em&gt;curl se utiliza en líneas de comandos o scripts para transferir datos. curl también es libcurl, se utiliza en automóviles, televisores, enrutadores, impresoras, equipos de audio, teléfonos móviles, tabletas, dispositivos médicos, decodificadores, juegos de computadora, reproductores multimedia y es el motor de transferencia de Internet para innumerables aplicaciones de software en más de veinte mil millones de instalaciones. Curl es utilizado diariamente por prácticamente todos los usuarios de Internet en el&amp;nbsp;mundo.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Así que como puedes ver, prácticamente en cada terminal existe &lt;code&gt;curl&lt;/code&gt; y aunque no lo conocieras antes de leer este texto, seguro que has usado la herramienta sin&amp;nbsp;saberlo.&lt;/p&gt;
&lt;h2 id="tu-bot-de-telegram-en-bash"&gt;Tu bot de Telegram en&amp;nbsp;Bash&lt;/h2&gt;
&lt;p&gt;Así que como ya te he dicho anteriormente, la manera más directa de hacer uso de tu bot de Telegram desde la terminal es usar directamente curl. Pero antes de explicarte cómo, hay que ver que se necesita. La respuesta es&amp;nbsp;simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tu bot de&amp;nbsp;Telegram.&lt;/li&gt;
&lt;li&gt;El Token para hacer uso del&amp;nbsp;mismo.&lt;/li&gt;
&lt;li&gt;Tu Chat&amp;nbsp;Id.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si todavía no dispones de lo anterior, te recuerdo que tienes explicado como crear un bot de Telegram desde cero y obtener tu Token y tu Chat Id en la primera parte de esta serie: &lt;a href="https://noroute2host.com/creat-bot-telegram.html"&gt;Bots en Telegram (I): Creación de un bot de Telegram&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Con todo lo anterior, hacer uso de tu bot de Telegram desde tu terminal es muy&amp;nbsp;sencillo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;https://api.telegram.org/bot&lt;span class="o"&gt;[&lt;/span&gt;TOKEN_SUPERSECRETO_DEL_BOT&lt;span class="o"&gt;]&lt;/span&gt;/sendMessage&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;TU_CHATID_SECRETO&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Este es un mensaje especial para/de mi bot&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Estas son las opciones de curl&amp;nbsp;usadas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-s&lt;/code&gt;: Activa el &lt;em&gt;Modo Silencioso&lt;/em&gt; de&amp;nbsp;curl.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-X&lt;/code&gt;: Establece el modo de la petición &lt;span class="caps"&gt;HTTP&lt;/span&gt; que enviaremos. En este caso usaremos &lt;em&gt;&lt;span class="caps"&gt;POST&lt;/span&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;URL&lt;/span&gt;: Justo después del modo se indica la &lt;span class="caps"&gt;URL&lt;/span&gt; https://api.telegram.org/bot[TOKEN_SUPERSECRETO_DEL_BOT]/sendMessage que se construye con el Token del bot&amp;nbsp;dentro.&lt;/li&gt;
&lt;li&gt;[TOKEN_SUPERSECRETO_DEL_BOT]: Es el Token de tu bot. Se incluye dentro de la propia&amp;nbsp;Url.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Al usar el modo &lt;span class="caps"&gt;POST&lt;/span&gt; se pueden pasar datos. La opción &lt;code&gt;-d&lt;/code&gt; sirve para indicar que vamos a pasar información en la petición &lt;span class="caps"&gt;POST&lt;/span&gt;. En este caso, dos campos &lt;em&gt;chat_id&lt;/em&gt; y &lt;em&gt;text&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;[TU_CHATID_SECRETO]: Es tu Chat &lt;span class="caps"&gt;ID&lt;/span&gt;. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="script-de-bot-de-telegram-para-tu-terminal"&gt;Script de bot de Telegram para tu&amp;nbsp;terminal&lt;/h2&gt;
&lt;p&gt;Una vez que ya se ha visto la manera más rápida de usar tu bot de Telegram desde tu consola, es hora de hacer esto un poco más sofisticado y compartir un script de bash para usar tu bot de Telegram desde la terminal: &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0030_bot-telegram-bash/0030_enviar_mensaje_telegram.sh?ref_type=heads"&gt;0030_enviar_mensaje_telegram.sh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Este script tiene dos modos de&amp;nbsp;uso:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Usando un fichero de configuración previamente definido donde esté la información del Token y el Chat&amp;nbsp;Id&lt;/li&gt;
&lt;li&gt;Pasando directamente el Token y el Chat Id por&amp;nbsp;parámetros.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Los parámetros de entrada admitidos&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: Fichero de configuración donde deben estar definidos el &lt;em&gt;TOKEN_SUPERSECRETO_DEL_BOT&lt;/em&gt; y &lt;em&gt;CHAT_ID&lt;/em&gt; especificados como se indica en el siguiente punto. Este parámetro se usa en el modo de funcionamiento&amp;nbsp;1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t&lt;/code&gt;: El &lt;em&gt;TOKEN_SUPERSECRETO_DEL_BOT&lt;/em&gt; de tu bot. Este parámetro se usa en el modo de funcionamiento&amp;nbsp;2.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;: Tu &lt;em&gt;CHAT_ID&lt;/em&gt; super secreto. Este parámetro se usa en el modo de funcionamiento&amp;nbsp;2.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m&lt;/code&gt;: Especifica el mensaje a enviar. Es un parámetro obligatorio en los dos modos de&amp;nbsp;funcionamiento.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Habilita el modo&amp;nbsp;depuración.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt;: Muestra la&amp;nbsp;ayuda.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="especificacion-del-fichero-de-configuracion-para-el-primer-modo-de-funcionamiento"&gt;Especificación del fichero de configuración para el primer modo de&amp;nbsp;funcionamiento&lt;/h3&gt;
&lt;p&gt;La sintaxis esperada del fichero de configuración es tremendamente simple. Solo hay que definir dos líneas o variables: &lt;code&gt;TOKEN&lt;/code&gt; y &lt;code&gt;CHAT_ID&lt;/code&gt;. A continuación se muestra un ejemplo, pero tienes un fichero de configuración ejemplo &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0030_bot-telegram-bash/0030_configuracion.ejemplo?ref_type=heads"&gt;aquí&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;TOKEN=&amp;quot;TOKEN_SUPERSECRETO_DEL_BOT&amp;quot;
CHAT_ID=&amp;quot;CHAT_ID_SUPERSECRETO_DEL_BOT&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="ejemplos-de-funcionamiento-del-script"&gt;Ejemplos de funcionamiento del&amp;nbsp;script&lt;/h3&gt;
&lt;p&gt;Como el script tiene dos modos de funcionamiento os voy a mostrar como se ejecuta y funciona en cada uno de&amp;nbsp;ellos.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modo 1:&lt;/strong&gt; Usando un archivo de configuración con el parámetro &lt;code&gt;-f&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./0030_enviar_mensaje_telegram.sh&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/tu/ruta/al/fichero/configuracion/telegram_bot.conf&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Prueba del bot de noroute2host usando el fichero de configuracion&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Ejemplo bot Telegram Bash Modo 1" src="https://noroute2host.com/images/0030_bot_telegram_bash_modo1.png" style="display:block"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modo 2:&lt;/strong&gt; Pasando por parámetros el Token y el Chat&amp;nbsp;Id&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./0030_enviar_mensaje_telegram.sh&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TOKEN_SUPERSECRETO_DEL_BOT&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CHAT_ID_SUPERSECRETO_DEL_BOT&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Prueba del bot de noroute2host pasando los datos por parámetro&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Ejemplo bot Telegram Bash Modo 2" src="https://noroute2host.com/images/0030_bot_telegram_bash_modo2.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="codigo-fuente-del-script-de-bot-de-telegram-para-bash"&gt;Código fuente del script de bot de Telegram para&amp;nbsp;Bash&lt;/h3&gt;
&lt;p&gt;Antes de terminar te recuerdo que tanto &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0030_bot-telegram-bash/0030_enviar_mensaje_telegram.sh?ref_type=heads"&gt;este&lt;/a&gt; como todos los scripts, ficheros de código u otras chuletas de código fuente las puedes encontrar en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files"&gt;repositorio del blog en GitLab&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="en-el-siguiente-capitulo-si-lo-hubiera"&gt;En el siguiente capitulo si lo&amp;nbsp;hubiera&amp;#8230;&lt;/h2&gt;
&lt;p&gt;Si en futuro hubiese un siguiente capítulo, estaría dedicado a como usar tu bot de Telegram desde Python. Hay múltiples formas de hacerlo y alguna de ellas es bastante interesante si lo que se quiere es estar notificado de eventos de tus aplicaciones&amp;nbsp;Python.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://noroute2host.com/creat-bot-telegram.html"&gt;Bots en Telegram (I): Creación de un bot de&amp;nbsp;Telegram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://t.me/noroute2host_bot"&gt;@noroute2host_bot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files"&gt;Repositorio&amp;nbsp;noroute2host-files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots"&gt;Documentación oficial de bots de&amp;nbsp;Telegram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/"&gt;Sitio oficial de&amp;nbsp;curl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="telegram"></category><category term="bot"></category><category term="telegram"></category><category term="bash"></category><category term="scripts"></category></entry><entry><title>Localización de aplicaciones Python con gettext</title><link href="https://noroute2host.com/localizacion-aplicaciones-python-gettext.html" rel="alternate"></link><published>2024-08-01T18:20:00+02:00</published><updated>2024-08-01T18:20:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2024-08-01:/localizacion-aplicaciones-python-gettext.html</id><summary type="html">&lt;p&gt;Recientemente me planteé traducir o localizar el &lt;a href="https://noroute2host.com/feliz-en-la-noche-pygame-game-jam.html"&gt;juego que desarrollé en Python con Pygame&lt;/a&gt;. Y la verdad es que no tenía claro como hacerlo. Acabé usando el módulo &lt;code&gt;gettext&lt;/code&gt;. Pero, a decir verdad, pensaba que iba a ser bastante más sencillo de lo que finalmente fué. Así que me ha parecido buena idea compartir como realizar este proceso y hacer algunas recomendaciones en base a mi&amp;nbsp;experiencia.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recientemente me planteé traducir o localizar el &lt;a href="https://noroute2host.com/feliz-en-la-noche-pygame-game-jam.html"&gt;juego que desarrollé en Python con Pygame&lt;/a&gt;. Y la verdad es que no tenía claro como hacerlo. Acabé usando el módulo &lt;code&gt;gettext&lt;/code&gt;. Pero, a decir verdad, pensaba que iba a ser bastante más sencillo de lo que finalmente fué. Así que me ha parecido buena idea compartir como realizar este proceso y hacer algunas recomendaciones en base a mi&amp;nbsp;experiencia.&lt;/p&gt;
&lt;p&gt;Lo primero que me llamó la atención es que pensaba encontrar más opciones de base o al menos más sencillas de utilizar. Pero la realidad es que la única opción en las librerías estándar de Python es &lt;a href="https://docs.python.org/es/3.12/library/gettext.html"&gt;gettext&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="acerca-de-gettext"&gt;Acerca de&amp;nbsp;gettext&lt;/h2&gt;
&lt;p&gt;gettext es una librería o un sistema de internacionalización y localización de aplicaciones (i18n y l10n) que se usa de manera muy común en el desarrollo de aplicaciones en entornos tipo Unix (como Linux por ejemplo). De hecho la implementación más famosa de gettext es &lt;a href="https://www.gnu.org/software/gettext/"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; gettext&lt;/a&gt;. Tanto es así que Python soporta dos maneras diferentes de usar&amp;nbsp;gettext:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;gettext&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;API&lt;/span&gt; basada en&amp;nbsp;clases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La segunda de las opciones, la &lt;span class="caps"&gt;API&lt;/span&gt; basada en clases es la opción recomendada para localizar aplicaciones y módulos Python debido a que ofrece una mayor flexibilidad. Para más información puedes consultar la &lt;a href="https://docs.python.org/es/3/library/gettext.html"&gt;documentación oficial de Python para gettext&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Antes de pasar a como usar gettext para localizar tus aplicaciones python (o cualquier aplicación que soporte gettext en realidad) me gustaría indicar que gettext permite tanto internacionalización como localización. En mi caso, he usado gettext fundamentalmente para temas de localización. Es decir, trasladar los textos de mi aplicación a varios idiomas. Se podría decir que la internacionalización es algo más amplio que puede incluir el formato de las fechas, números u otros&amp;nbsp;elementos.&lt;/p&gt;
&lt;h3 id="ficheros-de-gettext"&gt;Ficheros de&amp;nbsp;gettext&lt;/h3&gt;
&lt;p&gt;Como buena utilidad para sistemas Linux gettext basa su funcionamiento en una serie de ficheros. En concreto, además de los ficheros de código fuente de tu aplicación, hay que tener en cuenta tres tipos de ficheros a la hora de trabajar con&amp;nbsp;gettext:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.pot&lt;/code&gt;: Portable Object Template. Este fichero es la plantilla para traducción o localización que se genera cuando se extraen las cadenas a traducir de tu&amp;nbsp;aplicación.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.po&lt;/code&gt;: Portable Object. Estos ficheros son los que contienen la traducción de las cadenas a los diferentes lenguajes. Normalmente habrá un fichero &lt;code&gt;.po&lt;/code&gt; por cada&amp;nbsp;traducción.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.mo&lt;/code&gt;: Machine Object. Los ficheros &lt;code&gt;.mo&lt;/code&gt; son el resultado de compilar los ficheros &lt;code&gt;.po&lt;/code&gt;. Como en el caso anterior, también habrá un fichero &lt;code&gt;.mo&lt;/code&gt; por traducción. Se puede decir que la información de los ficheros &lt;code&gt;.po&lt;/code&gt; y &lt;code&gt;.mo&lt;/code&gt; es la misma. Pero, en el primer caso es legible por los humanos, en el segundo están en lenguaje&amp;nbsp;máquina.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="utilidades-de-gettext"&gt;Utilidades de&amp;nbsp;gettext&lt;/h3&gt;
&lt;p&gt;gettext tiene multitud de utilidades para diferentes objetivos. Puedes consultarlas todas &lt;a href="https://www.gnu.org/software/gettext/manual/gettext.html"&gt;aquí&lt;/a&gt;. Pero, para el caso que nos ocupa usaremos fundamentalmente&amp;nbsp;tres:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xgettext&lt;/code&gt; o &lt;code&gt;pygettext3&lt;/code&gt;: Esta utilidad escanea nuestros ficheros de código fuente para obtener cadenas &lt;em&gt;localizables&lt;/em&gt; y generar el fichero de plantilla &lt;code&gt;.pot&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msgmerge&lt;/code&gt;: Genera o regenera los ficheros &lt;code&gt;.po&lt;/code&gt; a partir del fichero &lt;code&gt;.pot&lt;/code&gt;. Como los ficheros que se usan para traducir son los &lt;code&gt;.po&lt;/code&gt;, ¿qué pasa cuando incluimos nuevas cadenas &lt;em&gt;localizables&lt;/em&gt; en nuestra aplicación? ¿hay que volver a crear y traducir los ficheros &lt;code&gt;.po&lt;/code&gt;? La respuesta es &lt;code&gt;msgmerge&lt;/code&gt;, que mezcla o une nuestro fichero &lt;code&gt;.po&lt;/code&gt; que ya contiene traducciones con el fichero de referencia &lt;code&gt;.pot&lt;/code&gt;. De este modo se preservan las traducciones existentes y se añaden las nuevas que encuentre en el fichero &lt;code&gt;.pot&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msgfmt&lt;/code&gt;: Este programa crea un archivo binario &lt;code&gt;.mo&lt;/code&gt; a partir de la compilación del fichero de texto &lt;code&gt;.po&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="estructura-de-ficheros"&gt;Estructura de&amp;nbsp;ficheros&lt;/h3&gt;
&lt;p&gt;Debido a la dependencia de gettext de los ficheros que hemos visto antes, también es necesaria una estructura de ficheros concreta para que el sistema pueda encontrar los ficheros &lt;code&gt;.mo&lt;/code&gt; con las traducciones. En esencia, debes colocar tus ficheros &lt;code&gt;.mo&lt;/code&gt; en la siguiente ruta: &lt;code&gt;localedir/language/LC_MESSAGES/&lt;/code&gt;. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;localedir&lt;/em&gt;: Es simplemente el directorio base de las traducciones. Veremos que puedes definir esta ubicación en tu&amp;nbsp;código.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;languaje&lt;/em&gt;: Se trata de un código de lenguaje. Puede ser genérico como &lt;code&gt;en&lt;/code&gt; o &lt;code&gt;es&lt;/code&gt;, o más específico como &lt;code&gt;es_ES&lt;/code&gt; o &lt;code&gt;es_AR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;LC_MESSAGES&lt;/em&gt;: Se trata de la categoría del locale. Por defecto, para gettext es&amp;nbsp;LC_MESSAGES. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puede parecer un poco complicado pero tampoco lo es demasiado. Como ejemplo, te muestro la estructura creada para mi juego en&amp;nbsp;Pygame:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;locales
├── en
│   └── LC_MESSAGES
│       ├── pang-nightfall.mo
│       └── pang-nightfall.po
├── es
│   └── LC_MESSAGES
│       ├── pang-nightfall.mo
│       └── pang-nightfall.po
└── pang-nightfall.pot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="trabajando-con-getttext-en-python"&gt;Trabajando con getttext en&amp;nbsp;Python&lt;/h2&gt;
&lt;p&gt;Una vez que hemos hablado un poco de gettext, su origen, su estructura y su funcionamiento básico, llega el momento de ponernos manos a la obra y mostrar como se puede usar para localizar tus aplicaciones&amp;nbsp;Python.&lt;/p&gt;
&lt;h3 id="usando-gnu-api-gettext-en-python"&gt;Usando &lt;span class="caps"&gt;GNU&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt; gettext en&amp;nbsp;Python&lt;/h3&gt;
&lt;p&gt;Aunque he dicho antes que acabé usando la implementación &lt;span class="caps"&gt;API&lt;/span&gt; basada en clases porque es la recomendada, creo que para explicar el funcionamiento voy a comenzar mostrando el modo &lt;span class="caps"&gt;GNU&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;gettext.&lt;/p&gt;
&lt;p&gt;Vamos a partir de un primer ejemplo, &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/0029_localizacion-python-gnu-gettext.py?ref_type=heads"&gt;0029_localizacion-python-gnu-gettext.py&lt;/a&gt;, donde se definen las cadenas con la función &lt;code&gt;_()&lt;/code&gt; que a su vez se define como un alias para &lt;code&gt;gettext.gettext()&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gettext&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gettext&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si ejecutamos este programa, el resultado será similar a si se hubiera hecho un &lt;code&gt;print&lt;/code&gt; normal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-gnu-gettext.py
Somos&lt;span class="w"&gt; &lt;/span&gt;cadenas&lt;span class="w"&gt; &lt;/span&gt;localizables
Hola&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Adios&lt;span class="w"&gt; &lt;/span&gt;noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pero en realidad lo que esta pasando es no se están imprimiendo las cadenas directamente. Sino que se está devolviendo la traducción localizada de cada mensaje con la función &lt;code&gt;gettext.gettext("mensaje")&lt;/code&gt;. Pero como no se han generado ficheros de traducción &lt;code&gt;.mo&lt;/code&gt; ni definido donde se encontrarían, directamente devuelve y por tanto imprime por defecto el identificador de la cadena dentro de módulo de gettext o el &lt;em&gt;msgid&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="usando-la-api-basada-en-clases-de-gettext-en-python"&gt;Usando la &lt;span class="caps"&gt;API&lt;/span&gt; basada en clases de gettext en&amp;nbsp;Python&lt;/h3&gt;
&lt;p&gt;Ahora vamos a transformar el ejemplo anterior para usar el método recomendado, es decir, la &lt;span class="caps"&gt;API&lt;/span&gt; basada en clases del módulo gettext de Python. Esto dará lugar al fichero &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/0029_localizacion-python-API-basada-clases.py?ref_type=heads"&gt;0029_localizacion-python-&lt;span class="caps"&gt;API&lt;/span&gt;-basada-clases.py&lt;/a&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gettext&lt;/span&gt;

&lt;span class="n"&gt;appname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;localedir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;locales&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En este caso la gran diferencia es el uso de la función &lt;code&gt;gettext.translation&lt;/code&gt; que según la documentación oficial se define por defecto del siguiente&amp;nbsp;modo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y en nuestro ejemplo la hemos usado&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Paso a explicar cada uno de los parámetros que se han&amp;nbsp;usado:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;domain&lt;/strong&gt;: Para el dominio se ha usado una variable llamada &lt;code&gt;appname&lt;/code&gt; definida previamente con el valor &lt;code&gt;noroute2host&lt;/code&gt;. La explicación podría ser más compleja, pero básicamente podemos quedarnos con que el dominio es el nombre de fichero que usará de base para buscar los ficheros de traducción &lt;code&gt;.mo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;localedir&lt;/strong&gt;: Se ha definido como una variable &lt;code&gt;localedir&lt;/code&gt; que tiene el valor &lt;code&gt;locales&lt;/code&gt;. Podría tener otro valor puesto que es básicamente la raíz donde va a buscar los ficheros de&amp;nbsp;traducción.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fallback&lt;/strong&gt;: Se ha puesto a &lt;code&gt;True&lt;/code&gt; para que si no encuentra un fichero &lt;code&gt;.mo&lt;/code&gt; la función no devuelva una excepción y devuelva un objeto de la clase &lt;code&gt;NullTranslations&lt;/code&gt;. De hecho, en este ejemplo todavía no están definidos los ficheros de traducción y como veremos después no se genera ningún&amp;nbsp;error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;languages&lt;/strong&gt;: Es una lista de uno o más lenguajes. Son los que intentará cargar el fichero &lt;code&gt;.mo&lt;/code&gt; si existe para localizar las&amp;nbsp;cadenas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;class_&lt;/strong&gt;: Este parámetro no se usa. Sirve para modificar el tipo de clase que devuelve la función &lt;code&gt;translation&lt;/code&gt;. Si no se especifica nada, por defecto será un objeto de la clase &lt;code&gt;GNUTranslations&lt;/code&gt;. Este es nuestro&amp;nbsp;caso.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por otro lado, hay otro cambio importante en el&amp;nbsp;código:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto es simplemente una manera, aunque la más conveniente, de hacer disponible la función &lt;code&gt;_()&lt;/code&gt; en la&amp;nbsp;aplicación.&lt;/p&gt;
&lt;p&gt;Después de todos los cambios el resultado es similar al del modo anterior. Como comentamos antes, se está intentando devolver la versión localizada de las cadenas pero como no se encuentran se devuelven tal&amp;nbsp;cual.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases.py
Somos&lt;span class="w"&gt; &lt;/span&gt;cadenas&lt;span class="w"&gt; &lt;/span&gt;localizables
Hola&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Adios&lt;span class="w"&gt; &lt;/span&gt;noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto se puede comprobar si se cambia el parámetro &lt;code&gt;fallback&lt;/code&gt; a &lt;code&gt;False&lt;/code&gt;. Al ejecutar nuestro ejemplo con este cambio, se produce un error al no encontrar los ficheros de&amp;nbsp;traducción.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases.py
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0029_localizacion-python-API-basada-clases.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;module&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;translations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gettext.translation&lt;span class="o"&gt;(&lt;/span&gt;appname,&lt;span class="w"&gt; &lt;/span&gt;localedir,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/lib/python3.8/gettext.py&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;603&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;translation
&lt;span class="w"&gt;    &lt;/span&gt;raise&lt;span class="w"&gt; &lt;/span&gt;FileNotFoundError&lt;span class="o"&gt;(&lt;/span&gt;ENOENT,
FileNotFoundError:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Errno&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;translation&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;domain:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="trabajando-con-los-ficheros-de-localizacion"&gt;Trabajando con los ficheros de&amp;nbsp;localización&lt;/h3&gt;
&lt;p&gt;Lo primero que hay que hacer es crear la estructura de directorios que ya hemos esbozado antes. En principio, vamos a generar localizaciones para dos idiomas: Español (es) e Inglés&amp;nbsp;(es).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;locales
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;locales/es
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;locales/es
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="generando-el-fichero-de-platilla-pot"&gt;Generando el fichero de platilla&amp;nbsp;(.pot)&lt;/h4&gt;
&lt;p&gt;El primer paso será generar el fichero de plantilla &lt;code&gt;.pot&lt;/code&gt;. Este fichero se genera con la utilidad &lt;code&gt;pygettext3&lt;/code&gt; o &lt;code&gt;xgettext&lt;/code&gt;. Esta utilidad lo que hace es &lt;em&gt;extraer&lt;/em&gt; los cadenas localizables de nuestro fichero Python. ¿Y como sabe cuáles son? Pues la respuesta es bastante sencilla, simplemente extrae los textos que se pasan como parámetros a la función &lt;code&gt;_()&lt;/code&gt;. Y esto es bastante importante porque significa, que lo que pongamos en esas llamadas es el texto base de nuestra aplicación. Es decir, lo que se mostrará si no hay traducción. Por lo que esta decisión &amp;#8220;puede&amp;#8221; marcar el lenguaje &lt;em&gt;base&lt;/em&gt; de tu aplicación. Y digo &lt;em&gt;puede&lt;/em&gt; porque al final te contaré como me gusta a mí usar este lenguaje &lt;em&gt;base&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;En cuanto a usar &lt;code&gt;pygettext3&lt;/code&gt; o &lt;code&gt;xgettext&lt;/code&gt; puedes usar la que quieras. Antiguamente &lt;code&gt;xgettext&lt;/code&gt; no funcionaba del todo con ficheros Python y por eso había una versión Python: &lt;code&gt;pygettext&lt;/code&gt;. Pero en la actualidad &lt;code&gt;xgettext&lt;/code&gt; soporta multitud de lenguajes, entre los que está&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;Para instalar las utilidades de &lt;span class="caps"&gt;GNU&lt;/span&gt; gettext, es posible que tengas que instalarlas antes. En en el caso de Debian/Ubuntu bastaría con usar&amp;nbsp;apt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# apt install gettext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sin embargo a mí me gusta más la salida que muestra &lt;code&gt;pygettext3&lt;/code&gt; y por eso es el que uso este último, pero es simplemente una cuestión de gustos. En cualquier caso, &lt;code&gt;pygettext&lt;/code&gt; suele venir incluido en las instalaciones de&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;La ejecución de esta herramienta podría&amp;nbsp;ser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pygettext3 -debug --verbose -d DOMINIO -o FICHERO_POT_SALIDA FICHERO_PYTHON_ENTRADA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Donde los parámetros&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;span class="caps"&gt;DOMINIO&lt;/span&gt;&lt;/em&gt;: Es un nombre único que identifica tu&amp;nbsp;aplicación.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;FICHERO_POT_SALIDA&lt;/em&gt;: Es el la ruta donde se dejará el fichero &lt;code&gt;.pot&lt;/code&gt; de&amp;nbsp;salida.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;FICHERO_PYTHON_SALIDA&lt;/em&gt;: El fichero de entrada donde se buscarán las&amp;nbsp;cadenas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por tanto, en el ejemplo que nos ocupa el comando quedaría tal que&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pygettext3&lt;span class="w"&gt; &lt;/span&gt;-debug&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;noroute2host&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y el resultado es el fichero &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/noroute2host.pot?ref_type=heads"&gt;locales/noroute2host.pot&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-25 20:53+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;


&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:10&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:11&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:12&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="creando-los-ficheros-de-traduccion-po-y-localizando-los-textos"&gt;Creando los ficheros de traducción (.po) y localizando los&amp;nbsp;textos&lt;/h4&gt;
&lt;p&gt;Con el fichero de plantilla creado, y teniendo en cuenta que vamos a trabajar con dos idiomas (español e inglés), hay que generar los ficheros de traducciones y localizar las&amp;nbsp;cadenas.&lt;/p&gt;
&lt;p&gt;El modo más simple de generar los ficheros de traducción es simplemente copiar el fichero &lt;code&gt;.pot&lt;/code&gt; para crear los ficheros &lt;code&gt;.po&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.po
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.po
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora que ya están creados los ficheros &lt;code&gt;.po&lt;/code&gt;, lo que hay que hacer es traducirlos. En el ejemplo que nos ocupa los hemos dejado&amp;nbsp;así:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Español: &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/es/LC_MESSAGES/noroute2host.po?ref_type=heads"&gt;locales/es/LC_MESSAGES/noroute2host.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-25 20:53+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:10&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:11&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:12&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Inglés: &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/en/LC_MESSAGES/noroute2host.po?ref_type=heads"&gt;locales/en/LC_MESSAGES/noroute2host.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-25 20:53+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:10&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;We are localizable strings&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:11&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hi noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases.py:12&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Goodbye noroute2host&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="compilando-los-ficheros-po-para-generar-los-ficheros-binarios-mo"&gt;Compilando los ficheros .po para generar los ficheros binarios&amp;nbsp;.mo&lt;/h4&gt;
&lt;p&gt;Con lo hecho anteriormente tenemos los catálogos de mensajes en formato texto. Pero nuestro programa Python necesita que estos mensajes estén compilados en un formato binario que pueda entender. Para este cometido usaremos la utilidad &lt;code&gt;msgfmt&lt;/code&gt;. Usaremos esta utilidad de la siguiente&amp;nbsp;forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;FICHERO_MO_SALIDA&lt;span class="w"&gt; &lt;/span&gt;FICHERO_PO_ENTRADA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Donde las opciones y los parámetros&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: Opción &lt;em&gt;fuzzy&lt;/em&gt;. Con esta opción se tienen en cuenta los mensajes que las utilidades anteriores han marcado como &lt;em&gt;fuzzy&lt;/em&gt; o &lt;em&gt;difusos&lt;/em&gt;. En mi caso es una opción que uso porque ha veces (no entiendo el por qué hay mensajes que los detecta así. Y si no se indica esta opción no se compilan en la&amp;nbsp;traducción.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt;: Sirve para especificar el fichero .mo de&amp;nbsp;salida.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;FICHERO_MO_SALIDA&lt;/em&gt;: Es el la ruta donde se dejará el fichero &lt;code&gt;.mo&lt;/code&gt; de&amp;nbsp;salida.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;FICHERO_PO_ENTRADA&lt;/em&gt;: El fichero de entrada &lt;code&gt;.po&lt;/code&gt; que se usará de base para la&amp;nbsp;compilación.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además, algo muy importante que hay que tener en cuenta es que la compilación tiene un fichero de entrada y uno de salida. Por tanto, hay que ejecutar el &lt;code&gt;msgfmt&lt;/code&gt; una vez por cada fichero &lt;code&gt;.po&lt;/code&gt; que tengamos. En nuestro ejemplo, tenemos 2 ficheros &lt;code&gt;.po&lt;/code&gt; por lo que habría que lanzar los siguientes 2&amp;nbsp;comandos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.mo&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.po
$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.mo&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.po
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="estructura-de-ficheros-de-localizacion"&gt;Estructura de ficheros de&amp;nbsp;localización&lt;/h4&gt;
&lt;p&gt;Ya tenemos todos los ficheros de localización creados. Esta sería la estructura de directorios y ficheros de&amp;nbsp;localización:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;locales/
├── en
│   └── LC_MESSAGES
│       ├── noroute2host.mo
│       └── noroute2host.po
├── es
│   └── LC_MESSAGES
│       ├── noroute2host.mo
│       └── noroute2host.po
└── noroute2host.pot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="probando-la-aplicacion-localizada"&gt;Probando la aplicación&amp;nbsp;localizada&lt;/h3&gt;
&lt;p&gt;Antes de pasar a probar la aplicación con localización, vamos a hacer un pequeño cambio en el código para que nos permita seleccionar el idioma antes de imprimir la salida. No es la mejor solución para pedir al usuario que elija el idioma de una lista pero para el ejemplo&amp;nbsp;valdrá.&lt;/p&gt;
&lt;p&gt;Esta versión del programa quedará reflejada en el fichero &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/0029_localizacion-python-API-basada-clases-multi.py?ref_type=heads"&gt;0029_localizacion-python-&lt;span class="caps"&gt;API&lt;/span&gt;-basada-clases-multi.py&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gettext&lt;/span&gt;

&lt;span class="n"&gt;appname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;localedir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;locales&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para seleccionar el idioma&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Seleccion de Idioma&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;esp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Por favor introduce esp o eng: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para imprimir cadenas&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El pequeño cambio que hemos hecho es crear la función &lt;code&gt;preguntar_idioma()&lt;/code&gt;, que pregunta al usuario por el idioma previamente. Para que, de este modo, establecer la localización antes de imprimir las cadenas. Es verdad que se imprime primero el texto que pregunta al usuario sin localizar pero para fines didácticos se ve claro. El resultado es el&amp;nbsp;siguiente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;Español:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;esp
Somos&lt;span class="w"&gt; &lt;/span&gt;cadenas&lt;span class="w"&gt; &lt;/span&gt;localizables
Hola&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Adios&lt;span class="w"&gt; &lt;/span&gt;noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;Inglés:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;eng
We&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;localizable&lt;span class="w"&gt; &lt;/span&gt;strings
Hi&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Goodbye&lt;span class="w"&gt; &lt;/span&gt;noroute2host
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como puedes ver, las cadenas se imprimen en un idioma diferente en función de la selección del usuario. Así que ¡objetivo&amp;nbsp;conseguido!&lt;/p&gt;
&lt;h3 id="actualizando-la-localizacion"&gt;Actualizando la&amp;nbsp;localización&lt;/h3&gt;
&lt;p&gt;Todo lo anterior es estupendo, pero ¿y si necesito añadir nuevos mensajes a mi aplicación? Pues la respuesta es que &lt;code&gt;gettext&lt;/code&gt; también tiene herramientas para actualizar los ficheros de traducción. Así que voy a contarte como&amp;nbsp;hacerlo.&lt;/p&gt;
&lt;h4 id="incluir-nuevos-mensajes-en-la-aplicacion"&gt;Incluir nuevos mensajes en la&amp;nbsp;aplicación&lt;/h4&gt;
&lt;p&gt;En primer lugar, vamos a añadir un nuevo mensaje a la función &lt;code&gt;imprimir_cadenas()&lt;/code&gt;. En concreto será la línea &lt;code&gt;print(_("Soy una nueva cadena localizable"))&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gettext&lt;/span&gt;

&lt;span class="n"&gt;appname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;localedir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;locales&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para seleccionar el idioma&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Seleccion de Idioma&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;esp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Por favor introduce esp o eng: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para imprimir cadenas&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="actualizando-el-fichero-de-platilla-pot"&gt;Actualizando el fichero de platilla&amp;nbsp;(.pot)&lt;/h4&gt;
&lt;p&gt;La actualización del fichero de plantilla consiste simplemente en volver a generarlo. Para ello usaremos exactamente el mismo comando que antes, salvo por el detalle de que ahora el fichero de entrada es el de la versión que tenemos con la selección del&amp;nbsp;usuario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pygettext3&lt;span class="w"&gt; &lt;/span&gt;-debug&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;noroute2host&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto volverá a generar el fichero &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/noroute2host.pot?ref_type=heads"&gt;locales/noroute2host.pot&lt;/a&gt; donde ya se encontrará el nuevo mensaje para&amp;nbsp;localizar. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-29 20:01+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:20&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:21&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:22&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:23&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="regenerando-los-ficheros-de-localizacion-po-a-partir-del-nuevo-fichero-de-plantilla-pot"&gt;Regenerando los ficheros de localización (.po) a partir del nuevo fichero de plantilla&amp;nbsp;(.pot)&lt;/h4&gt;
&lt;p&gt;Ahora que ya se ha actualizado el fichero de plantilla con la lista actualizada de mensajes localizables llega el momento de &lt;em&gt;mezclar&lt;/em&gt; la nueva plantilla con los ficheros de localización previos. Para este cometido existe la utilidad &lt;code&gt;msgmerge&lt;/code&gt; que permite hacer esta mezcla para incorporar los nuevos mensajes localizables a los ficheros &lt;code&gt;.po&lt;/code&gt; previos que ya contienen la traducción de los mensajes anteriores sin que esta se&amp;nbsp;pierda.&lt;/p&gt;
&lt;p&gt;El uso de &lt;code&gt;msgmerge&lt;/code&gt; sería de la siguiente&amp;nbsp;manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;msgmerge&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;FICHERO_PO_A_ACTUALIZAR&lt;span class="w"&gt; &lt;/span&gt;FICHERO_POT_PLANTILLA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Otra vez hay que tener en cuenta que hay que ejecutar esto para los dos ficheros &lt;code&gt;.po&lt;/code&gt;, uno por idioma, que estamos&amp;nbsp;usando.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;msgmerge&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.po&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot&lt;span class="w"&gt; &lt;/span&gt;
......&lt;span class="w"&gt; &lt;/span&gt;terminado.
$&lt;span class="w"&gt; &lt;/span&gt;msgmerge&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.po&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host.pot
......&lt;span class="w"&gt; &lt;/span&gt;terminado.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El resultado serán los nuevos ficheros &lt;code&gt;.po&lt;/code&gt; en los que ya se puede añadir la traducción de los nuevos mensajes. En este caso, los presento ya con la traducción del nuevo mensaje&amp;nbsp;incluida.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fichero de localización al español: &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/es/LC_MESSAGES/noroute2host.po?ref_type=heads"&gt;locales/es/LC_MESSAGES/noroute2host.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-29 20:01+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language:&lt;/span&gt;&lt;span class="s"&gt; \n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:20&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:21&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:22&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:23&lt;/span&gt;
&lt;span class="kt"&gt;#, fuzzy&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Fichero de localización al inglés: &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/en/LC_MESSAGES/noroute2host.po?ref_type=heads"&gt;locales/en/LC_MESSAGES/noroute2host.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-29 20:01+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language:&lt;/span&gt;&lt;span class="s"&gt; \n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:20&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;We are localizable strings&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:21&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hi noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:22&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adios noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Goodbye noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:23&lt;/span&gt;
&lt;span class="kt"&gt;#, fuzzy&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;I am a new localizable string&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="recompilando-los-ficheros-po-para-regenerar-los-ficheros-binarios-mo"&gt;ReCompilando los ficheros .po para ReGenerar los ficheros binarios&amp;nbsp;.mo&lt;/h4&gt;
&lt;p&gt;Ya solo nos quedaría la última operación que consiste simplemente en volver a generar los ficheros compilados &lt;code&gt;.mo&lt;/code&gt; a partir de ficheros &lt;code&gt;.po&lt;/code&gt;. Además, como dato curioso se ha dado el caso de que ha añadido el nuevo mensaje como &lt;em&gt;fuzzy&lt;/em&gt;. No tengo muy claro porque ocurre esto a veces, pero es por esto mismo que os recomendar la opción &lt;code&gt;-f&lt;/code&gt; del comando &lt;code&gt;msgfmt&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.mo&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host.po
$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.mo&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host.po
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="probando-de-nuevo-la-aplicacion-localizada-con-el-nuevo-texto"&gt;Probando de nuevo la aplicación localizada con el nuevo&amp;nbsp;texto.&lt;/h4&gt;
&lt;p&gt;Con lo anterior realizado, nuestra aplicación ya contiene el nuevo mensaje y sus versiones traducidas. Así que solo queda ejecutarla y comprobar que todo funciona&amp;nbsp;correctamente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;Español:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;esp
Somos&lt;span class="w"&gt; &lt;/span&gt;cadenas&lt;span class="w"&gt; &lt;/span&gt;localizables
Hola&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Adios&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Soy&lt;span class="w"&gt; &lt;/span&gt;una&lt;span class="w"&gt; &lt;/span&gt;nueva&lt;span class="w"&gt; &lt;/span&gt;cadena&lt;span class="w"&gt; &lt;/span&gt;localizable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;Inglés:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;eng
We&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;localizable&lt;span class="w"&gt; &lt;/span&gt;strings
Hi&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Goodbye&lt;span class="w"&gt; &lt;/span&gt;noroute2host
I&lt;span class="w"&gt; &lt;/span&gt;am&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;localizable&lt;span class="w"&gt; &lt;/span&gt;string
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="bonus-track-buenas-practicas-y-recomendaciones-personales"&gt;Bonus Track: Buenas prácticas y recomendaciones&amp;nbsp;personales&lt;/h2&gt;
&lt;p&gt;Lo cierto es que ha quedado un artículo un poco largo, pero no quiero terminarlo sin dejar algunas recomendaciones personales o comentar alguna buena&amp;nbsp;práctica.&lt;/p&gt;
&lt;h3 id="definir-las-cadenas-con-nombres-representativos-no-con-la-traduccion-del-idioma-por-defecto"&gt;Definir las cadenas con nombres representativos, no con la traducción del idioma por&amp;nbsp;defecto.&lt;/h3&gt;
&lt;p&gt;Reconozco que esta primera recomendación es algo bastante personal, pero en conjunción con la siguiente clarifica y facilita mucho el trabajo con las cadenas a localizar y su&amp;nbsp;gestión.&lt;/p&gt;
&lt;p&gt;Si observamos la estructura de los ficheros &lt;code&gt;.pot&lt;/code&gt; y &lt;code&gt;.po&lt;/code&gt; podemos ver que cada &lt;em&gt;mensaje&lt;/em&gt; localizable tiene un &lt;em&gt;msgid&lt;/em&gt; y un &lt;em&gt;msgstr&lt;/em&gt;. Por defecto el &lt;em&gt;msgid&lt;/em&gt; se rellena con la cadena que se le pasa a la función &lt;code&gt;_()&lt;/code&gt;. Y además el &lt;em&gt;msgid&lt;/em&gt; es lo que se muestra por defecto en la aplicación si no hay una localización (un fichero .mo) específico para el lenguaje que se quiera usar. Esto puede ser un punto positivo a favor de usar directamente las cadenas del lenguaje &amp;#8220;base&amp;#8221; como &lt;em&gt;msgid&lt;/em&gt; pues siempre mostrará un texto real en algún&amp;nbsp;idioma.&lt;/p&gt;
&lt;p&gt;Aún con esto, los ficheros quedan un poco raros en el lenguaje base teniendo &lt;em&gt;msgid&lt;/em&gt; igual a &lt;em&gt;msgstr&lt;/em&gt;, a mi gusto, y puede hacer que en los &lt;em&gt;msgid&lt;/em&gt; haya caracteres especiales. Es por esto que yo uso una nomenclatura alternativa para los &lt;em&gt;msgid&lt;/em&gt;: los defino como si fueran nombres de variables, usando solo números y letras pero usando el signo &lt;code&gt;-&lt;/code&gt; para separar palabras. Por ejemplo, en vez de usar la&amp;nbsp;línea:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Usaría la&amp;nbsp;siguiente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este cambio haría que al generar el fichero &lt;strong&gt;noroute2host.pot&lt;/strong&gt;, las entradas para esta cadena fueran las&amp;nbsp;siguientes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;#: 0029_localizacion-python-API-basada-clases-multi.py:21&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y ya, tal y como hemos explicado antes, en los &lt;em&gt;msgstr&lt;/em&gt; ficheros &lt;code&gt;.po&lt;/code&gt; de cada idioma se incluyen las&amp;nbsp;traducciones.&lt;/p&gt;
&lt;p&gt;La única pega es que, en caso de no encontrar una localización válida, se mostraría &lt;em&gt;&amp;#8220;hola-noroute2host&amp;#8221;&lt;/em&gt;. Para evitar esto, mi recomendación es controlar si hay o no una traducción disponible, y en caso de no haberla fijar un idioma por defecto. En mi opinión, esta manera de definir las cadenas localizables en combinación con la siguiente recomendación ayuda a trabajar de una manera más fácil y ágil con gettext en&amp;nbsp;Python.&lt;/p&gt;
&lt;h3 id="unifica-tus-cadenas-en-un-fichero"&gt;Unifica tus cadenas en un&amp;nbsp;fichero&lt;/h3&gt;
&lt;p&gt;Una cosa que me no me ha gustado al trabajar con &lt;code&gt;gettext&lt;/code&gt; es que las cadenas quedan un poco desperdigadas por los diferentes ficheros &lt;code&gt;.py&lt;/code&gt;. Aunque esto no es raro cuando estás programando en Python, e incluso puedes pasarle varios ficheros o un directorio de ficheros a &lt;code&gt;pygettext3&lt;/code&gt; o &lt;code&gt;xgettext&lt;/code&gt;, si me parece un problema cuando quieres cambiar algún texto. O incluso cuando simplemente quieres ver si se han extraído todas las cadenas&amp;nbsp;localizables.&lt;/p&gt;
&lt;p&gt;Así que para mejorar esto te propongo algo: ¿por qué no crear un fichero &lt;code&gt;.py&lt;/code&gt; donde se definan todas las cadenas como variables o constantes?. De este modo una vez generado el fichero de cadenas de texto, lo único que necesitas es importarlo en el resto de tus ficheros y referenciar las variables o constantes que hayas definido. De esto modo, solo tendrás un fichero donde definir tus cadenas de texto y el trabajo con ellas se facilitará mucho. Incluso puedes definir con comentarios secciones paras las diferentes cadenas. En mi opinión, esto facilita muchísimo el trabajo con las cadenas. Trabajas solo con un fichero &lt;code&gt;.py&lt;/code&gt; para los comandos de &lt;code&gt;gettext&lt;/code&gt;, si te ayudan traductores solo manejaras un fichero &lt;code&gt;.pot&lt;/code&gt; y &lt;code&gt;.po&lt;/code&gt;, y por supuesto la lista de cadenas localizables no está repartida por toda tu&amp;nbsp;aplicación.&lt;/p&gt;
&lt;p&gt;Esto es puramente una opinión personal, no se si es porque he trabajado con algún framework en otros lenguajes y hacían algo similar o porque es que así lo veo mucho más claro. En cualquier caso, en combinación con la anterior recomendación facilita mucho el trabajo con gettext. De hecho la &amp;#8220;trampita&amp;#8221; que hemos hecho para preguntar el idioma y toda la definición de cadenas irá al fichero propio de definición de cadenas, dejando nuestro código mucho más claro y limpio. Así que os dejo los cambios y el resultado a&amp;nbsp;continuación.&lt;/p&gt;
&lt;p&gt;Lo primero a mostrar serán nuestros dos ficheros &lt;code&gt;.py&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/definicion_cadenas.py?ref_type=heads"&gt;definicion_cadenas.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gettext&lt;/span&gt;

&lt;span class="n"&gt;appname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;noroute2host-defstr&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;localedir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;locales&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para seleccionar el idioma&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Seleccion de Idioma&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;esp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Por favor introduce esp o eng: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eng&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;localedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;es&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Se instalan las traducciones&lt;/span&gt;
&lt;span class="n"&gt;preguntar_idioma&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Definicion de cadenas localizables&lt;/span&gt;

&lt;span class="n"&gt;STR_CADENAS_LOCALIZABLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somos-cadenas-localizables&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STR_HOLA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STR_ADIOS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;adios-noroute2host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;STR_NUEVA_CADENA_LOCALIZABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nueva-cadena-localizable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/0029_localizacion-python-API-basada-clases-multi-con-fichero-cadenas.py?ref_type=heads"&gt;0029_localizacion-python-&lt;span class="caps"&gt;API&lt;/span&gt;-basada-clases-multi-con-fichero-cadenas.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;definicion_cadenas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;defstr&lt;/span&gt;

&lt;span class="c1"&gt;# Funcion para imprimir cadenas&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defstr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STR_CADENAS_LOCALIZABLES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defstr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STR_HOLA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defstr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STR_ADIOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defstr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STR_NUEVA_CADENA_LOCALIZABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;imprimir_cadenas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A continuación vamos a generar un nuevo conjunto de ficheros &lt;code&gt;.pot&lt;/code&gt; y &lt;code&gt;.po&lt;/code&gt; a los que les hemos añadido &lt;strong&gt;-defstr&lt;/strong&gt; en el nombre para diferenciarlos de los anteriores&amp;nbsp;ejemplos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creación de fichero de plantilla .pot&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pygettext3&lt;span class="w"&gt; &lt;/span&gt;-debug&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;noroute2host-defstr&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host-defstr.pot&lt;span class="w"&gt; &lt;/span&gt;definicion_cadenas.py&lt;span class="w"&gt; &lt;/span&gt;
Working&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;definicion_cadenas.py

&lt;span class="c1"&gt;# Creación inicial de nuevos ficheros .po&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host-defstr.pot&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host-defstr.po
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;locales/noroute2host-defstr.pot&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host-defstr.po
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y posteriormente se crean las traducciones de cada idioma (español e inglés) en los ficheros &lt;code&gt;.po&lt;/code&gt;. Este es el resultado final de los ficheros &lt;code&gt;.pot&lt;/code&gt; y &lt;code&gt;.po&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/noroute2host-defstr.pot?ref_type=heads"&gt;locales/noroute2host-defstr.pot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-31 17:36+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;


&lt;span class="kd"&gt;#: definicion_cadenas.py:23&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;somos-cadenas-localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:24&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:25&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;adios-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:26&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;nueva-cadena-localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/es/LC_MESSAGES/noroute2host-defstr.po?ref_type=heads"&gt;locales/es/LC_MESSAGES/noroute2host-defstr.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-31 17:36+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;


&lt;span class="kd"&gt;#: definicion_cadenas.py:23&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;somos-cadenas-localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Somos cadenas localizables&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:24&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hola noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:25&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;adios-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Adiós noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:26&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;nueva-cadena-localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Soy una nueva cadena localizable&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0029_localizacion-aplicaciones-python/locales/en/LC_MESSAGES/noroute2host-defstr.po?ref_type=heads"&gt;locales/en/LC_MESSAGES/noroute2host-defstr.po&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SOME DESCRIPTIVE TITLE.&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (C) YEAR ORGANIZATION&lt;/span&gt;
&lt;span class="c1"&gt;# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, YEAR.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Project-Id-Version:&lt;/span&gt;&lt;span class="s"&gt; PACKAGE VERSION\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;POT-Creation-Date:&lt;/span&gt;&lt;span class="s"&gt; 2024-07-31 17:36+0200\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;PO-Revision-Date:&lt;/span&gt;&lt;span class="s"&gt; YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Last-Translator:&lt;/span&gt;&lt;span class="s"&gt; FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Language-Team:&lt;/span&gt;&lt;span class="s"&gt; LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;MIME-Version:&lt;/span&gt;&lt;span class="s"&gt; 1.0\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Type:&lt;/span&gt;&lt;span class="s"&gt; text/plain; charset=UTF-8\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Content-Transfer-Encoding:&lt;/span&gt;&lt;span class="s"&gt; 8bit\n&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="py"&gt;Generated-By:&lt;/span&gt;&lt;span class="s"&gt; pygettext.py 1.5\n&amp;quot;&lt;/span&gt;


&lt;span class="kd"&gt;#: definicion_cadenas.py:23&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;somos-cadenas-localizables&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;We are localizable string&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:24&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hola-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hi noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:25&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;adios-noroute2host&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Goodbye noroute2host&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;#: definicion_cadenas.py:26&lt;/span&gt;
&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;nueva-cadena-localizable&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;I am a new localizable string&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finalmente solo nos queda compilar nuestros ficheros &lt;code&gt;.po&lt;/code&gt; para generar los ficheros &lt;code&gt;.mo&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host-defstr.mo&lt;span class="w"&gt; &lt;/span&gt;locales/es/LC_MESSAGES/noroute2host-defstr.po
$&lt;span class="w"&gt; &lt;/span&gt;msgfmt&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host-defstr.mo&lt;span class="w"&gt; &lt;/span&gt;locales/en/LC_MESSAGES/noroute2host-defstr.po
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por último, lo único que queda es comprobar que el resultado de la ejecución sigue siendo el&amp;nbsp;mismo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;español:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi-con-fichero-cadenas.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;esp
Somos&lt;span class="w"&gt; &lt;/span&gt;cadenas&lt;span class="w"&gt; &lt;/span&gt;localizables
Hola&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Adiós&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Soy&lt;span class="w"&gt; &lt;/span&gt;una&lt;span class="w"&gt; &lt;/span&gt;nueva&lt;span class="w"&gt; &lt;/span&gt;cadena&lt;span class="w"&gt; &lt;/span&gt;localizable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Seleccionando&amp;nbsp;inglés:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;0029_localizacion-python-API-basada-clases-multi-con-fichero-cadenas.py
Seleccion&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;Idioma
Por&lt;span class="w"&gt; &lt;/span&gt;favor&lt;span class="w"&gt; &lt;/span&gt;introduce&lt;span class="w"&gt; &lt;/span&gt;esp&lt;span class="w"&gt; &lt;/span&gt;o&lt;span class="w"&gt; &lt;/span&gt;eng:&lt;span class="w"&gt; &lt;/span&gt;eng
We&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;localizable&lt;span class="w"&gt; &lt;/span&gt;string
Hi&lt;span class="w"&gt; &lt;/span&gt;noroute2host
Goodbye&lt;span class="w"&gt; &lt;/span&gt;noroute2host
I&lt;span class="w"&gt; &lt;/span&gt;am&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;localizable&lt;span class="w"&gt; &lt;/span&gt;string
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como se puede observar el funcionamiento del programa es el mismo, pero hemos mejorado la estructura y la claridad de nuestro&amp;nbsp;código. &lt;/p&gt;
&lt;h3 id="anade-los-ficheros-binarios-mo-y-el-fichero-de-plantilla-pot-a-tu-gitignore"&gt;Añade los ficheros binarios (.mo) y el fichero de plantilla (.pot) a tu&amp;nbsp;.gitignore&lt;/h3&gt;
&lt;p&gt;Esta best-practice es simple, añade las extensiones &lt;code&gt;.mo&lt;/code&gt; y &lt;code&gt;.pot&lt;/code&gt; a tu fichero &lt;code&gt;.gitignore&lt;/code&gt;. Recuerda que los ficheros &lt;code&gt;.mo&lt;/code&gt; son compilados y no son código fuente, y que el fichero de plantilla &lt;code&gt;.pot&lt;/code&gt; tiene en los ficheros &lt;code&gt;.po&lt;/code&gt; específicos de cada idioma las versiones útiles del mismo. Aunque en el caso de los ficheros &lt;code&gt;.mo&lt;/code&gt; hay que tener en cuenta que si debes incluirlos en tus &lt;em&gt;releases&lt;/em&gt; puesto que si no están los textos no aparecerán&amp;nbsp;correctamente.&lt;/p&gt;
&lt;p&gt;Yo por mi parte estoy usando &lt;a href="https://www.toptal.com/developers/gitignore"&gt;gitignore.io&lt;/a&gt; para la generación de los ficheros &lt;em&gt;.gitignore&lt;/em&gt;. Este servicio te permite generar ficheros &lt;em&gt;.gitignore&lt;/em&gt; seleccionando tu sistema operativo, lenguajes de programación y otros elementos para generar un fichero de exclusiones para git fácilmente. Además de ser un servicio web, es software libre bajo licencia &lt;span class="caps"&gt;MIT&lt;/span&gt; y tiene disponible &lt;a href="https://github.com/toptal/gitignore.io"&gt;repositorio en github&lt;/a&gt;, por lo que te lo recomiendo sin&amp;nbsp;dudas.&lt;/p&gt;
&lt;h2 id="a-localizar-tus-aplicaciones"&gt;A localizar tus&amp;nbsp;aplicaciones&lt;/h2&gt;
&lt;p&gt;Como has podido observar, la localización de aplicaciones con &lt;code&gt;gettext&lt;/code&gt; no es una tarea sencilla, requiere de trabajo extra. Además te he propuesto algunas recomendaciones para facilitar y organizar mejor las cadenas localizables y los ficheros de traducción. Todos los ficheros que te he mostrado en este post están en el repositorio del blog &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/tree/main/0029_localizacion-aplicaciones-python?ref_type=heads"&gt;noroute2host-files&lt;/a&gt;. En el código de los ejemplos no he utilizado orientación a objetos por simplicidad, pero es fácilmente&amp;nbsp;adaptable.&lt;/p&gt;
&lt;p&gt;Por último, no puedo dejar de recordar que la localización e internacionalización de aplicaciones es más amplia que solo la traducción de cadenas. Permite jugar con los plurales, fechas o valores monetarios. Así que ya está en tu mano comenzar a localizar tus aplicaciones y aprovechar todas las posibilidades que &lt;code&gt;gettext&lt;/code&gt; nos&amp;nbsp;brinda.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/gettext.html"&gt;Documentación oficial de módulo de gettext para&amp;nbsp;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/gettext/"&gt;Sitio oficial de &lt;span class="caps"&gt;GNU&lt;/span&gt;&amp;nbsp;gettext&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files"&gt;Repositorio&amp;nbsp;noroute2host-files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.toptal.com/developers/gitignore"&gt;gitignore.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adrimcgrady.itch.io/happy-at-night"&gt;Feliz en la Noche, juego en PyGame que usa&amp;nbsp;gettext&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="gettext"></category><category term="desarrollo"></category><category term="localizacion"></category></entry><entry><title>Feliz en la Noche, mi primer juego en Python con PyGame para mi primera Game Jam</title><link href="https://noroute2host.com/feliz-en-la-noche-pygame-game-jam.html" rel="alternate"></link><published>2024-05-28T19:00:00+02:00</published><updated>2024-05-28T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2024-05-28:/feliz-en-la-noche-pygame-game-jam.html</id><summary type="html">&lt;p&gt;Con la idea de seguir practicando y mejorando con Python y aprender a realizar juegos con PyGame, decidí participar en mi primera Game Jam: la Indie Spain Jam 2023. El resultado fue &lt;em&gt;Feliz en la Noche&lt;/em&gt;, un juego desarrollado con PyGame-ce en una semana. Ahora publico el código fuente y os hablo sobre&amp;nbsp;él.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Con la idea de seguir practicando y mejorando con Python y aprender a realizar juegos con PyGame, decidí participar en mi primera Game Jam: la Indie Spain Jam 2023. El resultado fue &lt;em&gt;Feliz en la Noche&lt;/em&gt;, un juego desarrollado con PyGame-ce en una semana. Ahora publico el código fuente y os hablo sobre&amp;nbsp;él.&lt;/p&gt;
&lt;h2 id="feliz-en-la-noche"&gt;Feliz en la&amp;nbsp;Noche&lt;/h2&gt;
&lt;p&gt;Feliz en la Noche es un juego estilo Pang desarrollado para la Indie Spain Jam 2023. Los objetivos&amp;nbsp;eran:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Participar en mi primera Game&amp;nbsp;Jam.&lt;/li&gt;
&lt;li&gt;Aprender a trabajar con PyGame-ce y profundizar en Python mientras aprendía las bases de la programación de&amp;nbsp;juegos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El tema de la Game Jam era &lt;strong&gt;&amp;#8220;Cae la noche&amp;#8221;&lt;/strong&gt;. Después del &amp;#8220;brainstorming&amp;#8221; típico al inicio de una Game Jam, y después de empezar otro juego y tirarlo a la basura apareció esta idea, la de crear un juego al estilo Pang pero cambiando las bolas por soles. De modo que cuando el jugador acaba con un sol, la pantalla se va oscureciendo un&amp;nbsp;poco.&lt;/p&gt;
&lt;p&gt;El objetivo de cada nivel del juego es acabar con todos los soles para que la noche&amp;nbsp;caiga.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Feliz en la Noche Titulo" src="https://noroute2host.com/images/0028_feliz_en_la_noche_titulo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Puedes obtener más información, ver el resultado e incluso descargar versiones &lt;em&gt;empaquetadas&lt;/em&gt; de juego desde &lt;a href="https://adrimcgrady.itch.io/happy-at-night"&gt;su página de itch.io&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="historia-de-feliz-en-la-noche-aka-pang-nightfall"&gt;Historia de Feliz en la Noche (a.k.a Pang&amp;nbsp;Nightfall)&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Lucas es un antiguo astronauta de la Agencia Espacial Europea. El espacio era su casa, pero desde su jubilación siente una gran soledad en su interior. Solamente es feliz cuando &lt;strong&gt;&lt;span class="caps"&gt;CAE&lt;/span&gt; &lt;span class="caps"&gt;LA&lt;/span&gt; &lt;span class="caps"&gt;NOCHE&lt;/span&gt;&lt;/strong&gt; y puede contemplar el cielo nocturno. Observar las estrellas lo hace sonreír recordando los tiempos en los que fue un viajero entre&amp;nbsp;ellas.&lt;/p&gt;
&lt;p&gt;Pero ahora, unos traviesos soles han invadido sus tierras alumbrando todo a su paso e impidiendo contemplar la noche en todo su&amp;nbsp;esplendor.&lt;/p&gt;
&lt;p&gt;Usa el láser y ayuda a Lucas a acabar con sus enemigos. ¡Acabad con ellos para conseguir que la noche vuelva a caer de nuevo! Pero ten cuidado, cada vez que elimines a un enemigo, la noche ira&amp;nbsp;cayendo&amp;#8230;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Feliz en la Noche juego" src="https://noroute2host.com/images/0028_feliz_en_la_noche_juego.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="codigo-fuente-del-juego"&gt;Código fuente del&amp;nbsp;juego&lt;/h3&gt;
&lt;p&gt;Uno de objetivos del blog es divulgar el software libre. Al estar desarrollando el juego para una Game Jam, y más siendo la primera, no pude crear el repositorio y publicar el código fuente del juego en aquel momento. Pero ahora, unos meses más tarde, he preparado el repositorio en Gitlab, los ficheros &lt;code&gt;README.md&lt;/code&gt; y por fin, publicado el&amp;nbsp;código.&lt;/p&gt;
&lt;p&gt;Además, he aprovechado para preparar el juego para soportar traducciones y generado las dos primeras: Inglés y&amp;nbsp;Español.&lt;/p&gt;
&lt;p&gt;Tengo que reconocer que la idea era también hacer algunos artículos o algún tutorial de PyGame, pero creo que esto tendrá que esperar&amp;#8230; Así que si estás interesado puedes usar el &lt;a href="https://gitlab.com/adrimcgrady-pygames/pang-nightfall"&gt;repositorio de público de Feliz en la Noche&lt;/a&gt; para aprender Python y PyGame o más bien&amp;nbsp;PyGame-ce.&lt;/p&gt;
&lt;h2 id="desarrollando-videojuegos-con-pygame-ce"&gt;Desarrollando videojuegos con&amp;nbsp;PyGame-ce&lt;/h2&gt;
&lt;p&gt;Ahora que ya te he hablado del juego en sí, voy a contarte un poco más sobre PyGame y&amp;nbsp;PyGame-ce.&lt;/p&gt;
&lt;p&gt;PyGame es básicamente un conjunto de módulos Python para el desarrollo de videojuegos. Se podría decir que nos permite usar desde Python la conocidísima libreria &lt;code&gt;SDL&lt;/code&gt; a la vez que añade funcionalidades. Además, al estar usando Python, y dado que este es un lenguaje interpretado, permite que los juegos sean ejecutados en multitud de plataformas y sistemas&amp;nbsp;operativos.&lt;/p&gt;
&lt;p&gt;No se puede decir que PyGame sea realmente un motor gráfico para el desarrollo de juegos como pueden ser Godot, Unity o Unreal Engine. Esto hace que haya muchas cosas que un motor gráfico ya te resuelve de manera más o menos automática y que en PyGame tienes que hacerlo tú. Por ejemplo, una cosa tan simple como animar un personaje cambiando el sprite tienes que codificarla tú. Esto podría ser su punto débil, pero depende de lo que estés buscando. De hecho, esta misma situación puede verse como su punto fuerte, porque te permite aprender las bases del desarrollo de videojuegos. Así que lo que si puede ser PyGame es un buen punto de partida para aprender las bases y después saltar a algún motor&amp;nbsp;gráfico.&lt;/p&gt;
&lt;h3 id="pygame-vs-pygame-ce"&gt;PyGame vs&amp;nbsp;PyGame-ce&lt;/h3&gt;
&lt;p&gt;¿Pero por qué a veces estoy hablando de &lt;code&gt;PyGame&lt;/code&gt; y otras de &lt;code&gt;PyGame-ce&lt;/code&gt;? Esto se debe a que no hace mucho que la comunidad de PyGame y el responsable del repositorio del código tuvieron un conflicto. No conozco los detalles, pero parece que el origen viene de cierta dejadez del responsable a la hora de aceptar cambios, trabajar con la comunidad en mejoras y en general con ciertos problemas con el mantenimiento del PyGame original. Así que, llegado el momento, la mayor parte de los colaboradores de la comunidad decidieron seguir su propio camino y generar PyGame-ce. Esta versión cuenta con mejoras, mayor frecuencia de actualizaciones y de momento un desarrollo más activo. Así que se está imponiendo en la comunidad como la versión más recomendada para usar. De hecho, &lt;em&gt;Feliz en la Noche&lt;/em&gt; está desarrollado con&amp;nbsp;PyGame-ce.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://adrimcgrady.itch.io/happy-at-night"&gt;Página de Feliz en la Noche en&amp;nbsp;itch.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/adrimcgrady-pygames/pang-nightfall"&gt;Repositorio de Feliz en la Noche en&amp;nbsp;Gitlab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itch.io/jam/indie-spain-jam-23/entries"&gt;Todos los juegos de la Indie Spain Jam&amp;nbsp;23&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pyga.me"&gt;Página de&amp;nbsp;PyGame-ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pygame.org"&gt;Página de&amp;nbsp;PyGame&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="pygame"></category><category term="game jam"></category><category term="software libre"></category></entry><entry><title>Ordenación por número de versión en la terminal con sort y ls</title><link href="https://noroute2host.com/ordenacion-por-version-sort-y-ls.html" rel="alternate"></link><published>2024-02-24T21:00:00+01:00</published><updated>2024-02-24T21:00:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2024-02-24:/ordenacion-por-version-sort-y-ls.html</id><summary type="html">&lt;p&gt;Este post va a estar centrado en unas pequeñas opciones de los comandos &lt;code&gt;sort&lt;/code&gt; y &lt;code&gt;ls&lt;/code&gt;. Estas opciones de estos comandos nos van a permitir ordenar números de versiones. Es simplemente un pequeño truco que descubrí no hace mucho y me apetece&amp;nbsp;compartirlo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Este post va a estar centrado en unas pequeñas opciones de los comandos &lt;code&gt;sort&lt;/code&gt; y &lt;code&gt;ls&lt;/code&gt;. Estas opciones de estos comandos nos van a permitir ordenar números de versiones. Es simplemente un pequeño truco que descubrí no hace mucho y me apetece&amp;nbsp;compartirlo.&lt;/p&gt;
&lt;h2 id="los-comandos-sort-y-ls"&gt;Los comandos sort y&amp;nbsp;ls&lt;/h2&gt;
&lt;p&gt;Pero antes de nada hay que dar una breve pincelada sobre que son los comandos &lt;code&gt;sort&lt;/code&gt; y &lt;code&gt;ls&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Por si no los conoces (algo raro si has llegado a este lugar), son utilidades bastante básicas a la hora de trabajar con la&amp;nbsp;terminal.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En el caso de &lt;code&gt;sort&lt;/code&gt;, su función es ordenar las líneas de un archivo de texto o de la entrada estándar según el caso. Y eso es prácticamente todo lo que tengo que decir al respecto. Evidentemente tiene un montón de opciones que puedes consultar en su &lt;a href="https ://man7.org/linux/man-pages/man1/sort.1.html"&gt;página de man&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;En cuanto a &lt;code&gt;ls&lt;/code&gt;, su función es básicamente listar el contenido de un directorio. Por supuesto, también dispone de una ingente cantidad de opciones que también puedes revisar en su &lt;a href="https://man7.org/linux/man-pages/man1/ls.1.html"&gt;página de man&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ordenacion-de-versiones-con-el-comando-sort-usando-la-opcion-v-o-version-sort"&gt;Ordenación de versiones con el comando sort usando la opción &lt;code&gt;-V&lt;/code&gt; o &lt;code&gt;--version-sort&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;De todas las opciones del comando &lt;code&gt;sort&lt;/code&gt; voy a centrarme únicamente en una: &lt;code&gt;-V&lt;/code&gt; o &lt;code&gt;--version-sort&lt;/code&gt;. Y es que, hace poco, tuve que hacer un pequeño script que en algún punto necesitaba ordenar una lista de paquetes disponibles y realizar una operación con ella. Esta tesitura fue la que me hizo descubrir este pequeño truco que hoy comparto&amp;nbsp;contigo.&lt;/p&gt;
&lt;p&gt;La opción &lt;code&gt;-V&lt;/code&gt; o &lt;code&gt;--version-sort&lt;/code&gt;, como prefieras, te permite ordenar un conjunto de líneas tratándolas como versiones. Estas versiones pueden ser de código, de software o de algún tema similar. Parece una cuestión baladí, pero es muy&amp;nbsp;útil.&lt;/p&gt;
&lt;p&gt;Vamos a imaginar que tenemos la siguiente lista de ficheros que se corresponden con diferentes versiones de un software empaquetado en &lt;code&gt;rpm&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;migransoftware-2.22.rpm&lt;/li&gt;
&lt;li&gt;migransoftware-22.2.rpm&lt;/li&gt;
&lt;li&gt;migransoftware-2.2.2.rpm&lt;/li&gt;
&lt;li&gt;migransoftware-2.2.2.3.rpm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por tanto, este sería el fichero versiones.txt con el que vamos a trabajar como&amp;nbsp;demostración:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;migransoftware-2.22.rpm
migransoftware-22.2.rpm
migransoftware-2.2.2.rpm
migransoftware-2.2.2.3.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora veamos que pasa si usamos &lt;code&gt;sort&lt;/code&gt; para ordenar las lineas de este fichero de&amp;nbsp;versiones.&lt;/p&gt;
&lt;p&gt;En primer lugar, os muestro el resultado con la ordenación por defecto sin indicar ninguna opción. Como se puede observar, el resultado no es correcto. Por ejemplo, la versión 2.2.2 debería ser la primera, pero la salida no muestra&amp;nbsp;eso.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;versiones.txt&lt;span class="w"&gt; &lt;/span&gt;
migransoftware-2.2.2.3.rpm
migransoftware-2.2.2.rpm
migransoftware-2.22.rpm
migransoftware-22.2.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;¿Y si probamos con la opción &lt;code&gt;-n&lt;/code&gt; o &lt;code&gt;--numeric-sort&lt;/code&gt; que trata las líneas como valores numéricos? Pues vamos a ver que el resultado es igualmente&amp;nbsp;incorrecto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;versiones.txt
migransoftware-2.2.2.3.rpm
migransoftware-2.2.2.rpm
migransoftware-2.22.rpm
migransoftware-22.2.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora sí que sí, vamos a probar con la opción &lt;code&gt;-V&lt;/code&gt; o &lt;code&gt;--version-sort&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-V&lt;span class="w"&gt; &lt;/span&gt;versiones.txt
migransoftware-2.2.2.rpm
migransoftware-2.2.2.3.rpm
migransoftware-2.22.rpm
migransoftware-22.2.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como se puede observar el resultado es perfecto y la lista de opciones queda ordenada como&amp;nbsp;debe. &lt;/p&gt;
&lt;h2 id="ordenacion-de-versiones-con-el-comando-ls-usando-la-opcion-v"&gt;Ordenación de versiones con el comando ls usando la opción &lt;code&gt;-v&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Al igual que en el caso anterior, de la gran cantidad de versiones del comando &lt;code&gt;ls&lt;/code&gt;, voy a hablarte exclusivamente de la opción &lt;code&gt;v&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;De manera similar a lo descrito con &lt;code&gt;sort&lt;/code&gt;, y tal y como dice la documentación del comando &lt;code&gt;ls&lt;/code&gt;, la opción &lt;code&gt;-v&lt;/code&gt; &lt;em&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;ordena de modo natural los números de versión según el texto&amp;#8221;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Para mostrar su funcionamiento usaremos la misma lista de versiones que en los ejemplos anteriores. Pero en este caso, en vez de tener un fichero de versiones trabajaremos directamente con archivos en un directorio. Por supuesto, cada versión tendrá su propio archivo. De modo que si listamos el directorio donde están guardados los ficheros obtendremos lo siguiente (Se añade la opción &lt;code&gt;-1&lt;/code&gt; que modifica el comportamiento de &lt;code&gt;ls&lt;/code&gt; para mostrar un fichero por línea y que se vea con más&amp;nbsp;claridad):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1
migransoftware-2.2.2.3.rpm
migransoftware-2.2.2.rpm
migransoftware-2.22.rpm
migransoftware-22.2.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como se puede observar en la salida anterior, la ordenación no es correcta. ¿Pero que pasa si se usa la opción &lt;code&gt;-v&lt;/code&gt;? Pues que tendremos una lista correctamente ordenada según la versión como se puede apreciar a continuación (seguimos añadiendo la opción &lt;code&gt;-1&lt;/code&gt; para mayor&amp;nbsp;claridad):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;-v
migransoftware-2.2.2.rpm
migransoftware-2.2.2.3.rpm
migransoftware-2.22.rpm
migransoftware-22.2.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/sort.1.html"&gt;Página de man de&amp;nbsp;sort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/ls.1.html"&gt;Página de man de&amp;nbsp;ls&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="bash"></category><category term="linux"></category><category term="comandos"></category></entry><entry><title>Aplicaciones libres para Android. Índice.</title><link href="https://noroute2host.com/aplicaciones-libres-android.html" rel="alternate"></link><published>2023-06-22T19:00:00+02:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-06-22:/aplicaciones-libres-android.html</id><summary type="html">&lt;p&gt;Este post es un recopilatorio de todos los artículos publicados de la serie &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt;. En el encontrarás un índice y una pequeña descripción de todas las aplicaciones open source que sobre las que te he ido hablando. Además se convertirá en un buen recopilatorio si quieres empezar aplicaciones de código abierto en tu dispositivo o smartphone Android. Y por supuesto, se enlazará al post de cada aplicación para que puedas obtener un mayor detalle de manera rápida y&amp;nbsp;fácil.&lt;/p&gt;</summary><content type="html">&lt;h2 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h2&gt;
&lt;p&gt;Lo cierto es que la serie de artículos de &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; ha estado creciendo dentro del del blog. Así que llegados a este punto era necesario un artículo principal a modo de índice o recopilatorio para acceder fácilmente a todos los capítulos de la serie publicados en &lt;a href="https://noroute2host.com"&gt;noroute2host.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por tanto, este post es un recopilatorio de todos los artículos publicados de la serie &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt;. En el encontraras un índice y una pequeña descripción de todas las aplicaciones open source que sobre las que te he ido hablando. Además se convertirá en un buen recopilatorio si quieres empezar aplicaciones de código abierto en tu dispositivo o smartphone Android. Y por supuesto, se enlazará al post de cada aplicación para que puedas obtener un mayor detalle de manera rápida y&amp;nbsp;fácil.&lt;/p&gt;
&lt;h3 id="i-etar-calendario-open-source"&gt;&lt;a href="https://noroute2host.com/etar-calendario.html"&gt;I. Etar - Calendario Open&amp;nbsp;Source&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Etar es un calendario con licencia GPLv3 que cumple perfectamente y podemos instalar en nuestros dispositivos Android de manera muy sencilla. De hecho, Etar es simple y directamente una versión mejorada del calendario por defecto del proyecto &lt;span class="caps"&gt;AOSP&lt;/span&gt; (Android Open Source&amp;nbsp;Project).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Etar - Calendario Open Source" src="https://noroute2host.com/images/0009_etar_vista_calendario_360x800.png" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt; Etar - OpenSource&amp;nbsp;Calendar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artículo en el blog:&lt;/strong&gt; &lt;a href="https://noroute2host.com/etar-calendario.html"&gt;https://noroute2host.com/etar-calendario.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/Etar-Group/Etar-Calendar"&gt;https://github.com/Etar-Group/Etar-Calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/Etar-Group/Etar-Calendar"&gt;https://github.com/Etar-Group/Etar-Calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Calendar, Otros calendarios cloud o locales de los&amp;nbsp;fabricantes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/es/packages/ws.xsoh.etar"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar&amp;amp;hl=es"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ii-antennapod"&gt;&lt;a href="https://noroute2host.com/antennapod.html"&gt;&lt;span class="caps"&gt;II&lt;/span&gt;.&amp;nbsp;AntennaPod&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AntennaPod es un gestor y reproductor de podcasts brutal con licencia open source que podemos instalar en nuestros dispositivos Android de manera muy&amp;nbsp;sencilla.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AntennaPod" src="https://noroute2host.com/images/0012_antennapod_reproductor_360x800.png" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="ficha-de-la-app_1"&gt;Ficha de la&amp;nbsp;App&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;AntennaPod&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artículo en el blog:&lt;/strong&gt; &lt;a href="https://noroute2host.com/antennapod.html"&gt;https://noroute2host.com/antennapod.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://antennapod.org"&gt;https://antennapod.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/AntennaPod/AntennaPod"&gt;https://github.com/AntennaPod/AntennaPod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Podcasts, Spotify,&amp;nbsp;iVoox&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/app/de.danoeh.antennapod"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=de.danoeh.antennapod&amp;amp;hl=es"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="iii-aegis-authenticator"&gt;&lt;a href="https://noroute2host.com/aegis-authenticator.html"&gt;&lt;span class="caps"&gt;III&lt;/span&gt;. Aegis&amp;nbsp;Authenticator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aegis Authenticator es un gestor de tus tokens para los servicios con segundo factor de autenticación o &lt;span class="caps"&gt;2FA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Aegis" src="https://noroute2host.com/images/0016_aegis_menu.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="ficha-de-la-app_2"&gt;Ficha de la&amp;nbsp;App&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt; Aegis&amp;nbsp;Authenticator &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artículo en el blog:&lt;/strong&gt; &lt;a href="https://noroute2host.com/aegis-authenticator.html"&gt;https://noroute2host.com/aegis-authenticator.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://getaegis.app/"&gt;https://getaegis.app/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/beemdevelopment/Aegis"&gt;https://github.com/beemdevelopment/Aegis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Authenticator, Microsoft Authenticator, Authy,&amp;nbsp;andOTP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/en/packages/com.beemdevelopment.aegis"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="iv-markor"&gt;&lt;a href="https://noroute2host.com/markor.html"&gt;&lt;span class="caps"&gt;IV&lt;/span&gt;.&amp;nbsp;Markor&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Markor se define a él mismo como un editor de textos, notas y ToDo para Android que soporta Markdown, todo.txt, Zim. No está disponible en Google Play, pero sí lo tienes en&amp;nbsp;F-Droid.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Markor" src="https://noroute2host.com/images/0019_markor_editor_360x800.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="ficha-de-la-app_3"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;Markor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artículo en el blog:&lt;/strong&gt; &lt;a href="https://noroute2host.com/markor.html"&gt;https://noroute2host.com/markor.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/gsantner/markor#readme"&gt;https://github.com/gsantner/markor#readme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/gsantner/markor"&gt;https://github.com/gsantner/markor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt; Apache&amp;nbsp;2.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Keep, Evernote, o cualquier otro editor de texto, notas o&amp;nbsp;Markdown&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/packages/net.gsantner.markor/"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="v-ning"&gt;&lt;a href="https://noroute2host.com/ning-escaner-de-red.html"&gt;V.&amp;nbsp;Ning&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ning es un ligero escáner de red local al estilo Fing que te permite descubrir equipos y servicios en tu red&amp;nbsp;local.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ning" src="https://noroute2host.com/images/0024_ning_lista_dispositivos.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="ficha-de-la-app_4"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;Ning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artículo en el blog:&lt;/strong&gt; &lt;a href="https://noroute2host.com/ning-escaner-de-red.html"&gt;https://noroute2host.com/ning-escaner-de-red.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/csicar/Ning"&gt;https://github.com/csicar/Ning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/csicar/Ning"&gt;https://github.com/csicar/Ning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt;&amp;nbsp;Fing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/en/packages/de.csicar.ning/"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.zuccaro.ning"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="android"></category><category term="software libre"></category><category term="open source"></category><category term="android"></category></entry><entry><title>Bots en Telegram (I): Creación de un bot de Telegram</title><link href="https://noroute2host.com/creat-bot-telegram.html" rel="alternate"></link><published>2023-06-16T19:15:00+02:00</published><updated>2023-06-16T19:15:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-06-16:/creat-bot-telegram.html</id><summary type="html">&lt;p&gt;Empezamos una nueva serie de artículos relacionados con los bots de Telegram. En mi opinión, son una herramienta muy útil para integrar con nuestros scripts o programas para que nos informen de incidencias, el estado de funcionamiento de un servicio o cualquier cosa que se nos ocurra. En este primer capítulo, vamos a empezar por lo más básico: Crear un bot de&amp;nbsp;Telegram.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Empezamos una nueva serie de artículos relacionados con los bots de Telegram. En mi opinión, son una herramienta muy útil para integrar con nuestros scripts o programas para que nos informen de incidencias, el estado de funcionamiento de un servicio o cualquier cosa que se nos ocurra. En este primer capítulo, vamos a empezar por lo más básico: Crear un bot de Telegram. Y dejaré para los siguientes artículos otras cosas, como realizar diferentes acciones con ellos. La idea no es entrar en el detalle de todo lo que se puede hacer con un bot de Telegram porque sería casi infinito, pero sí mostrar como enviar mensajes desde bash o python por ejemplo. Es decir, os contaré como hacer que el bot os envíe mensajes pero ignoraré otras partes como conversar con el&amp;nbsp;bot.&lt;/p&gt;
&lt;h2 id="bots-en-telegram"&gt;Bots en&amp;nbsp;Telegram&lt;/h2&gt;
&lt;p&gt;Los bots de telegram son, en esencia, pequeñas aplicaciones que corren completamente dentro de la propia aplicación de Telegram. Tienen muchas funcionalidades y campos de aplicación. Desde reemplazar completamente a webs enteras, a integrar diferentes servicios, pasando por juegos en &lt;span class="caps"&gt;HTML&lt;/span&gt;. Vamos, que las aplicaciones del os bots de Telegram llegan prácticamente a cualquier cosa que se nos ocurra. Y es precisamente por esto, por lo que que a lo largo de esta serie voy centrarme en la utilidad que personalmente a mí más me ayuda. Se trata de usarlo como sistema de notificación para mis scripts y programas. Es decir, usar un bot como un sistema de notificación asociado a mis scripts y&amp;nbsp;programas.&lt;/p&gt;
&lt;h3 id="crear-un-bot-de-telegram"&gt;Crear un bot de&amp;nbsp;Telegram&lt;/h3&gt;
&lt;p&gt;Pero este capítulo 1 trata de como crear un bot de Telegram, así que vamos a ello. No pienses que es nada muy complicado ni nada por el estilo porque vas a tener ayuda. En concreto, nos va ayudar un bot oficial de Telegram creado para este cometido: &lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="BotFather Avatar" src="https://noroute2host.com/images/0025_botfather.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Lo primero que tienes que hacer es abrir una conversación con &lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;. Da igual que lo hagas, desde la web, un cliente de escritorio o la aplicación para móviles. Si nunca has hablado con él tendrás que pulsar en &lt;em&gt;Iniciar&lt;/em&gt; para comenzar. En cuanto hagas esto último, BotFather te responderá con a las acciones que puedes&amp;nbsp;realizar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="BotFather acciones posibles" src="https://noroute2host.com/images/0025_iniciar_bot_father.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Como ya habrás imaginado, la creación del nuevo bot se realiza enviando la orden &lt;code&gt;/newbot&lt;/code&gt;. Y mediante un proceso interactivo, BotFather te irá pidiendo lo&amp;nbsp;siguiente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre del Bot&lt;/strong&gt;: Es lo que aparecerá en la lista de contactos y por todos lados en realidad. Puede ser cualquier cadena de texto conteniendo incluso espacios. Para este ejemplo, le daremos el siguiente nombre: &lt;em&gt;El Bot de noroute2host.com&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nombre de usuario&lt;/strong&gt;: Será el nombre de usuario del bot y se usará también en las menciones los enlaces t.me y la búsqueda. Para el que estamos creando será &lt;em&gt;noroute2host_bot&lt;/em&gt;. Aquí si hay que tener en cuenta lo siguiente:&lt;ul&gt;
&lt;li&gt;Que debe tener una longitud de 5 a 32&amp;nbsp;caracteres.&lt;/li&gt;
&lt;li&gt;Debe estar formado únicamente por letras, números y/o&amp;nbsp;&amp;#8220;_&amp;#8221;.&lt;/li&gt;
&lt;li&gt;Ha de terminar en &amp;#8220;bot&amp;#8221;. Aunque no distingue entre mayúsculas y minúsculas, por lo que podría terminar en bot o&amp;nbsp;Bot.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Una vez que se le pasan los datos a BotFather este te devuelve tanto, un enlace al bot que en este caso es &lt;a href="https://t.me/noroute2host_bot"&gt;https://t.me/noroute2host_bot&lt;/a&gt; como el Token del mismo. Este token debes guardarlo en lugar seguro y no compartirlo, pues es lo que va a servir como autorización para usar el bot. Cualquiera con el token puede hacer llamadas a la &lt;span class="caps"&gt;API&lt;/span&gt; de Telegram y usar el&amp;nbsp;bot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Telegram resultado newbot" src="https://noroute2host.com/images/0025_newbot.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Como puedes ver en las acciones que permite BotFather, estas son muy diversas. Van desde modificar el perfil del bot hasta eliminarlo. Aquí te dejo una lista de algunos de&amp;nbsp;ellas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/setname&lt;/code&gt; para cambiar el nombre al&amp;nbsp;bot.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/setdescription&lt;/code&gt; para cambiar la descripción de nuestro&amp;nbsp;bot.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/setabouttext&lt;/code&gt; para cambiar la información que aparece en el acerca&amp;nbsp;de.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/setuserpic&lt;/code&gt; para cambiar la imagen de perfil de nuestro&amp;nbsp;bot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Así que, por ejemplo, puedes cambiar la imagen de perfil del bot con &lt;code&gt;/setuserpic&lt;/code&gt; y enviar la imagen que quieras a la conversación de&amp;nbsp;BotFather.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Telegram botfather setuserpic" src="https://noroute2host.com/images/0025_enviar_avatar_bot.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y este sería el&amp;nbsp;resultado:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Perfil bot Telegram noroute2host_bot" src="https://noroute2host.com/images/0025_noroute2host_bot_perfil.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="obtener-tu-chatid"&gt;Obtener tu&amp;nbsp;chatID&lt;/h3&gt;
&lt;p&gt;Como ya te he contado como crear el bot, lo siguiente que necesitas para que tu bot te alerte con las notificaciones o mensajes que quieras, es obtener tu &lt;strong&gt;Chat &lt;span class="caps"&gt;ID&lt;/span&gt;&lt;/strong&gt;. Esto se puede hacer de varias maneras, pero te voy a contar la que para mí es más sencilla y además se ajusta más a lo que ya hemos visto en este&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;Para obtener tu chat id, vamos a usar otro bot &lt;a href="https://telegram.me/myidbot"&gt;@myidbot&lt;/a&gt;. En este caso diría que no es bot oficial, pero es tremendamente útil. La operativa es bastante similar a la que seguimos con&amp;nbsp;BotFather:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abrimos un chat con el&amp;nbsp;bot.&lt;/li&gt;
&lt;li&gt;Le enviamos la orden de inicio &lt;code&gt;/start&lt;/code&gt; si no se envía&amp;nbsp;automáticamente.&lt;/li&gt;
&lt;li&gt;Le pedimos el id con &lt;code&gt;/getid&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aquí está el proceso, esta vez desde un dispositivo&amp;nbsp;móvil:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Obtener Chat ID bot Telegram" src="https://noroute2host.com/images/0025_obtener_chatid.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="en-el-siguiente-capitulo"&gt;En el siguiente&amp;nbsp;capítulo&amp;#8230;&lt;/h3&gt;
&lt;p&gt;Con lo descrito en este post ya puedes crear tu bot de Telegram y obtener tu Chat &lt;span class="caps"&gt;ID&lt;/span&gt; para realizar el envío de mensajes. En el siguiente o los siguientes capítulos detallaré el envío de mensajes usando el bot desde varias fuentes como pueden ser &lt;code&gt;bash&lt;/code&gt; o &lt;code&gt;python&lt;/code&gt;. &lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots"&gt;Documentación oficial de bots de&amp;nbsp;Telegram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://telegram.me/myidbot"&gt;@myidbot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://t.me/noroute2host_bot"&gt;@noroute2host_bot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="telegram"></category><category term="bot"></category><category term="telegram"></category></entry><entry><title>Aplicaciones libres para Android (V): Ning</title><link href="https://noroute2host.com/ning-escaner-de-red.html" rel="alternate"></link><published>2023-06-10T12:30:00+02:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-06-10:/ning-escaner-de-red.html</id><summary type="html">&lt;p&gt;De vuelta con la serie de Aplicaciones libres para Android, hoy os traigo quizás la app más simple de las que han llegado a esta sección. Se trata de &lt;strong&gt;Ning&lt;/strong&gt;, un escáner de red local similar a Fing y que destaca por su simplicidad y&amp;nbsp;ligereza.&lt;/p&gt;</summary><content type="html">&lt;p&gt;De vuelta con la serie de Aplicaciones libres para Android, hoy os traigo quizás la app más simple de las que han llegado a esta sección. Se trata de &lt;strong&gt;Ning&lt;/strong&gt;, un escáner de red local similar a Fing y que destaca por su simplicidad y&amp;nbsp;ligereza.&lt;/p&gt;
&lt;p&gt;Pero antes de comenzar a hablar de Ning y como siempre hago al comienzo de estos artículos, os recuerdo que la intención de la sección es mostrar aplicaciones que sean software libre. El objetivo puede ser tan simple como compartirlas y darlas a conocer, o más complejo como ayudar a reemplazar de manera sencilla o con garantías otras apps que incorporan nuestros dispositivos Android por defecto, o simplemente que instalamos por que son las más conocidas pese a que sean&amp;nbsp;privativas.&lt;/p&gt;
&lt;h2 id="ning"&gt;Ning&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Ning_Banner" src="https://noroute2host.com/images/0024_ning_banner.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Como ya he adelantado, la quinta aplicación de la serie es Ning. Y se trata de un ligero escáner de red local al estilo Fing. Pero como siempre, lo primero es la ficha de la app y sus enlaces a F-Droid o Google&amp;nbsp;Play.&lt;/p&gt;
&lt;h3 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;Ning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/csicar/Ning"&gt;https://github.com/csicar/Ning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/csicar/Ning"&gt;https://github.com/csicar/Ning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt;&amp;nbsp;Fing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/en/packages/de.csicar.ning/"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.zuccaro.ning"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;/h3&gt;
&lt;p&gt;Las características de Ning son bastante simples. Es un escáner de tu red local y te permite descubrir equipos y servicios en tu red local. Por ejemplo, cuando quieres saber la ip de un equipo conectado a tu red, descubrir todos los equipos de la red wifi a la que estás conectado o encontrar servicios de red dentro de tu red local. Ning descubre los sistemas y servicios dentro de la red mediante las siguientes fuentes de&amp;nbsp;datos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ping a los&amp;nbsp;dispositivos.&lt;/li&gt;
&lt;li&gt;Descubrimiento de servicios de red mediante Avahi y&amp;nbsp;Bonjour.&lt;/li&gt;
&lt;li&gt;Tablas &lt;span class="caps"&gt;ARP&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Escaneo de puertos &lt;span class="caps"&gt;TCP&lt;/span&gt; y &lt;span class="caps"&gt;UDP&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además, también incorpora información de los dispositivos y sus fabricantes mediante identificación del proveedor. Normalmente esta característica suele estar basada en la dirección &lt;span class="caps"&gt;MAC&lt;/span&gt; de los sistemas descubiertos. Si sientes curiosidad por esto, puedes consultar como está formada una dirección &lt;span class="caps"&gt;MAC&lt;/span&gt; &lt;a href="https://en.wikipedia.org/wiki/MAC_address"&gt;aquí&lt;/a&gt;, o incluso el detalle de cómo es la parte que identifica al proveedor &lt;a href="https://en.wikipedia.org/wiki/Organizationally_unique_identifier"&gt;en este otro enlace&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="escanea-tu-red-de-manera-sencilla"&gt;Escanea tu red de manera&amp;nbsp;sencilla&lt;/h3&gt;
&lt;p&gt;El uso de Ning es tremendamente simple. En cuanto abres la aplicación puedes elegir la interfaz de red sobre la que iniciar el escaneo, y solo con hacer el gestor de deslizar hacia abajo para refrescar, comenzará el escaneo de la red&amp;nbsp;seleccionada.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ning Seleccion de Red" src="https://noroute2host.com/images/0024_ning_seleccion_red.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y una vez pasados unos segundos tendrás en tu pantalla todos los equipos y servicios de red de la red escaneada como puedes ver a continuación. Las &lt;span class="caps"&gt;XX&lt;/span&gt;:&lt;span class="caps"&gt;XX&lt;/span&gt;:&lt;span class="caps"&gt;XX&lt;/span&gt; que aparecen es porque he activado la única opción de la que dispone la aplicación: Ocultar las direcciones &lt;span class="caps"&gt;MAC&lt;/span&gt; de los equipos&amp;nbsp;encontrados.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ning Lista de dispositivos descubiertos" src="https://noroute2host.com/images/0024_ning_lista_dispositivos.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Por último, también puedes seleccionar cualquiera de los equipos descubiertos para una información más detallada que incluye una lista de los puertos abiertos y los servicios corriendo sobre ellos. Por ejemplo, aquí se puede comprobar lo que ha encontrado de mi Raspberry&amp;nbsp;Pi.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ning Información de dispositivo" src="https://noroute2host.com/images/0024_ning_informacion_dispositivo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y ya está, así de fácil es escanear tu red con Ning. Así que, si lo necesitas aquí tienes una sencilla y gran alternativa a tu&amp;nbsp;disposición.&lt;/p&gt;
&lt;h5 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h5&gt;
&lt;p&gt;Si quieres conocer otras &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; y te has quedado con ganas de más en &lt;a href="https://noroute2host.com/aplicaciones-libres-android.html"&gt;este enlace está el índice con todos los artículos de la&amp;nbsp;serie&lt;/a&gt;&lt;/p&gt;</content><category term="android"></category><category term="software libre"></category><category term="redes"></category><category term="escáner"></category><category term="app"></category></entry><entry><title>Cambiamos la apariencia: Adiós “bullseye”, Hola “bookworm”.</title><link href="https://noroute2host.com/cambios-apariencia-bookworm.html" rel="alternate"></link><published>2023-06-10T12:00:00+02:00</published><updated>2023-06-10T12:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-06-10:/cambios-apariencia-bookworm.html</id><summary type="html">&lt;p&gt;Este post va a ser una &lt;em&gt;rara avis&lt;/em&gt; dentro del blog. No se hablará de nada técnico, ni de software libre (bueno de esto un poquito), ni de nada de lo habitual. Esto es simplemente una entrada de celebración: Cambiamos el diseño. ¡Adiós y Gracias &lt;code&gt;bullseye&lt;/code&gt;! ¡Bienvenido &lt;code&gt;bookworm&lt;/code&gt;!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Este post va a ser una &lt;em&gt;rara avis&lt;/em&gt; dentro del blog. No se hablará de nada técnico, ni de software libre (bueno de esto un poquito), ni de nada de lo habitual. Esto es simplemente una entrada de celebración: Cambiamos el diseño. ¡Adiós y Gracias &lt;code&gt;bullseye&lt;/code&gt;! ¡Bienvenido &lt;code&gt;bookworm&lt;/code&gt;!&lt;/p&gt;
&lt;h2 id="cambiando-el-diseno-de-homeworld-a-emerald"&gt;Cambiando el diseño. De Homeworld a&amp;nbsp;Emerald.&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://noroute2host.com"&gt;noroute2host.com&lt;/a&gt; comenzó su andadura pública muy poquito después de que la versión 11 de Debian, bullseye, viese la luz de manera definitiva. Pero en realidad, su puesta en marcha coincidió practicamente con la salida de bullseye. Así que la apariencia del blog estaba inspirada en el tema principal de la distro recién salida del horno: &lt;em&gt;Homeworld&lt;/em&gt;. Por tanto, ¿qué mejor momento para darle otro aire al blog que justo cuando Debian 12 bookworm ve la&amp;nbsp;luz?.&lt;/p&gt;
&lt;p&gt;Seguimos con esta pequeña tradición homenajeando a una de las distros con más solera del panorama Linux y mudamos la piel para inspirar la apariencia del blog con el tema por defecto de Debian 12 bookworm: &lt;em&gt;Emerald&lt;/em&gt;. Este cambio implica pasar de los tonos azul oscuro, rojo y grís a otros verdes y azulados. Aunque se han dejado detalles en rojo y gris que son los colores históricos de&amp;nbsp;Debian.&lt;/p&gt;
&lt;p&gt;Pero en cuanto al contenido y al funcionamiento nada cambia. La idea sigue siendo la misma, devolver a la comunidad (sobre todo a la comunidad libre) una parte de lo que me da cada día y compartir lo que me apetezca con todo aquel que pase por estos lares. Sin fechas fijas, sin ritmo prefijado y sin&amp;nbsp;guías.&lt;/p&gt;
&lt;p&gt;Sin más, un saludo y no olvides seguir las cuentas del blog en las redes&amp;nbsp;sociales:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mastodon.social/@noroute2host"&gt;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/noroute2hostcom"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="noroute2host"></category><category term="noroute2host"></category><category term="bookworm"></category><category term="cambios"></category></entry><entry><title>Previsualización de ficheros Markdown en Vim en la terminal</title><link href="https://noroute2host.com/vim-previsualizacion-markdown.html" rel="alternate"></link><published>2023-05-29T17:30:00+02:00</published><updated>2023-05-29T17:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-05-29:/vim-previsualizacion-markdown.html</id><summary type="html">&lt;p&gt;En esta entrada vamos a ver como previsualizar tus ficheros markdown con vim. Y sí, en la misma terminal que estás usando para editar tus ficheros con vim, sin necesidad de abrir ningún navegador, ni tan siquiera de tener un entorno de escritorio funcionando. Únicamente usaremos, el plugin de Vim &lt;code&gt;preview-markdown.vim&lt;/code&gt; y el renderizador de markdown &lt;code&gt;MarkDown Renderer&lt;/code&gt;, también conocido como &lt;code&gt;mdr&lt;/code&gt;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En esta entrada vamos a ver como previsualizar tus ficheros markdown con vim. Y sí, en la misma terminal que estás usando para editar tus ficheros con vim, sin necesidad de abrir ningún navegador, ni tan siquiera de tener un entorno de escritorio funcionando. Únicamente usaremos, el plugin de Vim &lt;code&gt;preview-markdown.vim&lt;/code&gt; y el renderizador de markdown &lt;code&gt;MarkDown Renderer&lt;/code&gt;, también conocido como &lt;code&gt;mdr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Si buscas plugins de Vim previsualización de archivos Markdown o incluso de otro tipo de archivos en &lt;a href="https://vimawesome.com/"&gt;Vim Awesome&lt;/a&gt; observarás que hay bastantes plugins para llevar acabo esta tarea. Pero también te darás cuenta que la mayoría de ellos usan un navegador para mostrarte la previsualización de los ficheros. Y esto es algo que no me gusta nada por varios motivos. Para empezar, en mi caso no tengo entorno de escritorio corriendo en mis &lt;span class="caps"&gt;SBC&lt;/span&gt; como la Raspberry Pi o el NanoPi Fire 3, y suelo conectarme por &lt;span class="caps"&gt;SSH&lt;/span&gt; para trabajar y editar ficheros. Pero es que incluso si estuviera trabajando en un entorno gráfico o exportando las X, la previsualización en un navegador me haría quitar el foco de la terminal para previsualizar el&amp;nbsp;fichero.&lt;/p&gt;
&lt;h2 id="preview-markdownvim"&gt;preview-markdown.vim&lt;/h2&gt;
&lt;p&gt;Así que por todo lo que os he contado antes, os voy a hablar de &lt;code&gt;preview-markdown.vim&lt;/code&gt;. En realidad, es un plugin bastante sencillo de usar y utilizar como podéis comprobar en su &lt;a href="https://github.com/skanehira/preview-markdown.vim"&gt;Repositorio oficial de GitHub&lt;/a&gt; o en &lt;a href="https://vimawesome.com/plugin/preview-markdown-vim"&gt;su ficha en Vim Awesome&lt;/a&gt;. Lo unico importante ha tener en cuenta es que existe un pre-requisito indispensable que es la instalación de un renderizador de Markdown para la terminal de los que nos indican en la documentación como&amp;nbsp;soportados:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mdr&lt;/li&gt;
&lt;li&gt;glow&lt;/li&gt;
&lt;li&gt;mdcat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En mi caso he optado por el primero de ellos, &lt;code&gt;mdr&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="pre-requisito-instalacion-de-mdr"&gt;Pre-Requisito: Instalación de&amp;nbsp;mdr&lt;/h3&gt;
&lt;p&gt;La instalación de mdr es bastante sencilla. Puedes realizar la compilación del código fuente desde &lt;a href="https://github.com/MichaelMure/mdr"&gt;El repositorio de mdr en GitHub&lt;/a&gt; o descargar un binario precompilado desde la &lt;a href="https://github.com/MichaelMure/mdr/releases"&gt;sección de Releases&lt;/a&gt; del repositorio. Esta última opción es la que yo he escogido por simplicidad. En mi caso he escogido el binario precompilado de la versión 0.25 de mdr para &lt;span class="caps"&gt;ARM&lt;/span&gt; en Linux. Si tu arquitectura coincide, puedes descargarlo directamente desde &lt;a href="https://github.com/MichaelMure/mdr/releases/download/v0.2.5/mdr_linux_arm"&gt;aquí&lt;/a&gt;. En otro caso, tendrás que descargar el que se corresponda con tu sistema operativo y&amp;nbsp;arquitectura.&lt;/p&gt;
&lt;p&gt;Vamos a proceder a realizar la descarga directamente en /usr/local/bin para lo que necesitarás permisos de escritura en esa carpeta. Lo que probablemente te lleve a realizar la descarga con root (como es mi caso) o con sudo. Una vez descargado habrá que darle permisos de ejecución al binario y ya lo tendremos listo para&amp;nbsp;usarlo. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root@nanopifire3:~#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/
root@nanopifire3:/usr/local/bin#&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://github.com/MichaelMure/mdr/releases/download/v0.2.5/mdr_linux_arm
root@nanopifire3:/usr/local/bin#&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;mdr_linux_arm
root@nanopifire3:~#&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;mdr_linux_arm&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/mdr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="instalacion-de-preview-markdownvim"&gt;Instalación de&amp;nbsp;preview-markdown.vim&lt;/h3&gt;
&lt;p&gt;Lo único que nos falta tener en cuenta antes de realizar la instalación de &lt;code&gt;preview-markdown.vim&lt;/code&gt; es simplemente que tu versión de Vim debe ser mayor o igual que 8.1.1401. Puedes comprobar esto de esta manera tan&amp;nbsp;simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vim&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Así que, ya cumpliendo con los dos pre-requisitos, lo único que falta es proceder a la instalación del plugin. En este artículo, y puesto que es el gestor de plugins para Vim que yo uso, voy a dejarte como lo instalo con el gestor de plugins Plug. Pero si tienes cualquier otro, basta que lo hagas como normalmente. O si no sabes, en &lt;a href="https://vimawesome.com/plugin/preview-markdown-vim"&gt;la ficha del plugin en Vim Awesome&lt;/a&gt; vienen diferentes ejemplos para diferentes gestores de plugins. Sea como fuere, para el caso de &lt;code&gt;Plug&lt;/code&gt; se instala&amp;nbsp;así:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Añadimos la siguiente línea al fichero &lt;code&gt;~/.vimrc&lt;/code&gt; en la sección de&amp;nbsp;Plug:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;quot; CONFIGURACIÓN GESTOR DE COMPLEMENTOS PLUG&lt;/span&gt;
&lt;span class="err"&gt;call plug#begin(&amp;#39;~/.vim/plugged&amp;#39;)&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot; -------------------------------&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot; ... Otros plugins de vim por aquí...&lt;/span&gt;
&lt;span class="err"&gt;Plug &amp;#39;skanehira/preview-markdown.vim&amp;#39;, {&amp;#39;for&amp;#39;: &amp;#39;markdown&amp;#39;}&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot; -------------------------------&lt;/span&gt;
&lt;span class="err"&gt;call plug#end()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si no estás muy familiarizado con el gestor de complementos Plug, te puede resultar extraña la parte &lt;code&gt;{'for': 'markdown'}&lt;/code&gt;. Su función es que el plugin solo se cargue cuando estemos editando ficheros&amp;nbsp;markdown.&lt;/p&gt;
&lt;p&gt;Una vez editado el fichero de configuración de vim, lo que nos falta es realizar la instalación del complemento. Esto último se hace ejecutando la siguiente orden desde dentro del propio&amp;nbsp;Vim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:PlugInstall
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="previsualizando-tus-ficheros-markdown-directamente-en-vim"&gt;Previsualizando tus ficheros Markdown directamente en&amp;nbsp;Vim&lt;/h3&gt;
&lt;p&gt;Ahora que está instalado &lt;code&gt;preview-markdown.vim&lt;/code&gt;, solo queda empezar a usarlo para previsualizar tus ficheros markdown dentro de Vim directamente desde la terminal. A este respecto, solo puedo decirte que es tremendamente simple de usar. Basta con ejecutar el siguiente comando dentro de&amp;nbsp;Vim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:PreviewMarkdown [left|top|right|bottom|tab]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que implica cada opción de la previsualización es bastante fáciles de&amp;nbsp;adivinar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;left&lt;/code&gt;: Abrirá una nueva ventana de Vim a la izquierda para la&amp;nbsp;previsualización.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;top&lt;/code&gt;: Abrirá una nueva ventana de Vim arriba para la&amp;nbsp;previsualización.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;right&lt;/code&gt;: Abrirá una nueva ventana de Vim a la derecha para la&amp;nbsp;previsualización.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bottom&lt;/code&gt;: Abrirá una nueva ventana de Vim debajo para la&amp;nbsp;previsualización.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tab&lt;/code&gt;: En vez de usar una ventana nueva para la previsualización, usará una&amp;nbsp;pestaña.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Plugin preview-markdown.vim en funcionamiento" src="https://noroute2host.com/images/0022_previewmarkdown_001.png" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="opciones-adicionales-de-preview-markdownvim"&gt;Opciones adicionales de&amp;nbsp;preview-markdown.vim&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;preview-markdown.vim&lt;/code&gt; dispone de un par de opciones adicionales para usar añadiendo a tu fichero &lt;code&gt;~/.vimrc&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La primera es bastante autodescriptiva: &lt;code&gt;g:preview_markdown_parser&lt;/code&gt;. Su función es la de elegir el previsualizador o renderizador que se quiera. Por defecto es &lt;code&gt;mdr&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La segunda es muy interesante: &lt;code&gt;g:preview_markdown_auto_update&lt;/code&gt;. Sirve para que la previsualización se vaya actualizando tal y como se vaya escribiendo en el buffer (cuando ejecutes :w por ejemplo) sin tener que volver a ejecutar el comando&amp;nbsp;PreviewMarkdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para configurar esta última opción, que es la más interesante, deberás a añadir a tu fichero &lt;code&gt;~/.vimrc&lt;/code&gt; la siguiente&amp;nbsp;configuración:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot; CONFIGURACIÓN MARKDOWN PREVIEW
let g:preview_markdown_auto_update = 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="algunas-consideraciones-sobre-este-plugin-de-vim"&gt;Algunas consideraciones sobre este plugin de&amp;nbsp;Vim&lt;/h3&gt;
&lt;p&gt;Para finalizar me gustaría aclarar algunas cositas sobre el plugin &lt;code&gt;preview-markdown.vim&lt;/code&gt;. La primera de ellas es que desgraciadamente no está en desarrollo activo ya. Pero aún así lo he querido traer el blog porque no he encontrado otro similar con desarrollo activo. Y&amp;#8230; ¿quién sabe? Lo mismo alguién que esté leyendo esto se anima a hacer un fork y continuarlo. Porque la segunda cosa que tengo que aclarar es que tiene algunos detalles que pulir como, por ejemplo, que cada vez que recarga la previsualización te lleva al principio del documento. Esto último puede resultar bastante molesto, sobre todo cuando el documento sobre el que estás trabajando es&amp;nbsp;grande.&lt;/p&gt;
&lt;p&gt;En cualquier caso, creo que es un plugin de bastante utilidad y que te ayudará a la hora de trabajar con Vim y ficheros Markdown. Y además, a la vez que hemos hablado del plugin te he mostrado un render de Markdown para la terminal que también puede ser muy util para leer archivos Markdown directamente en la&amp;nbsp;terminal.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/skanehira/preview-markdown.vim"&gt;Repositorio oficial preview-markdown.vim en&amp;nbsp;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vimawesome.com/plugin/preview-markdown-vim"&gt;Ficha de preview-markdown.vim en Vim&amp;nbsp;Awesome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MichaelMure/mdr"&gt;El repositorio de mdr en&amp;nbsp;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MichaelMure/mdr/releases"&gt;Sección de Releases de mdr en&amp;nbsp;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="vim"></category><category term="markdown"></category><category term="linux"></category></entry><entry><title>Como hacer que bash no distinga entre mayúsculas y minúsculas</title><link href="https://noroute2host.com/bash-mayusculas-minusculas.html" rel="alternate"></link><published>2023-03-31T17:30:00+02:00</published><updated>2023-03-31T17:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-03-31:/bash-mayusculas-minusculas.html</id><summary type="html">&lt;p&gt;El objetivo de este post es tan simple como lo que dice su título. ¿Cómo hacer que bash no distinga entre mayúsculas o minúsculas? O lo que es lo mismo&amp;#8230; ¿Cómo hacer que bash trabaje en modo &lt;em&gt;case insensitive&lt;/em&gt;?&lt;/p&gt;</summary><content type="html">&lt;p&gt;El objetivo de este post es tan simple como lo que dice su título. ¿Cómo hacer que bash no distinga entre mayúsculas o minúsculas? O lo que es lo mismo&amp;#8230; ¿Cómo hacer que bash trabaje en modo &lt;em&gt;case insensitive&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Que bash trabaje en modo &lt;em&gt;case sensitive&lt;/em&gt; no es un capricho ni es algo exclusivo de la propia shell bash. ¿Y por qué? Pues la verdad es que no lo he hablado con Dennis Ritchie, Ken Thompson ni Linus Torvlads, pero si tengo una frase grabada de una de mis primeras prácticas de la asignatura de Sistemas Operativos en la&amp;nbsp;universidad:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;En Linux todo es un&amp;nbsp;fichero.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Y teniendo en cuenta que la inmensa mayoría de los sistemas de ficheros para Linux y Unix son case sensitive, o lo que es lo mismo, distinguen entre mayúsculas y minúsculas&amp;#8230; Que bash, la shell de la que estamos hablando distinga entre mayúsculas y minúsculas cae por su propio&amp;nbsp;peso.&lt;/p&gt;
&lt;p&gt;Pero, a veces, nos interesa que el comportamiento sea distinto para que trabaje de modo que no haya distinción entre mayúsculas y minúsculas. Por ejemplo, en el artículo &lt;a href="https://noroute2host.com/imagemagick-android.html"&gt;Tratamiento y conversión de imágenes con ImageMagick (&lt;span class="caps"&gt;II&lt;/span&gt;) Aplicación a densidades de pantalla de Android&lt;/a&gt; se buscan las imágenes originales en base a su extensión definida por el tipo de formato origen que se pasaba como parámetros. Por ejemplo, si bash trabajara en su modo &amp;#8220;normal&amp;#8221; los ficheros &lt;code&gt;.png&lt;/code&gt; y &lt;code&gt;.PNG&lt;/code&gt; no serían tratados igual. Lo que nos lleva al meollo de este&amp;nbsp;post.&lt;/p&gt;
&lt;h2 id="como-hacer-que-bash-no-distinga-entre-mayusculas-y-minusculas"&gt;¿Como hacer que bash no distinga entre mayúsculas y&amp;nbsp;minúsculas?&lt;/h2&gt;
&lt;p&gt;Antes de explicar como hacer que bash no haga distinción entre mayúsculas y minúsculas, voy a poner un ejemplo de su funcionamiento original (case&amp;nbsp;sensitive).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tenemos 2 ficheros de texto en nuestro directorio de pruebas, pero uno tiene la extensión en maýusculas&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;fichero*
fichero1.txt
fichero2.TXT

&lt;span class="c1"&gt;# Si listamos los ficheros *.txt solo nos aparece uno de ellos&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;*.txt
fichero1.txt

&lt;span class="c1"&gt;# Y lo mismo para el caso de *.TXT&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;*.TXT
fichero2.TXT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pero ¿y si queremos listar todos los ficheros de texto con extensión &lt;code&gt;txt&lt;/code&gt; sin importar si está en mayúsculas o minúsculas? Pues que en el caso de bash, existe la posibilidad de hacer que no distinga entre mayúsculas y minúsculas para el caso de ficheros y directorios. La responsable es la función incorporada de bash &lt;code&gt;shopt&lt;/code&gt;. Esta función permite activar o desactivar bastantes aspectos de bash que modifican como se comporta por defecto en diferentes aspectos. Su uso básico es bastante&amp;nbsp;simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Activar una opción&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;OPCIÓN

&lt;span class="c1"&gt;# Desactivar una opción&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;OPCIÓN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para la opción de la distinción entre mayúsculas y minúsculas, el nombre no es demasiado original: &lt;code&gt;nocaseglob&lt;/code&gt;. Así que para activar o desactivar la opción sería tan simple&amp;nbsp;como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Activar modo case-insensitive&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;nocaseglob

&lt;span class="c1"&gt;# Volver al modo case-sensitive&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;nocaseglob
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por tanto, si activamos la opción, y volvemos a listar los ficheros .txt del directorio de pruebas veremos como ahora si se listan todos independientemente de si su extensión está en minúsculas o&amp;nbsp;mayúsculas.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;nocaseglob

adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;*.txt
fichero1.txt
fichero2.TXT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="mostrar-el-estado-de-las-opciones-de-shopt"&gt;Mostrar el estado de las opciones de&amp;nbsp;shopt&lt;/h3&gt;
&lt;p&gt;Como he comentado antes shopt provee una gran cantidad de modificadores de comportamiento de la shell bash. Y podemos listar el estado de todas ellas en cualquier momento simplemente ejecutando la función incorporada &lt;code&gt;shopt&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;
autocd&lt;span class="w"&gt;          &lt;/span&gt;off
assoc_expand_once&lt;span class="w"&gt;   &lt;/span&gt;off
cdable_vars&lt;span class="w"&gt;     &lt;/span&gt;off
cdspell&lt;span class="w"&gt;         &lt;/span&gt;off
checkhash&lt;span class="w"&gt;       &lt;/span&gt;off
checkjobs&lt;span class="w"&gt;       &lt;/span&gt;off
checkwinsize&lt;span class="w"&gt;    &lt;/span&gt;on
cmdhist&lt;span class="w"&gt;         &lt;/span&gt;on
compat31&lt;span class="w"&gt;        &lt;/span&gt;off
compat32&lt;span class="w"&gt;        &lt;/span&gt;off
compat40&lt;span class="w"&gt;        &lt;/span&gt;off
compat41&lt;span class="w"&gt;        &lt;/span&gt;off
compat42&lt;span class="w"&gt;        &lt;/span&gt;off
compat43&lt;span class="w"&gt;        &lt;/span&gt;off
compat44&lt;span class="w"&gt;        &lt;/span&gt;off
complete_fullquote&lt;span class="w"&gt;  &lt;/span&gt;on
direxpand&lt;span class="w"&gt;       &lt;/span&gt;off
dirspell&lt;span class="w"&gt;        &lt;/span&gt;off
dotglob&lt;span class="w"&gt;         &lt;/span&gt;off
execfail&lt;span class="w"&gt;        &lt;/span&gt;off
expand_aliases&lt;span class="w"&gt;  &lt;/span&gt;on
extdebug&lt;span class="w"&gt;        &lt;/span&gt;off
extglob&lt;span class="w"&gt;         &lt;/span&gt;off
extquote&lt;span class="w"&gt;        &lt;/span&gt;on
failglob&lt;span class="w"&gt;        &lt;/span&gt;off
force_fignore&lt;span class="w"&gt;   &lt;/span&gt;on
globasciiranges&lt;span class="w"&gt; &lt;/span&gt;on
globstar&lt;span class="w"&gt;        &lt;/span&gt;off
gnu_errfmt&lt;span class="w"&gt;      &lt;/span&gt;off
histappend&lt;span class="w"&gt;      &lt;/span&gt;on
histreedit&lt;span class="w"&gt;      &lt;/span&gt;off
histverify&lt;span class="w"&gt;      &lt;/span&gt;off
hostcomplete&lt;span class="w"&gt;    &lt;/span&gt;on
huponexit&lt;span class="w"&gt;       &lt;/span&gt;off
inherit_errexit&lt;span class="w"&gt; &lt;/span&gt;off
interactive_comments&lt;span class="w"&gt;    &lt;/span&gt;on
lastpipe&lt;span class="w"&gt;        &lt;/span&gt;off
lithist&lt;span class="w"&gt;         &lt;/span&gt;off
localvar_inherit&lt;span class="w"&gt;    &lt;/span&gt;off
localvar_unset&lt;span class="w"&gt;  &lt;/span&gt;off
login_shell&lt;span class="w"&gt;     &lt;/span&gt;on
mailwarn&lt;span class="w"&gt;        &lt;/span&gt;off
no_empty_cmd_completion&lt;span class="w"&gt; &lt;/span&gt;off
nocaseglob&lt;span class="w"&gt;      &lt;/span&gt;off
nocasematch&lt;span class="w"&gt;     &lt;/span&gt;off
nullglob&lt;span class="w"&gt;        &lt;/span&gt;off
progcomp&lt;span class="w"&gt;        &lt;/span&gt;on
progcomp_alias&lt;span class="w"&gt;  &lt;/span&gt;off
promptvars&lt;span class="w"&gt;      &lt;/span&gt;on
restricted_shell&lt;span class="w"&gt;    &lt;/span&gt;off
shift_verbose&lt;span class="w"&gt;   &lt;/span&gt;off
sourcepath&lt;span class="w"&gt;      &lt;/span&gt;on
xpg_echo&lt;span class="w"&gt;        &lt;/span&gt;off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si, por el contrario, solo queremos listar es estado de una de las opciones la sintaxis sería la&amp;nbsp;siguiente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;NOMBRE_OPCIÓN&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, para ver el estado de la opción &lt;code&gt;nocaseglob&lt;/code&gt;, cambiar su comportamiento y volver a listar el estado sería así de&amp;nbsp;simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Comprobar el estado antes de cambiar la opción&lt;/span&gt;
adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nocaseglob
nocaseglob&lt;span class="w"&gt;      &lt;/span&gt;off

&lt;span class="c1"&gt;# Activar nocaseglob&lt;/span&gt;
adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;nocaseglob

&lt;span class="c1"&gt;# Volver a comprobar el estado de la opción nocaseglob&lt;/span&gt;
adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nocaseglob
nocaseglob&lt;span class="w"&gt;      &lt;/span&gt;on
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por último, solo añadir que las opciones y modificadores que se pueden activar y desactivar con &lt;code&gt;shopt&lt;/code&gt; son válidas mientras la sesión de la shell esté activa. Por lo que al salir de la sesión volverán a su estado por&amp;nbsp;defecto&lt;/p&gt;
&lt;p&gt;Puedes hacer permanente estos cambios añadiendo el comando &lt;code&gt;shopt&lt;/code&gt; al fichero &lt;code&gt;.bashrc&lt;/code&gt;, pero en el caso de &lt;code&gt;nocaseglob&lt;/code&gt; no te lo recomiendo porque puede afectar a scripts o comandos el sistema que se lancen dentro de una shell&amp;nbsp;bash.&lt;/p&gt;
&lt;p&gt;Es más, como ya he comentado la opción tiene validez para toda la sesión por lo que tienes que tener cuidado si no la desactivas cuando no la necesites. Por ejemplo, si la activas dentro de un script y no la desactivas, afectará a todo el script. Puedes ver un ejemplo de como activarla cuando sea necesario y desactivarla después en el script &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0018_imagemagick-android/0018_generar_drawables_dpi.sh"&gt;0018_generar_drawables_dpi.sh&lt;/a&gt; del que te hablo en el artículo &lt;a href="https://noroute2host.com/imagemagick-android.html"&gt;Tratamiento y conversión de imágenes con ImageMagick (&lt;span class="caps"&gt;II&lt;/span&gt;) Aplicación a densidades de pantalla de&amp;nbsp;Android&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A partir de aquí, ya te toca a ti seguir investigando las opciones disponibles en &lt;code&gt;shopt&lt;/code&gt; y ver como cambian el comportamiento por defecto de la shell bash. Aunque no descarto que aparezca por el blog en un futuro un post hablando de las opciones más comunes de &lt;code&gt;shopt&lt;/code&gt;&amp;#8230;&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html"&gt;Manual de Referencia de&amp;nbsp;Shopt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="bash"></category><category term="linux"></category><category term="unix"></category></entry><entry><title>El comando tee</title><link href="https://noroute2host.com/comando-tee-linux.html" rel="alternate"></link><published>2023-03-24T12:30:00+01:00</published><updated>2023-03-24T12:30:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-03-24:/comando-tee-linux.html</id><summary type="html">&lt;p&gt;Toca hablar de comandos útiles para la terminal en Linux o &lt;span class="caps"&gt;UNIX&lt;/span&gt;, y en concreto de uno en particular: &lt;code&gt;tee&lt;/code&gt;. Se trata de un comando cuyo funcionamiento es bastante simple, pero es directamente proporcional a su utilidad. Básicamente lo que hace es leer de la entrada estándar y escribir a la salida estándar y a uno o más&amp;nbsp;ficheros.&lt;/p&gt;</summary><content type="html">&lt;h2 id="el-comando-tee"&gt;El comando&amp;nbsp;tee&lt;/h2&gt;
&lt;p&gt;Toca hablar de comandos útiles para la terminal en Linux o &lt;span class="caps"&gt;UNIX&lt;/span&gt;, y en concreto de uno en particular: &lt;code&gt;tee&lt;/code&gt;. Se trata de un comando cuyo funcionamiento es bastante simple, pero es directamente proporcional a su utilidad. Básicamente lo que hace es leer de la entrada estándar y escribir a la salida estándar y a uno o más ficheros. ¿Y para que sirve esto? Pues para algo tan sencillo y útil, como por ejemplo, ver la salida de un comando o script a la vez que la estamos volcando a un&amp;nbsp;fichero.&lt;/p&gt;
&lt;h3 id="instalando-tee"&gt;Instalando&amp;nbsp;tee&lt;/h3&gt;
&lt;p&gt;Antes de explicar como se usa, lo primero que os tengo que decir que el comando suele venir en el paquete &lt;code&gt;coreutils&lt;/code&gt; de tu distro. Es un paquete de los básicos de todas las distribuciones. Así que lo normal es que lo tengas instalado, pero si no es tu caso, la forma de instalarlo sería&amp;nbsp;esta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Debian/Ubuntu &lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coreutils

&lt;span class="c1"&gt;# RedHat/Centos/OracleLinux&lt;/span&gt;
dnf&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coreutils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="usando-tee"&gt;Usando&amp;nbsp;tee&lt;/h3&gt;
&lt;p&gt;Como curiosidad os diré que, si no estoy mal informado, su nombre viene de lo que se ha llamado una T de tuberías de toda la vida. Y la verdad es que tiene todo el sentido del mundo porque nuestro &lt;code&gt;tee&lt;/code&gt; hace fundamentalmente lo que una T en cualquier especialidad. De hecho, lo común es usar tee junto con pipe o tubería a la salida de otro&amp;nbsp;comando.&lt;/p&gt;
&lt;p&gt;El uso básico de &lt;code&gt;tee&lt;/code&gt; es bastantes&amp;nbsp;simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tee&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;OPCIONES&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;FICHERO&lt;span class="w"&gt; &lt;/span&gt;DE&lt;span class="w"&gt; &lt;/span&gt;SALIDA&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El fichero de salida puede ser sólo uno o varios, y en el comportamiento por defecto es sobreescribir el fichero de&amp;nbsp;salida.&lt;/p&gt;
&lt;p&gt;En cuanto a las opciones, las más útiles del comando son las&amp;nbsp;siguientes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-a&lt;/code&gt;: Como ya he indicado, por defecto tee sobrescribe el fichero de salida que le hemos indicado. Pero con esta opción, el comportamiento cambia para añadir al final del&amp;nbsp;fichero.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt;: Esta opción sirve para ignorar las interrupciones. Ahora veremos usos típicos de &lt;code&gt;tee&lt;/code&gt;, pero el más común es tras un comando con un pipe. Esta opción permite que el comando tee acabe de manera ordenada cuando queremos cortar la ejecución del comando fuente de los datos con &lt;code&gt;CTRL+C&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ejemplos-de-uso"&gt;Ejemplos de&amp;nbsp;uso&lt;/h3&gt;
&lt;p&gt;Una vez explicada la sintaxis y las opciones, podemos pasar a los usos más comunes del comando tee en nuestra terminal. Podemos empezar por el ejemplo de uso más habitual, es decir, mandar la salida de un comando o un script a un fichero mientras mantenemos la posibilidad de seguir mostrándola por la salida&amp;nbsp;estándar.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ejecutar un comando, ver su salida estándar y guardarla a la vez un un fichero.&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1 - Hola soy un texto que se verá por la salida estándar y se guardará en un fichero&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;prueba-tee.log

&lt;span class="c1"&gt;# Ejecutar un comando, ver su salida estándar y a la vez añadirla al final de un un fichero.&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2 - Hola soy un texto que se verá por la salida estándar y se guardará en un fichero&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;prueba-tee.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En la siguiente animación se puede ver el funcionamiento de los comandos anteriores y su&amp;nbsp;resultado.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gif comando tee" src="https://noroute2host.com/images/0020_tee_001.gif" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Pero&amp;#8230; ¿y si el comando que estamos ejecutando lanza algún error por la salida de errores? Pues con el ejemplo anterior, el error se mostraría por pantalla pero no se guardaría en el fichero indicado a tee. Sin embargo, en muchas veces puede ser útil, que tee guarde las salidas estándar y de error en el mismo fichero. Por ejemplo, cuando interesa dejar lanzado algo y guardar toda la salida para revisarla después. Para este caso, os muestro el siguiente&amp;nbsp;ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Comando sin errores&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;El comando tee en noroute2host&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;prueba-tee-errores.log

&lt;span class="c1"&gt;# Comando con errores&lt;/span&gt;
bash&lt;span class="w"&gt; &lt;/span&gt;SoyUnComandoQueNoExiste&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;prueba-tee-errores.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La clave está en &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; que tiene como resultado redirigir la salida de errores a la estándar. Por lo que al final, tee sigue haciendo lo que hace normalmente, pero ahora la salida estándar que se vuelca al fichero como siempre, ya incluye la salida de&amp;nbsp;errores.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gif comando tee" src="https://noroute2host.com/images/0020_tee_con_errores_002.gif" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Para terminar con los ejemplos, os dejo uno con la opción &lt;code&gt;-i&lt;/code&gt;. Al leer la documentación de que es lo que hace esta opción nos puede parecer confuso, pero con un ejemplo se ve mucho más&amp;nbsp;claro.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ejecución de una espera de 100 segundos que se para con CTRL+C&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;prueba-tee-opcion-i.log
^C

&lt;span class="c1"&gt;# Código de salida del comando anterior&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="m"&gt;130&lt;/span&gt;

&lt;span class="c1"&gt;# Misma operación usando la opción -i del comando tee&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;prueba-tee-opcion-i.log
^C

&lt;span class="c1"&gt;# Código de salida del comando anterior&lt;/span&gt;
adrimcgrady@nanopifire3:~/noroute2host-pruebas$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como se puede ver, si paramos la ejecución del comando o script fuente de los datos de tee mediante una interrupción, en este caso &lt;code&gt;CTRL+C&lt;/code&gt;, el resultado del comando completo no es 0, lo cuál implicaría que no terminó correctamente. Sin embargo, al usar la opción &lt;code&gt;-i&lt;/code&gt; tee termina con código de error&amp;nbsp;0.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/tee.1.html"&gt;Página de man de&amp;nbsp;tee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coreutils/coreutils/blob/master/src/tee.c"&gt;Código Fuente del del comando&amp;nbsp;tee&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="bash"></category><category term="linux"></category><category term="comandos"></category></entry><entry><title>Aplicaciones libres para Android (IV): Markor</title><link href="https://noroute2host.com/markor.html" rel="alternate"></link><published>2023-03-10T19:30:00+01:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-03-10:/markor.html</id><summary type="html">&lt;p&gt;Sumamos un nuevo capítulo a la lista de Aplicaciones Libres para Android. Hoy os hablo sobre Markor o como el mismo se define, un editor de textos, notas y ToDo para Android que soporta Markdown, todo.txt, Zim y&amp;nbsp;demás.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Como siempre y antes de nada me gustaría recordar que la intención de la sección es mostrar aplicaciones que sean software libre y con las que podamos reemplazar de manera sencilla o con garantías otras que traen nuestros dispositivos Android por defecto, o simplemente que instalamos por que son las más conocidas pese a que sean privativas. Dicho esto ya podemos pasar a hablar de&amp;nbsp;Markor.&lt;/p&gt;
&lt;h2 id="markor"&gt;Markor&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Markor Logo" src="https://noroute2host.com/images/0019_markor_logo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;En este cuarto episodio de la serie Aplicaciones Libres para Android se hablará sobre  Markor o como el mismo se define, un editor de textos, notas y ToDo para Android que soporta Markdown, todo.txt,&amp;nbsp;Zim.&lt;/p&gt;
&lt;p&gt;Como siempre al inicio, aquí está la ficha de la aplicación. Eso sí, os aviso desde ya que no busquéis la descarga desde Google Play porque, al menos a fecha de redacción de este artículo, la aplicación solo está disponible en F-Droid o en el propio repositorio en&amp;nbsp;Github.&lt;/p&gt;
&lt;h3 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;Markor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/gsantner/markor#readme"&gt;https://github.com/gsantner/markor#readme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/gsantner/markor"&gt;https://github.com/gsantner/markor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt; Apache&amp;nbsp;2.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Keep, Evernote, o cualquier otro editor de texto, notas o&amp;nbsp;Markdown&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/packages/net.gsantner.markor/"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;/h3&gt;
&lt;p&gt;Markor es básicamente un editor de textos para Android con soporte para varios lenguajes de marcado, posibilidad de crear listas To-Do y compatible con cualquier otro editor de texto. Y todo esto de manera completamente offline y sin anuncios ni permisos&amp;nbsp;innecesarios.&lt;/p&gt;
&lt;p&gt;El listado de características oficiales sacadas de su descripción en su repositorio de github son las&amp;nbsp;siguientes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📝 Crear notas y administrar tus listas To-Do usando lenguajes de marcado&amp;nbsp;simple.&lt;/li&gt;
&lt;li&gt;🌲 Funciona completamente&amp;nbsp;offline.&lt;/li&gt;
&lt;li&gt;👌 Compatible con cualquier otro software o&amp;nbsp;plataforma.&lt;/li&gt;
&lt;li&gt;🖍 Resaltado de sintaxis y&amp;nbsp;autoformato.&lt;/li&gt;
&lt;li&gt;👀 Conversión, previsualización y posibilidad de compartir documentos como &lt;span class="caps"&gt;HTML&lt;/span&gt; y &lt;span class="caps"&gt;PDF&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;📚 Cuaderno: Guarda todos los documentos en una simple carpeta del sistema de&amp;nbsp;ficheros.&lt;/li&gt;
&lt;li&gt;📓 QuickNote: Toma notas de manera&amp;nbsp;rápida.&lt;/li&gt;
&lt;li&gt;☑️ To-Do: Crea tus propias listas&amp;nbsp;to-do.&lt;/li&gt;
&lt;li&gt;🖍 Formatos: Markdown, todo.txt, Zim/WikiText, Texto Plano, csv, ics, ini, json, toml, vcf,&amp;nbsp;yaml.&lt;/li&gt;
&lt;li&gt;📋 Copia al portapapeles: Copia cualquier hecho, incluyendo los textos compartidos&amp;nbsp;markor.&lt;/li&gt;
&lt;li&gt;💡 El cuaderno es el directorio raíz de los documentos y puede ser cambiado en cualquier momento. Las QuickNotes y las listas To-Do son simples ficheros de&amp;nbsp;texto.&lt;/li&gt;
&lt;li&gt;🎨 Altamente personalizable, tema oscuro&amp;nbsp;incluido.&lt;/li&gt;
&lt;li&gt;💾 Autoguardado y opciones para&amp;nbsp;deshacer/rehacer.&lt;/li&gt;
&lt;li&gt;👌 Sin publicidad ni permisos&amp;nbsp;innecesarios.&lt;/li&gt;
&lt;li&gt;🌎 Elección de idioma, puedes usar uno distinto al del&amp;nbsp;sistema.&lt;/li&gt;
&lt;li&gt;🔃 Aplicación offline. Aunque funciona con otras apps para&amp;nbsp;sincronizar.&lt;/li&gt;
&lt;li&gt;🔒 Puedes cifrar tus ficheros de texto con &lt;span class="caps"&gt;AES256&lt;/span&gt; siempre que tu versión de Android lo permita. Y puedes usar jpencconverter para cifrar y descifrar en tu &lt;span class="caps"&gt;PC&lt;/span&gt;. Pero ten encuenta que solo se cifran archivos de texto, nada de imágenes o&amp;nbsp;adjuntos.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="un-completo-editor-de-textos-en-tu-bolsillo"&gt;Un completo editor de textos en tu&amp;nbsp;bolsillo&lt;/h3&gt;
&lt;p&gt;Visto el listado de características anterior te podrás imaginar porqué traigo Markor a la sección. Me parece un editor indispensable, en mi caso para la edición de ficheros Markdown. De hecho este articulo esta siendo escrito directamente desde mi móvil&amp;nbsp;Android.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Markor Editor" src="https://noroute2host.com/images/0019_markor_editor.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y me ha permitido escribir muchos post en &lt;a href="noroute2host.com"&gt;noroute2host.com&lt;/a&gt; en un periodo donde no podía estar sentado delante de un &lt;span class="caps"&gt;PC&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Markor Lista de Ficheros" src="https://noroute2host.com/images/0019_markor_lista.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Dispone de infinidad de opciones para personalizar el editor, así como otras propias para el lenguaje de marcado que estés usando. Por ejemplo te permite insertar cualquier elemento del lenguaje de marcado directamente desde su barra de&amp;nbsp;herramientas.&lt;/p&gt;
&lt;p&gt;En concreto, hay un par de funciones que me encantan. Estas son, poder tener varios archivos abiertos a la vez con su propia ventana de edición y la posibilidad de previsualización del texto resultado en cualquier&amp;nbsp;momento.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Markor Previsualización" src="https://noroute2host.com/images/0019_markor_previsualizacion.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="una-alternativa-libre-para-editar-cualquier-archivo-de-texto"&gt;Una alternativa libre para editar cualquier archivo de&amp;nbsp;texto&lt;/h3&gt;
&lt;p&gt;Markor te permitirá editar cualquier archivo de texto, y facilitará y mejorará tu experiencia editando tus textos en lenguajes de marcado el cualquier dispositivo Android. Y todo desde un a aplicación con un enfoque de software libre&amp;nbsp;total.&lt;/p&gt;
&lt;h5 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h5&gt;
&lt;p&gt;Si quieres conocer otras &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; y te has quedado con ganas de más en &lt;a href="https://noroute2host.com/aplicaciones-libres-android.html"&gt;este enlace está el índice con todos los artículos de la&amp;nbsp;serie&lt;/a&gt;&lt;/p&gt;</content><category term="android"></category><category term="software libre"></category><category term="markdown"></category><category term="editor"></category><category term="app"></category></entry><entry><title>Tratamiento y conversión de imágenes con ImageMagick (II) Aplicación a densidades de pantalla de Android.</title><link href="https://noroute2host.com/imagemagick-android.html" rel="alternate"></link><published>2023-01-27T18:00:00+01:00</published><updated>2022-01-27T18:00:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2023-01-27:/imagemagick-android.html</id><summary type="html">&lt;p&gt;Siguiendo con el tratamiento de imágenes desde la terminal con ImageMagick, vamos a aplicar lo visto en el &lt;a href="https://noroute2host.com/imagemagick.html"&gt;post anterior&lt;/a&gt; al desarrollo de aplicaciones para Android. En concreto, a la necesidad de trabajar con imágenes en diferentes tamaños para las diferentes densidades de pantalla que puede tener un dispositivo&amp;nbsp;Android.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Siguiendo con el tratamiento de imágenes desde la terminal con ImageMagick, vamos a aplicar lo visto en el &lt;a href="https://noroute2host.com/imagemagick.html"&gt;post anterior&lt;/a&gt; al desarrollo de aplicaciones para Android. En concreto, a la necesidad de trabajar con imágenes en diferentes tamaños para las diferentes densidades de pantalla que puede tener un dispositivo&amp;nbsp;Android.&lt;/p&gt;
&lt;h2 id="la-necesidad-los-diferentes-tamanos-y-densidades-de-pantalla-de-un-dispositivo-android"&gt;La necesidad: Los diferentes tamaños y densidades de pantalla de un dispositivo&amp;nbsp;Android.&lt;/h2&gt;
&lt;p&gt;Si no eres desarrollador de aplicaciones Android seguro que nos te has parado a pensar en este aspecto. Pero si lo eres&amp;#8230; te habrás dado cuenta de que Android corre en miles de dispositivos diferentes, y que la diversidad de tamaños y densidades de pantalla es absolutamente abrumadora. Si desarrollas una aplicación para Android, está puede acabar corriendo en smartphones, tablets, Android &lt;span class="caps"&gt;TV&lt;/span&gt;, etcétera. Y en cada una de estas categorías te puedes encontrar con pantallas completamente&amp;nbsp;diferentes.&lt;/p&gt;
&lt;p&gt;Esto que en realidad es un punto positivo porque tu aplicación puede funcionar en multitud de dispositivos diferentes, también puede ser un dolor de cabeza en el desarrollo de la parte gráfica de tu app. Porque si lo piensas, el resultado puede ser muy diferente en función de donde se esté ejecutando. A nivel de disposición de elementos sobre la pantalla, la solución más recomendable y usada es la de definir los elementos usando medidas y dimensiones relativas, pero ese no es el ámbito de este post. Aquí nos vamos a centrar en las imágenes, que aunque puedes definir sus contenedores de manera relativa, el resultado puede ser deficiente. Por ejemplo, aunque conserves la relación de aspecto, si tienes una imagen de 25x25 píxeles, se mostrará de manera muy diferente en dispositivos con pantallas muy diferentes, y no solo en resolución, sino sobre todo en&amp;nbsp;densidad.&lt;/p&gt;
&lt;h3 id="densidad-de-pantalla-o-dpi"&gt;Densidad de pantalla o &lt;span class="caps"&gt;DPI&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Vamos a ver que es esto de la Densidad de pantalla o los &lt;span class="caps"&gt;DPI&lt;/span&gt;. Anteriormente, la medida base para ver como se veía algo un una pantalla era la resolución, es decir, 640x480, 1980x1080&amp;#8230; Pero en la época de los smartphones es fácil ver como afecta la densidad de pantalla al escalado de las imágenes. Solo tenemos que pensar en dos dispositivos con el mismo tamaño de pantalla pero con diferentes resoluciones máximas. Es bastante común ver pantallas iguales, por ejemplo 6 pulgadas, pero que una tenga una resolución &lt;span class="caps"&gt;HD&lt;/span&gt; (1280x720) y otro &lt;span class="caps"&gt;FHD&lt;/span&gt; (1920x1080), o similares. Si se define la densidad de pantalla como el número de pixeles por pantalla está bastante claro que las dos pantallas que hemos puesto como ejemplo tendrán diferentes densidades de pantalla. Y a que nos lleva esto, pues a que la misma imagen mostrada en pantallas del mismo tamaño se verá mucho más grande (y puede que pixelada) en la pantalla &lt;span class="caps"&gt;HD&lt;/span&gt; que en la &lt;span class="caps"&gt;FHD&lt;/span&gt;. Si no te queda claro con mi explicación te recomiendo que le eches un ojo a &lt;a href="https://developer.android.com/training/multiscreen/screendensities?hl=es-419"&gt;este enlace&lt;/a&gt; de la documentación&amp;nbsp;oficial.&lt;/p&gt;
&lt;h2 id="la-solucion-oficial-de-android-definir-diferentes-versiones-de-la-misma-imagen-para-segun-que-dpi"&gt;La solución oficial de Android: Definir diferentes versiones de la misma imagen para según que &lt;span class="caps"&gt;DPI&lt;/span&gt;.&lt;/h2&gt;
&lt;p&gt;¿Y que proponen desde Android para lidiar con este problema? En el caso de las imágenes, una de las soluciones es crear 6 conjuntos de gráficos diferentes que serían los&amp;nbsp;siguientes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ldpi: Para densidades de pantalla de aproximadamente 120 dpi. Tamaño de imagen&amp;nbsp;0.75x.&lt;/li&gt;
&lt;li&gt;mdpi: Para densidades de pantalla de aproximadamente 160 dpi. Tamaño de imagen de referencia&amp;nbsp;1x.&lt;/li&gt;
&lt;li&gt;hdpi: Para densidades de pantalla de aproximadamente 240 dpi. Tamaño de imagen&amp;nbsp;1.5x.&lt;/li&gt;
&lt;li&gt;xhdpi: Para densidades de pantalla de aproximadamente 320 dpi. Tamaño de imagen&amp;nbsp;2x.&lt;/li&gt;
&lt;li&gt;xxhdpi: Para densidades de pantalla de aproximadamente 480 dpi. Tamaño de imagen&amp;nbsp;3x.&lt;/li&gt;
&lt;li&gt;xxxhdpi: Para densidades de pantalla de aproximadamente 640 dpi. Tamaño de imagen&amp;nbsp;4x.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por tanto, la proporción de escalamiento de estas 6 densidades principales sería 3:4:6:8:12:16. Pero puedes ver más claramente estas proporciones en la siguiente imagen extraida de la documentación de&amp;nbsp;Android:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Android diferentes dpi" src="https://noroute2host.com/images/0018_densidades_android_dpi.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Por ejemplo, si tenemos una imagen de tamaño de referencia (mdpi) de 48x48 píxeles, los diferentes tamaños según dpi deberían&amp;nbsp;ser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;36x36 (0.75x) para&amp;nbsp;ldpi&lt;/li&gt;
&lt;li&gt;48x48 (1.0x) para&amp;nbsp;mdpi&lt;/li&gt;
&lt;li&gt;72x72 (1.5x) para&amp;nbsp;hdpi&lt;/li&gt;
&lt;li&gt;96x96 (2.0x) para&amp;nbsp;xhdpi&lt;/li&gt;
&lt;li&gt;144x144 (3.0x) para&amp;nbsp;xxhdpi&lt;/li&gt;
&lt;li&gt;192x192 (4.0x) para&amp;nbsp;xxxhdpi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Todas estas imágenes se colocarán en diferentes subdirectorios dentro del directorio &lt;em&gt;res/&lt;/em&gt; de la aplicación. De modo que creando un directorio con &lt;em&gt;drawable-&lt;/em&gt; más el nombre de cada densidad de pantalla y dejando dentro la imagen con la resolución correcta, la aplicación cogerá automáticamente la que corresponda en función de la pantalla en que esté funcionando. De este modo el árbol de recursos quedaría tal que&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;|--res
|  |--drawable-ldpi
|  |  |--imagenGuay.png
|  |--drawable-mdpi
|  |  |--imagenGuay.png
|  |--drawable-hdpi
|  |  |--imagenGuay.png
|  |--drawable-xhdpi
|  |  |--imagenGuay.png
|  |--drawable-xxhdpi
|  |  |--imagenGuay.png
|  |--drawable-xxxhdpi
|  |  |--imagenGuay.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Además, merece la pena destacar que hay un par de densidades especiales que son las&amp;nbsp;siguientes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nodpi: Para gráficos que queremos que se muestren igual en todas las densidades de&amp;nbsp;pantalla.&lt;/li&gt;
&lt;li&gt;tvdpi: Para pantallas entre mdpi y hdpi de aproximadamente 213 dpi. Tamaño de imagen&amp;nbsp;1.33x.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="automatizando-la-creacion-de-las-diferentes-imagenes-para-cada-dpi-con-imagemagick-y-un-script-en-bash"&gt;Automatizando la creación de las diferentes imágenes para cada &lt;span class="caps"&gt;DPI&lt;/span&gt; con ImageMagick y un script en&amp;nbsp;bash.&lt;/h2&gt;
&lt;p&gt;Una vez explicada la teoría, ya podemos pasar al objetivo de esta entrada del blog, que no es otro que el de poner a tu disposición un script bash que a partir de un directorio de imágenes base, las transforme y genere 6 imágenes diferentes, que ubicará en la ruta correcta dentro del directorio &lt;em&gt;res/&lt;/em&gt;. Incluyendo además una conversión de formato si fuese necesaria. Es decir, vamos aplicar todo lo que os conté de ImageMagick en el &lt;a href="https://noroute2host.com/imagemagick.html"&gt;capítulo I&lt;/a&gt; para automatizar el proceso de creación de imágenes para diferentes densidades de pantalla en el desarrollo de una aplicación de&amp;nbsp;Android.&lt;/p&gt;
&lt;h3 id="el-script-generar_drawables_dpish"&gt;El script:&amp;nbsp;generar_drawables_dpi.sh&lt;/h3&gt;
&lt;p&gt;El script que os comparto para automatizar la creación de las diferentes imágenes asociadas a las distintas densidades de pantalla lo he llamado &lt;code&gt;0018_generar_drawables_dpi.sh&lt;/code&gt; y lo tenéis &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0018_imagemagick-android/0018_generar_drawables_dpi.sh"&gt;compartido directamente en el repositorio del blog en Gitlab&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Este pequeño script hace exactamente lo que hemos hablado&amp;nbsp;anteriormente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Genera 6 imágenes por cada imagen encontrada en el directorio que llamaremos de origen. Es decir, una imagen para cada una de las densidades de&amp;nbsp;pantalla.&lt;/li&gt;
&lt;li&gt;Además, convertirá la imagen &amp;#8220;original&amp;#8221; del formato &amp;#8220;original&amp;#8221; al que le indiquemos. Por ejemplo, de &lt;span class="caps"&gt;XCF&lt;/span&gt; a &lt;span class="caps"&gt;PNG&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Por último, si así lo deseamos, copiará todas las imágenes generadas al proyecto de nuestra App Android en&amp;nbsp;desarrollo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En cuanto al directorio y las imágenes originales hay que tener en cuenta los siguientes&amp;nbsp;aspectos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Todas las imágenes del directorio deberán ser del mismo formato de&amp;nbsp;&amp;#8220;origen&amp;#8221;.&lt;/li&gt;
&lt;li&gt;El tamaño de las imágenes deberá ser el deseado para la densidad &lt;code&gt;xxxhdpi&lt;/code&gt;. Es decir, en vez de tomar como referencia &lt;code&gt;mdpi&lt;/code&gt; tomaremos la imagen más grande para ir reduciendo al ir generando las sucesivas&amp;nbsp;imágenes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="usando-generar_drawables_dpish"&gt;Usando&amp;nbsp;generar_drawables_dpi.sh&lt;/h4&gt;
&lt;p&gt;El uso y la llamada del script sería como cualquier script en bash. Es decir &lt;code&gt;./generar_drawables_dpi.sh&lt;/code&gt; o &lt;code&gt;0018_generar_drawables_dpi.sh&lt;/code&gt; si has usado el mismo nombre del fichero en el repositorio. Pero la utilidad tiene varios parámetros obligatorios y otros opcionales que detallo a&amp;nbsp;continuación:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt;: Se usa para definir el directorio donde se encuentran las imágenes &amp;#8220;originales&amp;#8221;. &lt;em&gt;Parámetro obligatorio&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt;: Se usará para definir el directorio de salida para las nuevas imágenes. Dentro de este directorio se creará una carpeta para cada dpi. &lt;em&gt;Parámetro obligatorio&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: Se utilizará para indicar al script el formato de partida de las imágenes dentro del directorio definido con &lt;code&gt;-i&lt;/code&gt;. &lt;em&gt;Parámetro obligatorio&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-s&lt;/code&gt;: Se utiliza para indicar al script el formato de salida de las nuevas imágenes. &lt;em&gt;Parámetro obligatorio&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;: Si se define, lo último que hará la herramienta es copiar todas las imágenes generadas al directorio definido con este parámetro. Debe ser una ruta al directorio &lt;code&gt;res&lt;/code&gt; del proyecto de tu aplicación Android en desarrollo. &lt;em&gt;Parámetro opcional&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Modo Debug. Añade una salida más detallada. &lt;em&gt;Parámetro opcional&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt;: Ayuda. Muestra información de como ejecutar la&amp;nbsp;utilidad.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ejemplos-de-uso"&gt;Ejemplos de&amp;nbsp;uso&lt;/h4&gt;
&lt;p&gt;A continuación os muestro varios ejemplos de uso de la herramienta junto con la salida de la ejecución. Para estos ejemplos vamos a usar, como imagen dentro del directorio de entrada, una imagen con el logo del blog, con tamaño inicial 192x192, con formato inicial &lt;span class="caps"&gt;XCF&lt;/span&gt; y con formato de salida &lt;span class="caps"&gt;PNG&lt;/span&gt;. Está sería la imágen&amp;nbsp;original:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Android diferentes dpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_192.png" style="display:block" title="Logo Azul Original"&gt;&lt;/p&gt;
&lt;p&gt;Y estas las diferentes ejecuciones del&amp;nbsp;script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generar imágenes para diferentes densidades en formato &lt;span class="caps"&gt;PNG&lt;/span&gt; desde formato &lt;span class="caps"&gt;XCF&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./0018_generar_drawables_dpi.sh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;xcf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;png

Comienzo&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;span class="w"&gt;  &lt;/span&gt;Comprobando&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;instalación&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;ImageMagick...
&lt;span class="w"&gt;  &lt;/span&gt;Generando&lt;span class="w"&gt; &lt;/span&gt;recursos...
Fin&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Generar imágenes para diferentes densidades en formato &lt;span class="caps"&gt;PNG&lt;/span&gt; desde formato &lt;span class="caps"&gt;XCF&lt;/span&gt; y copiar a directorio de&amp;nbsp;proyecto.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./0018_generar_drawables_dpi.sh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;xcf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/

Comienzo&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;span class="w"&gt;  &lt;/span&gt;Comprobando&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;instalación&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;ImageMagick...
&lt;span class="w"&gt;  &lt;/span&gt;Generando&lt;span class="w"&gt; &lt;/span&gt;recursos...
&lt;span class="w"&gt;  &lt;/span&gt;Copiando&lt;span class="w"&gt; &lt;/span&gt;al&lt;span class="w"&gt; &lt;/span&gt;proyecto...
Fin&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Generar imágenes para diferentes densidades en formato &lt;span class="caps"&gt;PNG&lt;/span&gt; desde formato &lt;span class="caps"&gt;XCF&lt;/span&gt; y copiar a directorio de proyecto incluyendo la opción para debug o&amp;nbsp;depuración.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./0018_generar_drawables_dpi.sh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;xcf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/

Comienzo&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;span class="w"&gt;  &lt;/span&gt;Comprobando&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;instalación&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;ImageMagick...
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;Binario&lt;span class="w"&gt; &lt;/span&gt;ImageMagick:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick
&lt;span class="w"&gt;  &lt;/span&gt;Generando&lt;span class="w"&gt; &lt;/span&gt;recursos...
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xxxhdpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xxhdpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xhdpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;.5%&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/hdpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/mdpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.75%&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/ldpi&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/originales/*.xcf
&lt;span class="w"&gt;  &lt;/span&gt;Copiando&lt;span class="w"&gt; &lt;/span&gt;al&lt;span class="w"&gt; &lt;/span&gt;proyecto...
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xxxhdpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-xxxhdpi
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xxhdpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-xxhdpi
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/xhdpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-xhdpi
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/hdpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-hdpi
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/mdpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-mdpi
DEBUG:&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/noroute2host.com/files/0018_imagemagick-android/imgs/ldpi/*.png&lt;span class="w"&gt; &lt;/span&gt;/home/usuario/StudioProjects/miApp/app/src/main/res/drawable-ldpi
Fin&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;ejecución.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por último, solo me queda mostrar las imágenes resultantes que se han obtenido a partir de la que hemos usado de&amp;nbsp;base:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ldpi (36x36)&lt;/th&gt;
&lt;th&gt;mdpi (48x48)&lt;/th&gt;
&lt;th&gt;hdpi (72x72)&lt;/th&gt;
&lt;th&gt;xhdpi (96x96)&lt;/th&gt;
&lt;th&gt;xxhdpi (144x144)&lt;/th&gt;
&lt;th&gt;xxxhdpi (192x192)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt="favicon ldpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_ldpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="favicon mdpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_mdpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="favicon hdpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_hdpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="favicon xhdpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_xhdpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="favicon xxhdpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_xxhdpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="favicon xxxhdpi" src="https://noroute2host.com/images/0018_favicon_noroute2host_xxxhdpi.png" style="display:block"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="codigo-fuente-del-script"&gt;Código fuente del&amp;nbsp;script&lt;/h4&gt;
&lt;p&gt;Como ya os he comentado anteriormente este script, así como todos los del blog, lo tenéis en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/"&gt;repositorio Noroute2host Files&lt;/a&gt;. Así que mejor que poner por aquí el código, consultarlo y descargarlo directamenre &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0018_imagemagick-android/0018_generar_drawables_dpi.sh"&gt;aquí&lt;/a&gt;. ¡Y por supuesto es mejorable, modificable, ampliable y todo lo que se os&amp;nbsp;ocurra!&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://noroute2host.com/imagemagick.html"&gt;Capítulo I: Tratamiento y conversión de imágenes con ImageMagick&amp;nbsp;(I)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/multiscreen/screendensities?hl=es-419"&gt;Documentación oficial de Android para trabajar con diferentes densidades de&amp;nbsp;pantalla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files/"&gt;Repositorio noroute2host&amp;nbsp;Files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="linux"></category><category term="terminal"></category><category term="imagemagick"></category><category term="android"></category></entry><entry><title>Tratamiento y conversión de imágenes con ImageMagick (I)</title><link href="https://noroute2host.com/imagemagick.html" rel="alternate"></link><published>2022-12-23T16:00:00+01:00</published><updated>2022-12-23T16:00:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-12-23:/imagemagick.html</id><summary type="html">&lt;p&gt;Seguro que has tenido hacer cambios en las propiedades de una imagen (tamaño, formato, etc) muchas veces. Y seguro que has usado cualquier programa de edición en tu &lt;span class="caps"&gt;PC&lt;/span&gt; o una app para el móvil. Pero&amp;#8230; ¿y si tuvieras que hacer el mismo cambio a decenas o cientos de imágenes? ¿sería efectivo hacerlo de esa forma? Para solucionar esto, damos gracias a &lt;code&gt;ImageMagick&lt;/code&gt; por existir. En este primer capítulo os contaré como realizar algunas operaciones básicas con imágenes desde la terminal con ImageMagick. Y habrá un segundo capítulo donde aplicaré estas transformaciones para generar un script que nos ayude a lidiar con las diferentes densidades de pantalla en el desarrollo de aplicaciones para&amp;nbsp;Android.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Seguro que has tenido hacer cambios en las propiedades de una imagen (tamaño, formato, etc) muchas veces. Y seguro que has usado cualquier programa de edición en tu &lt;span class="caps"&gt;PC&lt;/span&gt; o una app para el móvil. Pero&amp;#8230; ¿y si tuvieras que hacer el mismo cambio a decenas o cientos de imágenes? ¿sería efectivo hacerlo de esa forma? Para solucionar esto, damos gracias a &lt;code&gt;ImageMagick&lt;/code&gt; por existir. En este primer capítulo os contaré como realizar algunas operaciones básicas con imágenes desde la terminal con ImageMagick. Y habrá un segundo capítulo donde aplicaré estas transformaciones para generar un script que nos ayude a lidiar con las diferentes densidades de pantalla en el desarrollo de aplicaciones para&amp;nbsp;Android.&lt;/p&gt;
&lt;h2 id="imagemagick"&gt;ImageMagick&lt;/h2&gt;
&lt;p&gt;Creo que es lógico empezar hablando de  ImageMagick. Se trata de una suite open source para la visualización, edición y conversión de imágenes. De modo que permite la modificación de múltiples propiedades de nuestras imágenes. Desde animaciones a reducción de ruido o cambios de tamaño o directamente la conversión entre formatos. Y todo esto para usar desde la terminal, por lo que las posibilidades de automatización y de encadenamiento de operaciones son casi&amp;nbsp;infinitas.&lt;/p&gt;
&lt;p&gt;En este ejemplo, voy a centrarme en las más básicas que, al menos para mí&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cambio de&amp;nbsp;tamaño.&lt;/li&gt;
&lt;li&gt;Conversión de&amp;nbsp;formato.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="instalacion-de-imagemagick"&gt;Instalación de&amp;nbsp;ImageMagick&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ImageMagick&lt;/code&gt; se puede instalar de varias&amp;nbsp;maneras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mediante la mejor opción que suele ser la paquetería de tu distribución. El paquete suele llamarse &lt;em&gt;imagemagick&lt;/em&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Debian/Ubuntu &lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;imagemagick

&lt;span class="c1"&gt;# RedHat/Centos/OracleLinux&lt;/span&gt;
dnf&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;ImageMagick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Si por el contrario tienes algún problema con los paquetes oficiales como por ejemplo un bug que impide convertir tus imágenes desde el formato &lt;span class="caps"&gt;XCF&lt;/span&gt; a otros formatos y usas Debian/Ubuntu puedes ayudarte de &amp;#8220;&lt;span class="caps"&gt;IMEI&lt;/span&gt; - ImageMagick Easy Install&amp;#8221; que automáticamente descarga los fuentes, los compila y los instala: &lt;a href="https://softcreatr.github.io/imei/"&gt;https://softcreatr.github.io/imei/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="usando-imagemagick"&gt;Usando&amp;nbsp;ImageMagick&lt;/h3&gt;
&lt;p&gt;Independientemente del método de instalación que hayas usado el funcionamiento será el mismo. Sí tienes que tener en cuenta que en las versiones antiguas de imagemagick, el comando básico para el tratamiento de las imágenes era &lt;code&gt;convert&lt;/code&gt; y ahora se llama &lt;code&gt;magick&lt;/code&gt;. De hecho, se sigue manteniendo &lt;code&gt;convert&lt;/code&gt; como un link simbólico a &lt;code&gt;magick&lt;/code&gt; para facilitar la compatibilidad de scripts o aplicaciones ya creadas&amp;nbsp;previamente.&lt;/p&gt;
&lt;p&gt;Como ejemplo para las transformaciones vamos a usar el logo del blog. Una imagen cuadrada de tamaño 378x378 y formato &lt;span class="caps"&gt;XCF&lt;/span&gt;. Y para que no tengas que fiarte de mi palabra con respecto a la imagen inicial, el primer subcomando del que te hablaré es &lt;code&gt;identify&lt;/code&gt;. Este comando provee información básica sobre una imagen sobre una&amp;nbsp;imagen.&lt;/p&gt;
&lt;p&gt;El uso de ImageMagick para la opción &lt;code&gt;identify&lt;/code&gt; es bastante&amp;nbsp;simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;identify&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo.xcf&lt;span class="w"&gt; &lt;/span&gt;
noroute2host_logo.xcf&lt;span class="w"&gt; &lt;/span&gt;XCF&lt;span class="w"&gt; &lt;/span&gt;378x378&lt;span class="w"&gt; &lt;/span&gt;378x378+0+0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;sRGB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.020u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00.007
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para una información más extensa se puede usar el modificador &lt;code&gt;-verbose&lt;/code&gt;, pero sin él ya nos quedan claras las características de nuestra imagen inicial. Así que vamos a empezar por mostrar como convertir nuestro archivo &lt;span class="caps"&gt;XCF&lt;/span&gt; a &lt;span class="caps"&gt;PNG&lt;/span&gt; manteniendo el resto de propiedades. Y esto se hace todavía más fácil. La sintaxis básica para esto&amp;nbsp;es:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;magick&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;imagen_entrada.formato_original&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;imagen_salida.formato_salida&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que en el caso de la conversión de nuestro logo a &lt;span class="caps"&gt;PNG&lt;/span&gt; desde &lt;span class="caps"&gt;XCF&lt;/span&gt; quedaría de la siguiente&amp;nbsp;manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conversión de imagen XCF a PNG&lt;/span&gt;
adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo.xcf&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo.png

&lt;span class="c1"&gt;# Uso de identify para ver las propiedades de la nueva imagen&lt;/span&gt;
adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;identify&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo.png
noroute2host_logo.png&lt;span class="w"&gt; &lt;/span&gt;PNG&lt;span class="w"&gt; &lt;/span&gt;378x378&lt;span class="w"&gt; &lt;/span&gt;378x378+0+0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;sRGB&lt;span class="w"&gt; &lt;/span&gt;12617B&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00.000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y aquí esta la imagen&amp;nbsp;resultante:&lt;/p&gt;
&lt;p&gt;&lt;img alt="noroute2host.com logo png" src="https://noroute2host.com/images/0017_noroute2host_logo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Si además de convertir el formato, quieres redimensionar la imagen, no es ningún problema. Solo tendrás que añadir la operación &lt;code&gt;-resize&lt;/code&gt; junto con el tamaño deseado al comando de conversión. Por lo que quedaría&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conversión de imagen XCF a PNG + Cambio de tamaño a 128x128&lt;/span&gt;
adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo.xcf&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;128x128&lt;span class="w"&gt;  &lt;/span&gt;noroute2host_logo_128.png

&lt;span class="c1"&gt;# Uso de identify para ver las propiedades de la nueva imagen&lt;/span&gt;
adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;identify&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo_128.png&lt;span class="w"&gt; &lt;/span&gt;
noroute2host_logo_128.png&lt;span class="w"&gt; &lt;/span&gt;PNG&lt;span class="w"&gt; &lt;/span&gt;128x128&lt;span class="w"&gt; &lt;/span&gt;128x128+0+0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;sRGB&lt;span class="w"&gt; &lt;/span&gt;5937B&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00.000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este sería el&amp;nbsp;resultado:&lt;/p&gt;
&lt;p&gt;&lt;img alt="noroute2host.com logo png 128" src="https://noroute2host.com/images/0017_noroute2host_logo_128.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Al respecto de las imágenes &lt;span class="caps"&gt;XCF&lt;/span&gt;, tengo que admitir que la imagen usada de ejemplo es monocapa, pero un archivo &lt;span class="caps"&gt;XCF&lt;/span&gt; puede tener varias capas. Y si haces las conversiones tal y como te he descrito arriba, se generarían imágenes diferentes para capas diferentes añadiendo un apéndice numérico al final del nombre del fichero. Si quieres evitar esto, debes usar la operación &lt;code&gt;-flatten&lt;/code&gt; que es un alias de &lt;code&gt;-layers flatten&lt;/code&gt;. Y que básicamente lo que hace es &amp;#8220;aplanar&amp;#8221; todas las capas. Hay otras opciones para las capas tal y como puedes consultar &lt;a href="https://imagemagick.org/script/command-line-options.php?#layers"&gt;aquí&lt;/a&gt;. En cualquier caso, dejo aquí un ejemplo de como realizar esta&amp;nbsp;operación.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conversión de imagen XCF multicapa a PNG aplanando las capas.&lt;/span&gt;
adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo_mcapa.xcf&lt;span class="w"&gt; &lt;/span&gt;-flatten&lt;span class="w"&gt; &lt;/span&gt;noroute2host_logo_mcapa.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="mogrify-mejorando-el-tratamiento-por-lotes"&gt;Mogrify. Mejorando el tratamiento por&amp;nbsp;lotes.&lt;/h3&gt;
&lt;p&gt;Lo último que voy a contarte de ImageMagick algo relativo a sus posibilidades de ser usado por lotes o en scripts. Y es que para este cometido existe un subcomando diferente llamado &lt;code&gt;mogrify&lt;/code&gt;. Esta herramienta nos permite una mejor automatización. Pero hay que tener en cuenta que si no usamos la opción &lt;code&gt;-format&lt;/code&gt; para realizar conversión del fichero original o &lt;code&gt;-path&lt;/code&gt; para cambiar el destino se sobrescribirá el fichero&amp;nbsp;original.&lt;/p&gt;
&lt;p&gt;De modo, que si queremos hacer la misma operación que anteriormente, es decir, convertir una imagen de formato &lt;span class="caps"&gt;XCF&lt;/span&gt; a &lt;span class="caps"&gt;PNG&lt;/span&gt; y cambiar su tamaño, pero ampliándolo a todas las imágenes de un directorio sería algo&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@bixito:~$&lt;span class="w"&gt; &lt;/span&gt;magick&lt;span class="w"&gt; &lt;/span&gt;mogrify&lt;span class="w"&gt; &lt;/span&gt;-resize&lt;span class="w"&gt; &lt;/span&gt;128x128&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/imgs_convertidas/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/imgs_originales/*.xcf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En el anterior comando estamos usando la herramienta mogrify para tratar todas todas las imágenes &lt;span class="caps"&gt;XCF&lt;/span&gt; del directorio &lt;em&gt;$&lt;span class="caps"&gt;HOME&lt;/span&gt;/imgs_originales/*.xcf&lt;/em&gt; con 3&amp;nbsp;opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-resize&lt;/code&gt;: Para redimensionar las imágenes a un tamaño de&amp;nbsp;128x128.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-format&lt;/code&gt;: Para establecer el formato de salida a &lt;span class="caps"&gt;PNG&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-path&lt;/code&gt;: Para fijar el directorio de salida de las nuevas imágenes a $&lt;span class="caps"&gt;HOME&lt;/span&gt;/imgs_convertidas/&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puedes consultar más opciones para la herramienta &lt;code&gt;mogrify&lt;/code&gt; en el enlace que te dejo al final del&amp;nbsp;post.&lt;/p&gt;
&lt;h3 id="algo-mas"&gt;¿Algo&amp;nbsp;más?&lt;/h3&gt;
&lt;p&gt;Como puedes ver, las posibilidades de ImageMagick son casi infinitas. Así que si quieres ampliar tu conocimiento de que más puedes hacer te recomiendo que eches uno ojo a la documentación oficial. En concreto, te recomiendo &lt;a href="https://imagemagick.org/Usage/"&gt;este apartado&lt;/a&gt; donde hay infinidad de ejemplos de todo lo que puedes hacer. Por mi parte, te emplazo al siguiente capítulo de este tema que estará disponible próximamente, y en el que te contaré como usar estas operaciones para trabajar con las diferentes densidades de pantalla en el desarrollo de aplicaciones para&amp;nbsp;Android.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://imagemagick.org/"&gt;Página oficial del proyecto&amp;nbsp;ImageMagick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ImageMagick/ImageMagick"&gt;Repositorio oficial del proyecto&amp;nbsp;ImageMagick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://imagemagick.org/Usage/"&gt;Ejemplos de uso de funcionalidades de&amp;nbsp;ImageMagick&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://imagemagick.org/script/command-line-tools.php"&gt;Herramientas de la linea de comandos de&amp;nbsp;ImageMagick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://imagemagick.org/script/mogrify.php"&gt;Documentación oficial de la utilidad&amp;nbsp;mogrify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://softcreatr.github.io/imei/"&gt;&lt;span class="caps"&gt;IMEI&lt;/span&gt; - ImageMagick Easy&amp;nbsp;Install&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="imagemagick"></category><category term="bash"></category><category term="linux"></category></entry><entry><title>Aplicaciones libres para Android (III): Aegis Authenticator</title><link href="https://noroute2host.com/aegis-authenticator.html" rel="alternate"></link><published>2022-11-25T20:00:00+01:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-11-25:/aegis-authenticator.html</id><summary type="html">&lt;p&gt;Tercer capítulo de la serie Aplicaciones Libres para Android. Para este capítulo os acerco Aegis Authenticator, un gestor de tus tokens para los servicios con segundo factor de autenticación o &lt;span class="caps"&gt;2FA&lt;/span&gt;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La intención de la sección es mostrar aplicaciones que sean software libre y con las que podamos reemplazar de manera sencilla o con garantías otras que traen nuestros dispositivos Android por defecto, o simplemente que instalamos por que son las más conocidas pese a que sean&amp;nbsp;privativas.&lt;/p&gt;
&lt;h2 id="aegis-authenticator"&gt;Aegis&amp;nbsp;Authenticator&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Aegis Logo" src="https://noroute2host.com/images/0016_aegis_logo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Ya vamos por el tercer artículo de la serie Aplicaciones Libres para Android. En este caso vengo con Aegis Authenticator, un gestor de tus tokens para los servicios con segundo factor de autenticación o &lt;span class="caps"&gt;2FA&lt;/span&gt;. Aunque tengo que decir que la idea original era venir con andOTP, pero desgraciadamente esta&amp;nbsp;descontinuado.&lt;/p&gt;
&lt;h3 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt; Aegis&amp;nbsp;Authenticator &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://getaegis.app/"&gt;https://getaegis.app/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/beemdevelopment/Aegis"&gt;https://github.com/beemdevelopment/Aegis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Authenticator, Microsoft Authenticator, Authy,&amp;nbsp;andOTP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/en/packages/com.beemdevelopment.aegis"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;/h3&gt;
&lt;p&gt;Aegis Authenticator dispone de las funcionalidades que necesitas para gestionar tu acceso seguro a diferentes servicios. Y como no puede ser de otra manera estando en esta sección, es completamente&amp;nbsp;opensource.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Aegis Menu" src="https://noroute2host.com/images/0016_aegis_menu.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="algoritmos-soportados"&gt;Algoritmos&amp;nbsp;soportados&lt;/h4&gt;
&lt;p&gt;Soporta completamente los algoritmos &lt;span class="caps"&gt;HOTP&lt;/span&gt; y &lt;span class="caps"&gt;TOTP&lt;/span&gt;. Además se soportar otros procolos como mOTP de manera&amp;nbsp;experimental.&lt;/p&gt;
&lt;h4 id="anadir-nuevas-entradas"&gt;Añadir nuevas&amp;nbsp;entradas&lt;/h4&gt;
&lt;p&gt;Pra añadir nuevas entradas, dispone de varios&amp;nbsp;métodos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Usar los típicos códigos &lt;span class="caps"&gt;QR&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Introducir los datos&amp;nbsp;manualmente.&lt;/li&gt;
&lt;li&gt;Importar desde otras&amp;nbsp;apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="metodos-de-importacion"&gt;Métodos de&amp;nbsp;importación&lt;/h4&gt;
&lt;p&gt;Con respecto a este último punto, hay que destacar la cantidad de metodos de importación soportados. Según la información&amp;nbsp;oficial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;2FAS&lt;/span&gt;&amp;nbsp;Authenticator&lt;/li&gt;
&lt;li&gt;Authenticator&amp;nbsp;Plus&lt;/li&gt;
&lt;li&gt;Authy&lt;/li&gt;
&lt;li&gt;andOTP&lt;/li&gt;
&lt;li&gt;FreeOTP&lt;/li&gt;
&lt;li&gt;FreeOTP+&lt;/li&gt;
&lt;li&gt;Google&amp;nbsp;Authenticator&lt;/li&gt;
&lt;li&gt;Microsoft&amp;nbsp;Authenticator&lt;/li&gt;
&lt;li&gt;Texto&amp;nbsp;Plano&lt;/li&gt;
&lt;li&gt;Steam&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;TOTP&lt;/span&gt;&amp;nbsp;Authenticator&lt;/li&gt;
&lt;li&gt;WinAuth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aunque para algunos de estos modos de importación requiere acceso&amp;nbsp;root.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Aegis Importar" src="https://noroute2host.com/images/0016_aegis_importar.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h4 id="seguridad"&gt;Seguridad&lt;/h4&gt;
&lt;p&gt;La aplicación cuenta con varias funciones de seguridad como puede ser la encriptación de la bóveda que se puede desbloquear mediante contraseña o características&amp;nbsp;biométricas.&lt;/p&gt;
&lt;p&gt;También hay otras opciones de seguridad bastante útiles como bloquear la captura de pantalla o el típico &amp;#8220;pulsar para&amp;nbsp;mostrar&amp;#8221;.&lt;/p&gt;
&lt;h4 id="backups"&gt;Backups&lt;/h4&gt;
&lt;p&gt;Aegis te facilita la copia de seguridad permitiendote exportar la claves manualmente a un archivo &lt;span class="caps"&gt;JSON&lt;/span&gt;, realizar copias de seguridad automáticas, o integrarla en la copia de&amp;nbsp;Android.&lt;/p&gt;
&lt;p&gt;Hay que indicar que las opciones automáticas solo estan disponibles para bóvedas&amp;nbsp;cifradas.&lt;/p&gt;
&lt;h4 id="otras-caracteristicas"&gt;Otras&amp;nbsp;características&lt;/h4&gt;
&lt;p&gt;Esta App dispone de otras&amp;nbsp;características:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Es realmente curiosa la posibilidad de instalar paquetes de iconos personalizados para customizar automáticamente la nuevas entradas. Incluyendo uno &amp;#8220;no oficial&amp;#8221; propio que puedes descargar desde &lt;a href="https://aegis-icons.github.io/"&gt;aquí&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;La organización de los tokens y su ordenación según diferentes criterios. Permite la creación de grupos o notas para este&amp;nbsp;cometido.&lt;/li&gt;
&lt;li&gt;Está disponible para funcionar con diferentes temas incluyendo el&amp;nbsp;oscuro.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="una-alternativa-libre-para-que-te-quedes-sin-excusas-para-usar-siempre-2fa"&gt;Una alternativa libre para que te quedes sin excusas para usar siempre &lt;span class="caps"&gt;2FA&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Aegis Authenticator lo tiene todo para que no tengas excusas para activar y usar siempre que sea posible el segundo factor de autenticación. ¡Venga! ¡La seguridad no es&amp;nbsp;negociable!&lt;/p&gt;
&lt;h5 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h5&gt;
&lt;p&gt;Si quieres conocer otras &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; y te has quedado con ganas de más en &lt;a href="https://noroute2host.com/aplicaciones-libres-android.html"&gt;este enlace está el índice con todos los artículos de la&amp;nbsp;serie&lt;/a&gt;&lt;/p&gt;</content><category term="android"></category><category term="software libre"></category><category term="2fa"></category><category term="app"></category></entry><entry><title>Ampliar swap a máquina virtual Linux sin reiniciar</title><link href="https://noroute2host.com/ampliar-swap-mv-linux.html" rel="alternate"></link><published>2022-11-04T19:15:00+01:00</published><updated>2022-11-04T19:15:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-11-04:/ampliar-swap-mv-linux.html</id><summary type="html">&lt;p&gt;La definición de requisitos de una máquina virtual puede ser una tarea tan sencilla de hacer como susceptible de modificaciones posteriores. En este caso, vamos a ver como ampliar la swap a una máquina virtual sin reiniciar el sistema&amp;nbsp;operativo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La definición de requisitos de una máquina virtual puede ser una tarea tan sencilla de hacer como susceptible de modificaciones posteriores. En este caso, vamos a ver como ampliar la swap a una máquina virtual sin reiniciar el sistema&amp;nbsp;operativo.&lt;/p&gt;
&lt;h2 id="swap-un-poco-de-literatura"&gt;&lt;span class="caps"&gt;SWAP&lt;/span&gt;: Un poco de&amp;nbsp;literatura&lt;/h2&gt;
&lt;p&gt;Si has llegado a este artículo es porque sabes que es la swap o memoria de intercambio. Pero por si acaso hago un pequeño&amp;nbsp;resumen.&lt;/p&gt;
&lt;p&gt;Al final, la memoria &lt;span class="caps"&gt;RAM&lt;/span&gt; física de un ordenador es finita. Para salvar este límite los sistemas operativos crean en disco un segundo nivel de memoria llamado de intercambio o&amp;nbsp;swap.&lt;/p&gt;
&lt;p&gt;Evidentemente este nivel es mas lento, pero te permite pasar programas desde la &lt;span class="caps"&gt;RAM&lt;/span&gt; a la swap cuando no estás haciendo uso de ellos, si necesitas memoria para otros programas que si está usando. Cuando vuelvas a necesitar algo que está en swap tu sistema operativo volverá a subirlo a &lt;span class="caps"&gt;RAM&lt;/span&gt; y&amp;nbsp;listo.&lt;/p&gt;
&lt;p&gt;La swap no es más que un pequeño truco que usan los sistemas operativos para disponer de más memoria. Normalmente está memoria swap debería usarse solo cuando tú sistema está falto de &lt;span class="caps"&gt;RAM&lt;/span&gt;. Aunque esto no es siempre&amp;nbsp;así&amp;#8230;&lt;/p&gt;
&lt;h2 id="ampliando-la-swap-en-caliente"&gt;Ampliando la swap en&amp;nbsp;caliente&lt;/h2&gt;
&lt;p&gt;Vamos a partir de una máquina virtual con 4 &lt;span class="caps"&gt;GB&lt;/span&gt; de swap. Estos 4 &lt;span class="caps"&gt;GB&lt;/span&gt; están ubicados en una partición &lt;span class="caps"&gt;LVM&lt;/span&gt; actualmente. En mi caso, no quiero ampliar por problemas de rendimiento sino por requisitos en la instalación de un software. Además, esta es solo una forma de las múltiples que hay para conseguir este&amp;nbsp;objetivo.&lt;/p&gt;
&lt;h3 id="comprobacion-de-configuracion-actual-swap"&gt;Comprobación de configuración actual&amp;nbsp;swap&lt;/h3&gt;
&lt;p&gt;En primer lugar, lo mejor es comprobar la configuración actual de swap con el comando &lt;code&gt;swapon -s&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-mv&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# swapon -s&lt;/span&gt;
Filename&lt;span class="w"&gt;                                &lt;/span&gt;Type&lt;span class="w"&gt;            &lt;/span&gt;Size&lt;span class="w"&gt;    &lt;/span&gt;Used&lt;span class="w"&gt;    &lt;/span&gt;Priority
/dev/dm-1&lt;span class="w"&gt;                               &lt;/span&gt;partition&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;4194300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="preparar-el-nuevo-espacio"&gt;Preparar el nuevo&amp;nbsp;espacio&lt;/h3&gt;
&lt;p&gt;Lo siguiente es preparar el nuevo espacio para alojar la nueva memoria de intercambio. En mi caso, he añadido un nuevo disco virtual a mi máquina virtual de VirtualBox que ha sido reconocido como &lt;code&gt;/dev/sdc&lt;/code&gt;. (El procedimiento de añadir un nuevo disco virtual y descubrirlo en la mv lo puedes encontrar en múltiples sitios de&amp;nbsp;Internet.)&lt;/p&gt;
&lt;p&gt;Una vez asignado el nuevo disco, hay que prepararlo para usarlo como memoria&amp;nbsp;swap.&lt;/p&gt;
&lt;p&gt;Lo primero será hacer una partición &lt;span class="caps"&gt;LVM&lt;/span&gt; con &lt;code&gt;fdisk&lt;/code&gt; usando todo el disco, aunque el tamaño es a gusto del&amp;nbsp;consumidor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# fdisk /dev/sdc&lt;/span&gt;
Welcome&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;fdisk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;util-linux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.23.2&lt;span class="o"&gt;)&lt;/span&gt;.

Changes&lt;span class="w"&gt; &lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;remain&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;memory&lt;span class="w"&gt; &lt;/span&gt;only,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;until&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;decide&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;them.
Be&lt;span class="w"&gt; &lt;/span&gt;careful&lt;span class="w"&gt; &lt;/span&gt;before&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;command.

Device&lt;span class="w"&gt; &lt;/span&gt;does&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;contain&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;recognized&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;table
Building&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;DOS&lt;span class="w"&gt; &lt;/span&gt;disklabel&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;identifier&lt;span class="w"&gt; &lt;/span&gt;0x190a4313.

The&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;presents&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;logical&lt;span class="w"&gt; &lt;/span&gt;sector&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;smaller&lt;span class="w"&gt; &lt;/span&gt;than
the&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;sector&lt;span class="w"&gt; &lt;/span&gt;size.&lt;span class="w"&gt; &lt;/span&gt;Aligning&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;sector&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;optimal
I/O&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;boundary&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;recommended,&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;performance&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;impacted.

Command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;p

Disk&lt;span class="w"&gt; &lt;/span&gt;/dev/sdc:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.7&lt;span class="w"&gt; &lt;/span&gt;GB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10737418240&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20971520&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors
&lt;span class="nv"&gt;Units&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes
Sector&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;logical/physical&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes
I/O&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;minimum/optimal&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes
Disk&lt;span class="w"&gt; &lt;/span&gt;label&lt;span class="w"&gt; &lt;/span&gt;type:&lt;span class="w"&gt; &lt;/span&gt;dos
Disk&lt;span class="w"&gt; &lt;/span&gt;identifier:&lt;span class="w"&gt; &lt;/span&gt;0x190a4313

&lt;span class="w"&gt;   &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;Boot&lt;span class="w"&gt;      &lt;/span&gt;Start&lt;span class="w"&gt;         &lt;/span&gt;End&lt;span class="w"&gt;      &lt;/span&gt;Blocks&lt;span class="w"&gt;   &lt;/span&gt;Id&lt;span class="w"&gt;  &lt;/span&gt;System

Command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;n
Partition&lt;span class="w"&gt; &lt;/span&gt;type:
&lt;span class="w"&gt;   &lt;/span&gt;p&lt;span class="w"&gt;   &lt;/span&gt;primary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;primary,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;extended,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;free&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;e&lt;span class="w"&gt;   &lt;/span&gt;extended
Select&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;p
Partition&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-4,&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
First&lt;span class="w"&gt; &lt;/span&gt;sector&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;-20971519,&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
Using&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;
Last&lt;span class="w"&gt; &lt;/span&gt;sector,&lt;span class="w"&gt; &lt;/span&gt;+sectors&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;+size&lt;span class="o"&gt;{&lt;/span&gt;K,M,G&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;-20971519,&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20971519&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
Using&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20971519&lt;/span&gt;
Partition&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Linux&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;

Command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;t
Selected&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
Hex&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;L&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;codes&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;8e
Changed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Linux&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Linux LVM&amp;#39;&lt;/span&gt;

Command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;w
The&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;altered!

Calling&lt;span class="w"&gt; &lt;/span&gt;ioctl&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;re-read&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;table.
Syncing&lt;span class="w"&gt; &lt;/span&gt;disks.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con el comando &lt;code&gt;partprobe&lt;/code&gt; hacemos que el kernel actualice la información de particiones y tenga en cuenta la nueva. Los mensajes del dispositivo &lt;code&gt;/dev/sr0&lt;/code&gt; pueden&amp;nbsp;obviarse.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# partprobe&lt;/span&gt;
Warning:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;/dev/sr0&lt;span class="w"&gt; &lt;/span&gt;read-write&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Read-only&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="o"&gt;)&lt;/span&gt;.&lt;span class="w"&gt;  &lt;/span&gt;/dev/sr0&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;opened&lt;span class="w"&gt; &lt;/span&gt;read-only.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora con &lt;code&gt;lvdiskscan&lt;/code&gt; comprobaremos que efectivamente nuestro sistema reconoce la nueva partición y está lista para ser usada con &lt;span class="caps"&gt;LVM&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# lvmdiskscan&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_raiz&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;10&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sda1&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;MiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_swap&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sda2&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;40&lt;/span&gt;.49&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LVM&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;volume
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_almacen/lv_almacen&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;20&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_home&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_opt&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_tmp&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/vg_so/lv_var&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sdb&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LVM&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;volume
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sdc1&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;10&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disks
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;partitions
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LVM&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;whole&lt;span class="w"&gt; &lt;/span&gt;disk
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LVM&lt;span class="w"&gt; &lt;/span&gt;physical&lt;span class="w"&gt; &lt;/span&gt;volume
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En este punto ya podemos pasar a crear todos los elementos de &lt;span class="caps"&gt;LVM&lt;/span&gt; donde alojaremos nuestra nueva swap. Lo primero es crear el &lt;em&gt;Physical Volume&lt;/em&gt; sobre nuestra partición con &lt;code&gt;pvcreate&lt;/code&gt; y comprobarlo con &lt;code&gt;pvs&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# pvcreate /dev/sdc1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;Physical&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdc1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;successfully&lt;span class="w"&gt; &lt;/span&gt;created.

&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# pvs&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;PV&lt;span class="w"&gt;         &lt;/span&gt;VG&lt;span class="w"&gt;           &lt;/span&gt;Fmt&lt;span class="w"&gt;  &lt;/span&gt;Attr&lt;span class="w"&gt; &lt;/span&gt;PSize&lt;span class="w"&gt;    &lt;/span&gt;PFree
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sda2&lt;span class="w"&gt;  &lt;/span&gt;vg_so&lt;span class="w"&gt;   &lt;/span&gt;lvm2&lt;span class="w"&gt; &lt;/span&gt;a--&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;.49g&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.00m
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sdb&lt;span class="w"&gt;   &lt;/span&gt;vg_almacen&lt;span class="w"&gt; &lt;/span&gt;lvm2&lt;span class="w"&gt; &lt;/span&gt;a--&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;20&lt;/span&gt;.00g&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/sdc1&lt;span class="w"&gt;               &lt;/span&gt;lvm2&lt;span class="w"&gt; &lt;/span&gt;---&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;10&lt;/span&gt;.00g&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;10&lt;/span&gt;.00g
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Posteriormente, añadiremos el &lt;span class="caps"&gt;PV&lt;/span&gt; creado al &lt;em&gt;Volume Group&lt;/em&gt; existente para el sistema operativo donde tenemos actualmente el volumen de swap. Si lo prefieres puedes crear un &lt;span class="caps"&gt;VG&lt;/span&gt; nuevo solo para la nueva swap. Pero en este caso usaremos el existente para el sistema operativo. Así que primero se ampliará el &lt;span class="caps"&gt;VG&lt;/span&gt; con &lt;code&gt;vgextend&lt;/code&gt; y después se comprobará la ampliación con &lt;code&gt;vgs&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# vgextend vg_so /dev/sdc1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;Volume&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vg_so&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;successfully&lt;span class="w"&gt; &lt;/span&gt;extended

&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# vgs&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;VG&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;#PV #LV #SN Attr   VSize    VFree&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;vg_almacen&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wz--n-&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;20&lt;/span&gt;.00g&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;vg_so&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wz--n-&lt;span class="w"&gt;  &lt;/span&gt;&amp;lt;&lt;span class="m"&gt;40&lt;/span&gt;.49g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00g
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="deshabilitar-swap-para-redimensionar"&gt;Deshabilitar swap para&amp;nbsp;redimensionar&lt;/h3&gt;
&lt;p&gt;En este ejemplo veremos cómo ampliar el volumen de swap actual. Se podría generar una nueva swap2 y tener ambas activadas a la vez, o descartar la antigua y solo usar la nueva. Pero, en este caso lo que haremos será ampliar el espacio de intercambio anterior con el espacio del nuevo disco para acabar con una única memoria swap con el total del&amp;nbsp;espacio.&lt;/p&gt;
&lt;p&gt;Para esto, lo primero ha hacer es deshabilitar las swap actual para poder redimensionarla. &lt;strong&gt;Al hacer esta operación todo lo que está en swap pasará a memoria &lt;span class="caps"&gt;RAM&lt;/span&gt;. Así que es recomendable realizar este paso cuando el consumo de memoria &lt;span class="caps"&gt;RAM&lt;/span&gt; no esté en su punto más elevado. En cualquier caso, deberías asegurarte antes de que tienes suficiente memoria con &lt;code&gt;free -m&lt;/code&gt; por&amp;nbsp;ejemplo.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# swapoff -v /dev/vg_so/lv_swap&lt;/span&gt;
swapoff&lt;span class="w"&gt; &lt;/span&gt;/dev/vg_so/lv_swap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para comprobar que efectivamente se ha deshabilitado se puede hacer&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# swapon -s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="redimensionar-swap"&gt;Redimensionar&amp;nbsp;swap&lt;/h3&gt;
&lt;p&gt;Una vez que nuestra memoria de intercambio está inactiva, podemos proceder a ampliar el volumen lógico con el espacio del nuevo disco y su nueva partición &lt;code&gt;/dev/sdc1&lt;/code&gt; con &lt;code&gt;lvresize&lt;/code&gt;. Aunque antes y después comprobaremos el tamaño del volumen de swap con &lt;code&gt;lvs&lt;/code&gt; para ir revisando como se ejecuta la&amp;nbsp;ampliación.&lt;/p&gt;
&lt;p&gt;La ampliación la vamos a hacer añadiendo 8 &lt;span class="caps"&gt;GB&lt;/span&gt;. Pero puedes hacerla por el tamaño total si obvias el &lt;code&gt;+8G&lt;/code&gt; o simplemente por la cantidad que desees, siempre que tengas espacio disponible en el &lt;span class="caps"&gt;VG&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# lvs /dev/vg_so/lv_swap&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;LV&lt;span class="w"&gt;      &lt;/span&gt;VG&lt;span class="w"&gt;         &lt;/span&gt;Attr&lt;span class="w"&gt;       &lt;/span&gt;LSize&lt;span class="w"&gt; &lt;/span&gt;Pool&lt;span class="w"&gt; &lt;/span&gt;Origin&lt;span class="w"&gt; &lt;/span&gt;Data%&lt;span class="w"&gt;  &lt;/span&gt;Meta%&lt;span class="w"&gt;  &lt;/span&gt;Move&lt;span class="w"&gt; &lt;/span&gt;Log&lt;span class="w"&gt; &lt;/span&gt;Cpy%Sync&lt;span class="w"&gt; &lt;/span&gt;Convert
&lt;span class="w"&gt;  &lt;/span&gt;lv_swap&lt;span class="w"&gt; &lt;/span&gt;vg_so&lt;span class="w"&gt; &lt;/span&gt;-wi-a-----&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.00g

&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# lvresize /dev/vg_so/lv_swap -L +8G /dev/sdc1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;Size&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;logical&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;vg_so/lv_swap&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;extents&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;GiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3072&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;extents&lt;span class="o"&gt;)&lt;/span&gt;.
&lt;span class="w"&gt;  &lt;/span&gt;Logical&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;vg_so/lv_swap&lt;span class="w"&gt; &lt;/span&gt;successfully&lt;span class="w"&gt; &lt;/span&gt;resized.

&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# lvs /dev/vg_so/lv_swap&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;LV&lt;span class="w"&gt;      &lt;/span&gt;VG&lt;span class="w"&gt;         &lt;/span&gt;Attr&lt;span class="w"&gt;       &lt;/span&gt;LSize&lt;span class="w"&gt;  &lt;/span&gt;Pool&lt;span class="w"&gt; &lt;/span&gt;Origin&lt;span class="w"&gt; &lt;/span&gt;Data%&lt;span class="w"&gt;  &lt;/span&gt;Meta%&lt;span class="w"&gt;  &lt;/span&gt;Move&lt;span class="w"&gt; &lt;/span&gt;Log&lt;span class="w"&gt; &lt;/span&gt;Cpy%Sync&lt;span class="w"&gt; &lt;/span&gt;Convert
&lt;span class="w"&gt;  &lt;/span&gt;lv_swap&lt;span class="w"&gt; &lt;/span&gt;vg_so&lt;span class="w"&gt; &lt;/span&gt;-wi-a-----&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.00g
&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# mkswap /dev/vg_so/lv_swap&lt;/span&gt;
mkswap:&lt;span class="w"&gt; &lt;/span&gt;/dev/vg_so/lv_swap:&lt;span class="w"&gt; &lt;/span&gt;warning:&lt;span class="w"&gt; &lt;/span&gt;wiping&lt;span class="w"&gt; &lt;/span&gt;old&lt;span class="w"&gt; &lt;/span&gt;swap&lt;span class="w"&gt; &lt;/span&gt;signature.
Setting&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;swapspace&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12582908&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;KiB
no&lt;span class="w"&gt; &lt;/span&gt;label,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6d710bbb-6efb-586e-955c-235e83ef7d29
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="activacion-de-swap-ampliada"&gt;Activación de swap&amp;nbsp;ampliada&lt;/h3&gt;
&lt;p&gt;Como ya tenemos nuestra memoria swap ampliada a 12 &lt;span class="caps"&gt;GB&lt;/span&gt;, solo queda reactivarla con &lt;code&gt;swapon&lt;/code&gt;. En el ejemplo usamos parametrización para activar todas las disponibles, pero se podría modificar el comando para activar una swap en específico si tuvieras mas de&amp;nbsp;una.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# swapon -va&lt;/span&gt;
swapon&lt;span class="w"&gt; &lt;/span&gt;/dev/mapper/vg_so-lv_swap
swapon:&lt;span class="w"&gt; &lt;/span&gt;/dev/mapper/vg_so-lv_swap:&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;swap&lt;span class="w"&gt; &lt;/span&gt;signature:&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;page-size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;same&lt;span class="w"&gt; &lt;/span&gt;byte&lt;span class="w"&gt; &lt;/span&gt;order
swapon:&lt;span class="w"&gt; &lt;/span&gt;/dev/mapper/vg_so-lv_swap:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pagesize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;swapsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;12884901888&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;devsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;12884901888&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para finalizar, ya solo queda comprobar que la swap ampliada esta activa y tiene el nuevo tamaño con &lt;code&gt;swapon -s&lt;/code&gt;. Así como revisar el estado de la memoria con &lt;code&gt;free&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# swapon -s&lt;/span&gt;
Filename&lt;span class="w"&gt;                                &lt;/span&gt;Type&lt;span class="w"&gt;            &lt;/span&gt;Size&lt;span class="w"&gt;    &lt;/span&gt;Used&lt;span class="w"&gt;    &lt;/span&gt;Priority
/dev/dm-1&lt;span class="w"&gt;                               &lt;/span&gt;partition&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;12582908&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;-2

&lt;span class="o"&gt;[&lt;/span&gt;root@linux-vm&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# free -m&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;total&lt;span class="w"&gt;        &lt;/span&gt;used&lt;span class="w"&gt;        &lt;/span&gt;free&lt;span class="w"&gt;      &lt;/span&gt;shared&lt;span class="w"&gt;  &lt;/span&gt;buff/cache&lt;span class="w"&gt;   &lt;/span&gt;available
Mem:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;10005&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;4819&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;4993&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;3496&lt;/span&gt;
Swap:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;12287&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;12287&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tldp.org/LDP/sag/html/memory-management.html"&gt;Memoria virtual en &lt;span class="caps"&gt;TLDP&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://es.wikipedia.org/wiki/Espacio_de_intercambio"&gt;Explicación de memoria de intercambio en&amp;nbsp;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="linux"></category><category term="terminal"></category><category term="swap"></category><category term="memoria"></category></entry><entry><title>NFLscores Mastodon Bot. Crear Bot de Mastodon y tootear con Python (II)</title><link href="https://noroute2host.com/mastodon-bot-python-nflscores.html" rel="alternate"></link><published>2022-10-14T19:00:00+02:00</published><updated>2022-10-14T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-10-14:/mastodon-bot-python-nflscores.html</id><summary type="html">&lt;p&gt;Segunda parte sobre la creación de bots con Mastodon desde Python. Este segundo capítulo esta centrado en contar el proyecto real de bot de marcadores de la &lt;span class="caps"&gt;NFL&lt;/span&gt; en vivo para&amp;nbsp;Mastodon.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Segunda parte sobre la creación de bots con Mastodon desde Python. Este segundo capítulo esta centrado en contar el proyecto real de bot de marcadores de la &lt;span class="caps"&gt;NFL&lt;/span&gt; en vivo para&amp;nbsp;Mastodon.&lt;/p&gt;
&lt;p&gt;Si todavía no has leído la parte I, la tienes aquí: &lt;a href="https://noroute2host.com/mastodon-bot-python.html"&gt;Crear Bot de Mastodon y tootear con Python (&lt;span class="caps"&gt;II&lt;/span&gt;)&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="bot-scoreboard-nfl-para-mastodon"&gt;Bot Scoreboard &lt;span class="caps"&gt;NFL&lt;/span&gt; para&amp;nbsp;Mastodon&lt;/h2&gt;
&lt;p&gt;Pues sí, tal como suena, me he puesto manos a la obra y con el fin de aprender y enseñar he creado un bot que he puesto a&amp;nbsp;funcionar.&lt;/p&gt;
&lt;p&gt;¿Y de qué es? Pues tras un intenso estudio de mercado y de los gustos del personal el Mastodon de 30 segundos o así, he decidido hacer lo contrario. Es decir, un bot de deportes. Es más, no es que lo haya hecho de fútbol o de baloncesto donde todavía podría tener alguna esperanza de repercusión. No, es un bot de un Scoreboard de la jornada en &lt;span class="caps"&gt;NFL&lt;/span&gt;: &lt;a href="https://mastodon.online/@nflscores"&gt;NFLscores Mastodon&amp;nbsp;Bot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Nflscores Tusky" src="https://noroute2host.com/images/0014_mastodon-bot-python-nflscores_tusky.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;¿Y por qué? Pues porque me apetecía. Ya que le iba a echar un tiempecito, por lo menos, que fuera algo que me guste y que al menos sea útil para mí. Así que el enfoque es básicamente educativo tanto para mí como para el que quiera&amp;nbsp;usarlo.&lt;/p&gt;
&lt;h3 id="el-proyecto"&gt;El&amp;nbsp;proyecto&lt;/h3&gt;
&lt;p&gt;Mi situación actual, solo me deja trastear con el móvil, y en mi móvil tengo todo lo necesario para desarrollar el&amp;nbsp;proyecto:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un editor de código para Python: &lt;a href="https://play.google.com/store/apps/details?id=ru.iiec.pydroid3"&gt;Pydroid&amp;nbsp;3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Un cliente de Mastodon: &lt;a href="https://tusky.app/"&gt;Tusky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Un editor de Markdown: &lt;a href="https://gsantner.net/project/markor.html"&gt;Markor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Un cliente&amp;nbsp;ssh.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además, es el primer proyecto real que de libera en noroute2host. Así que me hace especial ilusión. Por lo que aquí tienes ya el &lt;a href="https://gitlab.com/noroute2host/nflscores-mastodon-bot"&gt;Enlace al proyecto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por otro lado, me gustaría dejar claro que ni soy experto dn Python, ni esto es más que una version inicial aunque funcional. Faltan bastantes cosas, comprobaciones de errores, etcétera. Y quizás hay algún objeto que podría ser un&amp;nbsp;diccionario.&lt;/p&gt;
&lt;p&gt;Pero a mí me ha servido para explicar el funcionamiento de un bot en Mastodon, aprender algo de Python orientado a objetos y tener mis marcadores de &lt;span class="caps"&gt;NFL&lt;/span&gt; en directo en&amp;nbsp;Tusky.&lt;/p&gt;
&lt;h3 id="funcionamiento"&gt;Funcionamiento&lt;/h3&gt;
&lt;p&gt;La base para la publicación de toots es la que vimos en el primer capítulo. Aunque con algunos añadidos bastante&amp;nbsp;interesantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;El bot se alimenta de los datos que proporciona una &lt;a href="http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard"&gt;&lt;span class="caps"&gt;API&lt;/span&gt; de &lt;span class="caps"&gt;ESPN&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Se puede limitar el tamaño maximo de toot para dividir en varios toots si es&amp;nbsp;necesario.&lt;/li&gt;
&lt;li&gt;Tanto la &lt;span class="caps"&gt;API&lt;/span&gt; de marcadores, como la de Mastodon, el Token y el número máximo de caracteres por toot se puede configurar mediante fichero de&amp;nbsp;configuración.&lt;/li&gt;
&lt;li&gt;La programación de toots se hace mediante cron cambiando la frecuencia de publicación en función de los horarios de&amp;nbsp;partido.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Básicamente, el funcionamiento de cada ejecución del bot es el&amp;nbsp;siguiente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Se obtiene la configuración del fichero &lt;code&gt;config.conf&lt;/code&gt;. Donde se encuentra la &lt;span class="caps"&gt;API&lt;/span&gt; de marcadores y la info para publicar en&amp;nbsp;mastodon.&lt;/li&gt;
&lt;li&gt;Se hace una petición a la &lt;span class="caps"&gt;API&lt;/span&gt; y se descarga la información en formato&amp;nbsp;json.&lt;/li&gt;
&lt;li&gt;Se traduce la información a objetos de&amp;nbsp;Python.&lt;/li&gt;
&lt;li&gt;Con los objetos, se construye el&amp;nbsp;scoreboard.&lt;/li&gt;
&lt;li&gt;Se publica el toot o los toots con la información del scoreboard. Si el tamaño del toot supera lo establecido en la configuración se dividirá el&amp;nbsp;toot.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;En el &lt;a href="https://gitlab.com/noroute2host/nflscores-mastodon-bot"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;.md&lt;/a&gt; del proyecto podéis encontrar más información de como instalarlo o usarlo. Asi de cómo esta&amp;nbsp;construido.&lt;/p&gt;
&lt;h3 id="contribuir"&gt;Contribuir&lt;/h3&gt;
&lt;p&gt;El proyecto ha sido publicado en Gitlab bajo licencia GPLv3, por lo que todo el mundo puede contribuir. Además pienso que esta preparado para poder usar otra &lt;span class="caps"&gt;API&lt;/span&gt; y desarrollando otro &amp;#8220;recolector&amp;#8221; sería posible adaptar a cualquier deporte de partidos de&amp;nbsp;equipos.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mastodon.online/@nflscores"&gt;Perfil del bot Nflscores en&amp;nbsp;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/nflscores-mastodon-bot"&gt;Proyecto NFLscores Mastodon Bot en&amp;nbsp;Gitlab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://noroute2host.com/mastodon-bot-python.html"&gt;Crear Bot de Mastodon y tootear con Python&amp;nbsp;(I)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="mastodon"></category><category term="bot"></category><category term="rss"></category></entry><entry><title>Crear Bot de Mastodon y tootear con Python (I)</title><link href="https://noroute2host.com/mastodon-bot-python.html" rel="alternate"></link><published>2022-10-06T19:00:00+02:00</published><updated>2022-10-06T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-10-06:/mastodon-bot-python.html</id><summary type="html">&lt;p&gt;En esta ocasión os traigo la manera de crear un bot en Mastodon para después usarlo y tootear de Python. Este es la primera parte de una pareja de posts a este&amp;nbsp;respecto.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En esta ocasión os traigo la manera de crear un bot en Mastodon para después usarlo y tootear desde Python. Esta es la primera parte de una pareja de posts a este&amp;nbsp;respecto.&lt;/p&gt;
&lt;p&gt;En este primer capítulo, os explicaré detalladamente cómo crear una cuenta de Mastodon para usarla como un bot, y como postear en ella desde Python. Mientras que en el segundo capítulo os mostraré un bot que actualmente ya está funcionando y cuyo código estará publicado en&amp;nbsp;Gitlab.&lt;/p&gt;
&lt;h2 id="mastodon"&gt;Mastodon&lt;/h2&gt;
&lt;p&gt;Podríamos definir Mastodon como una red social de tipo mocroblogging que es libre y descentralizada. Descentralizada implica que los usuarios se reparten en instancias y estas instancias pueden interactuar entre ellas. Además Mastodon forma parte del Fediverso y puede interoperar con otras redes sociales que implementen&amp;nbsp;ActivityPub.&lt;/p&gt;
&lt;p&gt;Pero tampoco quiero entrar de lleno en estos términos. Si tienes interés y ganas de ampliar información sobre Mastodon y el Fediverso, he dejado algunos links al final del&amp;nbsp;artículo.&lt;/p&gt;
&lt;p&gt;Para lo que si aprovecharé, es para recordar que tenemos cuenta de Mastodon de noroute2host: &lt;a href="https://mastodon.social/@noroute2host"&gt;https://mastodon.social/@noroute2host&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="creando-un-bot-en-una-instancia-de-mastodon"&gt;Creando un bot en una instancia de&amp;nbsp;Mastodon&lt;/h2&gt;
&lt;p&gt;Cualquier cuenta de Mastodon puede servir para un bot. Así que el primer paso será simple, elegir una instancia, y crear una cuenta en&amp;nbsp;ella.&lt;/p&gt;
&lt;p&gt;Puedes elegir una instancia pública desde &lt;a href="https://joinmastodon.org/servers"&gt;aquí&lt;/a&gt;. En mi caso, por ejemplo, voy a elegir una de las mayoritarias: &lt;a href="https://mastodon.online"&gt;mastodon.online&lt;/a&gt;, pero tú puedes elegir cualquiera que te&amp;nbsp;guste.&lt;/p&gt;
&lt;p&gt;Una vez creada tu cuenta puedes completar tu perfil, avatar, etcétera. Pero lo importante en esta sección para seguir la buena práctica, es que marques tu cuenta como&amp;nbsp;bot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Opcion Bot" src="https://noroute2host.com/images/0013_mastodon-bot-python_botcheck.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Con esto ya tendrás tu cuenta bot&amp;nbsp;creada.&lt;/p&gt;
&lt;h3 id="creando-el-token-para-tootear-mediante-la-api"&gt;Creando el token para tootear mediante la &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Ahora es el momento de crear una aplicación autorizada dentro de nuestra cuenta de Mastodon que nos permita publicar desde la &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Esto es muy sencillo de hacer. Se ejecuta desde las opciones de nuestra propia cuenta en el apartado &lt;strong&gt;Desarrollo&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Pulsando en &lt;strong&gt;Nueva Aplicación&lt;/strong&gt; nos permitirá fundamentalmente dar un nombre a nuestra aplicación y restablecer los permisos que tendrá. Estos van desde publicar o leer estados, hasta borrarlos o hacer follow a cuentas. Por simplificar, para este ejemplo puedes seleccionar &lt;strong&gt;Read&lt;/strong&gt; y &lt;strong&gt;Write&lt;/strong&gt;. Aunque si quieres puedes refinar estos permisos&amp;nbsp;bastante.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Opcion Bot" src="https://noroute2host.com/images/0013_mastodon-bot-python_app-creada.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Una vez has creado tu aplicación autorizada y dado permisos, es el momento de obtener tu token de acceso para tootear vía &lt;span class="caps"&gt;API&lt;/span&gt;. Y es tan facil como pulsar en tu aplicación y copiar el &lt;strong&gt;Token de Acceso&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Opcion Bot" src="https://noroute2host.com/images/0013_mastodon-bot-python_app-token.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h2 id="hacer-funcionar-el-bot-desde-python"&gt;Hacer funcionar el bot desde&amp;nbsp;Python&lt;/h2&gt;
&lt;p&gt;Ya a nivel de Mastodon esta todo creado. Tenemos una cuenta y un aplicación autorizada con su token de acceso a buen recaudo. Ahora toca usar todo desde Python. Para ello usaremos la librería&amp;nbsp;Mastodon.py.&lt;/p&gt;
&lt;h3 id="mastodonpy"&gt;Mastodon.py&lt;/h3&gt;
&lt;p&gt;Mastodon.py es un wrapper para la &lt;span class="caps"&gt;API&lt;/span&gt; de Mastodon implementado en Python. Para instalarlo en tu &lt;a href="https://noroute2host.com/python-virtualenv.html"&gt;virtualenv&lt;/a&gt; o distribución directamente puedes hacerlo con&amp;nbsp;pip.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;Mastodon.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="tootear-desde-python"&gt;Tootear desde&amp;nbsp;Python&lt;/h3&gt;
&lt;p&gt;Ya solo nos falta tootear desde Python. Para ello habrá que crear una nueva instancia de la &lt;span class="caps"&gt;API&lt;/span&gt; usando el wrapper Mastodon.py, para mas adelante publicar un toot usando esa instancia que acabamos de crear. Además, en el ejemplo siguiente incluyo también como responder a uno de nuestros&amp;nbsp;toots.&lt;/p&gt;
&lt;p&gt;Hay que tener en cuenta que los datos de &lt;code&gt;access_token&lt;/code&gt; y &lt;code&gt;api_base_url&lt;/code&gt; son los de mi aplicación de prueba. Y que deberán ser sustituidos en función del token de cada uno y de la instancia que se esté&amp;nbsp;usando.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Importar la clase necesaria desde Mastodon.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mastodon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mastodon&lt;/span&gt;

&lt;span class="c1"&gt;# Crear la instancia de la API&lt;/span&gt;
&lt;span class="n"&gt;mastodon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mastodon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gJVj3oO1fJgg3AbLSfq7Yj43J_c0PJ65AP9ypcO4ZCA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://mastodon.online&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Tootear. Guardo la salida en la variable toot&lt;/span&gt;
&lt;span class="c1"&gt;# para poder responder despues.&lt;/span&gt;
&lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mastodon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mi primer toot usando la API&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Tootear en modo respuesta. Para identificar a&lt;/span&gt;
&lt;span class="c1"&gt;# que toot responder podria usar directamente la&lt;/span&gt;
&lt;span class="c1"&gt;# variable toot o solo el id que se guarda en toot[&amp;#39;id&amp;#39;]&lt;/span&gt;
&lt;span class="n"&gt;mastodon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Y ahora me estoy respondiendo a mi mismo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_reply_to_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con ésto ya podriamos tootear desde la &lt;span class="caps"&gt;API&lt;/span&gt; cada vez que lo necesitemos, incluir tareas programadas para tootear automáticamente o lo que se nos&amp;nbsp;ocurra.&lt;/p&gt;
&lt;p&gt;Por último, indicar que este ejemplo se ha dejado publicado en el repositorio &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0013-mastodon-bot-python/0013_mastodon-bot-ejemplo.py"&gt;noroute2host-files&lt;/a&gt;.  Y no olvidéis que esta es la primera parte de este  tema y que habrá una segunda mostrando un bot real que esta funcionando&amp;nbsp;actualmente.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joinmastodon.org/"&gt;Página oficial de&amp;nbsp;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mastodon/mastodon"&gt;Repositorio del código oficial de&amp;nbsp;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"&gt;App oficial de&amp;nbsp;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://f-droid.org/es/packages/com.keylesspalace.tusky/"&gt;Tusky. Otro cliente para&amp;nbsp;mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodonpy.readthedocs.io/en/stable/"&gt;Documentación de&amp;nbsp;Mastodon.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/halcy/Mastodon.py"&gt;Repositorio de código de&amp;nbsp;Mastodon.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;El &lt;a href="https://adrianperales.com/2014/05/sobre-redes-libres-y-descentralizadas/"&gt;artículo&lt;/a&gt; de &lt;a href="https://masto.rocks/@aperalesf"&gt;Adrián Perales&lt;/a&gt; que me llevó a las redes sociales&amp;nbsp;libres.&lt;/li&gt;
&lt;li&gt;Y el &lt;a href="https://podcastlinux.com/posts/podcastlinux/86-Podcast-Linux/"&gt;podcast&lt;/a&gt; de &lt;a href="https://podcastlinux.com/"&gt;Podcast Linux&lt;/a&gt; que me lo&amp;nbsp;contó.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodon.social/@noroute2host"&gt;Cuenta de Mastodon de&amp;nbsp;noroute2host&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="mastodon"></category><category term="bot"></category><category term="rrss"></category></entry><entry><title>Aplicaciones libres para Android (II): AntennaPod</title><link href="https://noroute2host.com/antennapod.html" rel="alternate"></link><published>2022-09-30T20:00:00+02:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-09-30:/antennapod.html</id><summary type="html">&lt;p&gt;Segundo artículo de la serie Aplicaciones Libres para Android. Nueve meses han pasado desde el primer articulo de la serie. Ahora traemos AntennaPod, un gestor y reproductor de podcasts brutal con licencia que podemos instalar en nuestros dispositivos Android de manera muy&amp;nbsp;sencilla.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La intención de la sección es mostrar aplicaciones que sean software libre y con las que podamos reemplazar de manera sencilla o con garantías otras que traen nuestros dispositivos Android por defecto, o simplemente que instalamos por que son las más conocidas pese a que sean&amp;nbsp;privativas.&lt;/p&gt;
&lt;h2 id="antennapod"&gt;AntennaPod&lt;/h2&gt;
&lt;p&gt;&lt;img alt="AntennaPod Banner" src="https://noroute2host.com/images/0012_antennapod_logo.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Este es el segundo artículo de la serie Aplicaciones Libres para Android. Nueve meses han pasado desde el primer articulo de la serie. Ahora traemos AntennaPod, un gestor y reproductor de podcasts brutal con licencia que podemos instalar en nuestros dispositivos Android de manera muy&amp;nbsp;sencilla.&lt;/p&gt;
&lt;h3 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt;&amp;nbsp;AntennaPod&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://antennapod.org"&gt;https://antennapod.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/AntennaPod/AntennaPod"&gt;https://github.com/AntennaPod/AntennaPod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Podcasts, Spotify,&amp;nbsp;iVoox&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/app/de.danoeh.antennapod"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=de.danoeh.antennapod&amp;amp;hl=es"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;/h3&gt;
&lt;p&gt;AntennaPod tiene todas las características que puedas esperar de un reproductor y de un gestor de tu colección de&amp;nbsp;podcasts.&lt;/p&gt;
&lt;p&gt;Quizás la función que mas valoro de AntennaPod  es la facilidad para buscar, añadir o importar padcasts a tu colección. Desde el apartado &lt;em&gt;Añadir Podcast&lt;/em&gt;  muestra sugerencias y proporciona un buscador. Pero si aún así no es suficiente, te permite importar podcasts directamente desde su &lt;span class="caps"&gt;RSS&lt;/span&gt;, desde una lista &lt;span class="caps"&gt;OPML&lt;/span&gt;, desde una carpeta local o buscando en otras&amp;nbsp;fuentes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AntennaPod Add Podcast" src="https://noroute2host.com/images/0012_antennapod_addpodcast.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Desde el apartado &lt;em&gt;Suscripciones&lt;/em&gt; puedes gestionar la lista de podcasts a la que estas suscrito, así como comprobar la lista de episodios, consutar si hay nuevos, descargarlos, u otras opciones sobre tus&amp;nbsp;suscripciones.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AntennaPod Suscripciones" src="https://noroute2host.com/images/0012_antennapod_subs.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Otros apartados del menu principal te permiten gestionar la cola de escucha, listar los episodios de los podcasts a los que estas suscrito o gestionar la&amp;nbsp;descargas.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AntennaPod Menu" src="https://noroute2host.com/images/0012_antennapod_menu.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="sincronizacion"&gt;Sincronización&lt;/h3&gt;
&lt;p&gt;Una característica muy interesante de esta aplicación es la posibilidad de usar gpodder.net o gpoddersycn como servicios de&amp;nbsp;sincronización.&lt;/p&gt;
&lt;h3 id="ajustes"&gt;Ajustes&lt;/h3&gt;
&lt;p&gt;La cantidad y variedad de ajustes y personalizaciones que tiene Antennapod es impresionante. Desde los típicos temas oscuro o claro, pasando por el uso de red o la eliminación automática o programada de episodios. Tal es la variedad que te animo a personalizar la aplicación de la forma que más se adapte a&amp;nbsp;ti.&lt;/p&gt;
&lt;h3 id="reproduccion"&gt;Reproducción&lt;/h3&gt;
&lt;p&gt;El reproductor de la app tiene poco de lo que hablar, es simple, cumple su cometido y funciona&amp;nbsp;perfectamente.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AntennaPod Reproductor" src="https://noroute2host.com/images/0012_antennapod_reproductor.jpg" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="android-auto"&gt;Android&amp;nbsp;Auto&lt;/h3&gt;
&lt;p&gt;Además de todo lo anterior, AntennaPod es compatible con Android Auto. Y eso está genial si eres de los que escuchas podcasts cuando vas&amp;nbsp;conduciendo.&lt;/p&gt;
&lt;p&gt;Sinceramente no lo he podido probar porque nunca vi la app en Android Auto debido a que no me aparecía en el menú en el coche. Pero acabo de descubrir mientras redacto el artículo que es porque tengo instalada la versión de F-Droid, y Android Auto por defecto no muestra apps que no se hayan instalado desde Google Play. Para solucionar esto tienes dos&amp;nbsp;opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Activar la opcion de &amp;#8220;Fuentes Desconocidas&amp;#8221; de Android Auto que es independiente de que la tengas activa tu smartphone. Se accede a esta opción desde el modo desarrollador. &lt;a href="https://support.google.com/androidauto/thread/9008705?hl=en"&gt;Aquí&lt;/a&gt; puedes ver cómo acceder a estas&amp;nbsp;opciones.&lt;/li&gt;
&lt;li&gt;Instalar la versión de Google&amp;nbsp;Play.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="una-alternativa-libre-para-tus-podcasts"&gt;Una alternativa libre para tus&amp;nbsp;podcasts&lt;/h3&gt;
&lt;p&gt;AntennaPod no es que sea una alternativa válida para las gestión y reproducción de podcasts, es que probablemente no vayas a en contar ninguna opción mejor. ¿Cómo? ¿Has llegado hasta aquí y todavía no la has&amp;nbsp;instalado?&lt;/p&gt;
&lt;h5 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h5&gt;
&lt;p&gt;Si quieres conocer otras &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; y te has quedado con ganas de más en &lt;a href="https://noroute2host.com/aplicaciones-libres-android.html"&gt;este enlace está el índice con todos los artículos de la&amp;nbsp;serie&lt;/a&gt;&lt;/p&gt;</content><category term="android"></category><category term="software libre"></category><category term="podcast"></category><category term="app"></category></entry><entry><title>Instalación y uso de virtualenv de Python</title><link href="https://noroute2host.com/python-virtualenv.html" rel="alternate"></link><published>2022-07-13T19:00:00+02:00</published><updated>2022-07-13T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-07-13:/python-virtualenv.html</id><summary type="html">&lt;p&gt;Cuando estás desarrollando algo, hay varias tareas relacionadas con el entorno de desarrollo y sus características que suelen ser un quebradero de cabeza bastante importante. Muchas de estas tareas tienen que ver con la posibilidad de tener diferentes entornos donde desarrollar sin afectar a la versión productiva de tu aplicación o con la gestión de dependencias de librerías y versiones de estas. En el caso de Python, &lt;code&gt;virtualenv&lt;/code&gt; te ayuda a lidiar con esta&amp;nbsp;problemática.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuando estás desarrollando algo, hay varias tareas relacionadas con el entorno de desarrollo y sus características que suelen ser un quebradero de cabeza bastante importante. Muchas de estas tareas tienen que ver con la posibilidad de tener diferentes entornos donde desarrollar sin afectar a la versión productiva de tu aplicación o con la gestión de dependencias de librerías y versiones de estas. En el caso de Python, &lt;code&gt;virtualenv&lt;/code&gt; te ayuda a lidiar con esta&amp;nbsp;problemática.&lt;/p&gt;
&lt;h2 id="virtualenv"&gt;Virtualenv&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;virtualenv&lt;/code&gt; es una herramienta que te permite crear entornos Python aislados. No tiene grandes requerimientos y es bastante simple. Como se decía en la introducción nos ayudará a resolver bastantes problemas derivados de la gestión de dependencias o como mecanismo para tener varios entornos de&amp;nbsp;desarrollo.&lt;/p&gt;
&lt;p&gt;Por ejemplo, podemos nombrar un par de casos de&amp;nbsp;uso:&lt;/p&gt;
&lt;p&gt;Imagina que tienes un proyecto en python funcionando de maravilla, pero necesitas actualizar la versión de una librería. Si haces la actualización en el entorno donde está funcionando tu aplicación corres el riesgo de romperla y que no funcione. Pero imagina ahora, que puedes crear un entorno virtual independiente, desplegar tu aplicación, actualizar la librería y probar sin ningún&amp;nbsp;riesgo.&lt;/p&gt;
&lt;p&gt;O por poner otro ejemplo, si quieres tener un entorno de desarrollo independiente dentro del mismo sistema donde tienes tu aplicación, puedes generar un virtualevn diferente, desplegar tu aplicación, y hacer todas las pruebas y cambios que necesites sin poner en riesgo la versión productiva. Así como independizar tu desarrollo de librerías de python que también usa tu Sistema&amp;nbsp;Operativo.&lt;/p&gt;
&lt;h3 id="instalacion-de-virtualenv"&gt;Instalación de&amp;nbsp;Virtualenv&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;virtualenv&lt;/code&gt; se puede instalar de varias&amp;nbsp;maneras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mediante paquetería de tu distribución. El paquete suele llamarse &lt;em&gt;virtualenv&lt;/em&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Debian/Ubuntu Si el paquete se llama virtualenv&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;virtualenv

&lt;span class="c1"&gt;# RedHat/Centos/OracleLinux&lt;/span&gt;
yum&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;virtualenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Si dispones de&amp;nbsp;pipx:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;virtualenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Directamente desde &lt;code&gt;pip&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;virtualenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="usando-virtualenv"&gt;Usando&amp;nbsp;Virtualenv&lt;/h3&gt;
&lt;p&gt;Sea cual sea el método de instalación elegido, vamos a lo importante: a como crear tu entorno virtual y trabajar con&amp;nbsp;él.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para crear un nuevo entorno virtual usando el interprete python por defecto del&amp;nbsp;sistema.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;venv-ejemplo
created&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;CPython3.8.10.final.0-64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;661ms
&lt;span class="w"&gt;  &lt;/span&gt;creator&lt;span class="w"&gt; &lt;/span&gt;CPython3Posix&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/adrimcgrady/python/virtualenvs/venv-ejemplo,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;seeder&lt;span class="w"&gt; &lt;/span&gt;FromAppData&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;setuptools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wheel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pkg_resources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;via&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;app_data_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/adrimcgrady/.local/share/virtualenv/seed-app-data/v1.0.1.debian.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;activators&lt;span class="w"&gt; &lt;/span&gt;BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Para crear un nuevo entorno virtual usando un interprete python&amp;nbsp;personalizado.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/path/a/tu/python&lt;span class="w"&gt; &lt;/span&gt;venv-ejemplo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Activar tu&amp;nbsp;virtualenv:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv-ejemplo/bin/activate
&lt;span class="o"&gt;(&lt;/span&gt;venv-ejemplo&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Ahora ya puedes instalar una librería en ese&amp;nbsp;virtualenv.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;venv-ejemplo&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cowsay
Collecting&lt;span class="w"&gt; &lt;/span&gt;cowsay
&lt;span class="w"&gt;  &lt;/span&gt;Downloading&lt;span class="w"&gt; &lt;/span&gt;cowsay-5.0.tar.gz&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kB&lt;span class="o"&gt;)&lt;/span&gt;
Building&lt;span class="w"&gt; &lt;/span&gt;wheels&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;collected&lt;span class="w"&gt; &lt;/span&gt;packages:&lt;span class="w"&gt; &lt;/span&gt;cowsay
&lt;span class="w"&gt;  &lt;/span&gt;Building&lt;span class="w"&gt; &lt;/span&gt;wheel&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cowsay&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;setup.py&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;Created&lt;span class="w"&gt; &lt;/span&gt;wheel&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cowsay:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cowsay-5.0-py2.py3-none-any.whl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;25707&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4a2f91d17b7de2dc18f8b010af907b056b99573e3af866b87d3eae0c9d067a81
&lt;span class="w"&gt;  &lt;/span&gt;Stored&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;directory:&lt;span class="w"&gt; &lt;/span&gt;/home/adrimcgrady/.cache/pip/wheels/64/ae/fe/f16b584bc50cebd8fc7eb1fa69e96abf8b9024114517874952
Successfully&lt;span class="w"&gt; &lt;/span&gt;built&lt;span class="w"&gt; &lt;/span&gt;cowsay
Installing&lt;span class="w"&gt; &lt;/span&gt;collected&lt;span class="w"&gt; &lt;/span&gt;packages:&lt;span class="w"&gt; &lt;/span&gt;cowsay
Successfully&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;cowsay-5.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Y ahora ya podemos usar &lt;code&gt;cowsay&lt;/code&gt; en nuestro&amp;nbsp;virtualenv.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ejemplo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;adrimcgrady&lt;/span&gt;&lt;span class="nd"&gt;@nanopifire3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;virtualenvs&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="mf"&gt;3.8.10&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Jun&lt;/span&gt;  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GCC&lt;/span&gt; &lt;span class="mf"&gt;9.4.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt;
&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;cowsay&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cowsay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Muuuuuuuu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;_________&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Muuuuuuuu&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="o"&gt;=========&lt;/span&gt;
         \
          \
            &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\&lt;span class="n"&gt;_______&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\       &lt;span class="p"&gt;)&lt;/span&gt;\&lt;span class="o"&gt;/&lt;/span&gt;\
                &lt;span class="o"&gt;||----&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt;     &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Cuando quieras desactivar tu entorno virtual solo tienes que usar la orden &lt;code&gt;deactivate&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;venv-ejemplo&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;deactivate
adrimcgrady@nanopifire3:~/python/virtualenvs$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Hemos salido de nuestro entorno virtual, pero continúa ahí para cuando lo queramos usar. Pero, ahora vamos a comprobar que, efectivamente, cowsay no está instalado en el entorno python por&amp;nbsp;defecto.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;python
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.8.10&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;default,&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2021&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:49:15&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;GCC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.4.0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;linux
Type&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;information.
&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;cowsay
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;module&amp;gt;
ModuleNotFoundError:&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;module&lt;span class="w"&gt; &lt;/span&gt;named&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cowsay&amp;#39;&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Por último si queremos borrar un entorno virtual será tan simple como borrar el directorio donde lo creamos. Por&amp;nbsp;ejemplo:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~/python/virtualenvs$&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;venv-ejemplo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://virtualenv.pypa.io/en/stable/index.html"&gt;Página oficial de&amp;nbsp;Virtualenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/cowsay/"&gt;Página de cowsay en&amp;nbsp;pypi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="python"></category><category term="terminal"></category><category term="linux"></category></entry><entry><title>Codificar y Decodificar Base64 desde la terminal</title><link href="https://noroute2host.com/codificar-decodificar-bases64.html" rel="alternate"></link><published>2022-02-28T21:45:00+01:00</published><updated>2022-02-28T21:45:00+01:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-02-28:/codificar-decodificar-bases64.html</id><summary type="html">&lt;p&gt;Recientemente he tenido que hacer uso de base64 para codificar algunas cadenas de texto. Esto puede ser útil para codificar un fichero de forma básica, convertir una cadena con caracteres no alfabéticos a una cadena que sólo tiene estos caracteres, o incluso para codificar tokens o cadenas de autorización en determinadas APIs. Así que aprovechando eso, os dejo este mini-artículo de como usar el comando base64 desde la&amp;nbsp;terminal.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recientemente he tenido que hacer uso de base64 para codificar algunas cadenas de texto. Esto puede ser útil para codificar un fichero de forma básica, convertir una cadena con caracteres no alfabéticos a una cadena que sólo tiene estos caracteres, o incluso para codificar tokens o cadenas de autorización en determinadas APIs. Así que aprovechando eso, os dejo este mini-artículo de como usar el comando base64 desde la&amp;nbsp;terminal.&lt;/p&gt;
&lt;p&gt;Para ir al grano voy a poner directamente ejemplos de uso del comando base64 en la&amp;nbsp;terminal.&lt;/p&gt;
&lt;h3 id="codificar-una-cadena-en-base64"&gt;Codificar una cadena en&amp;nbsp;base64&lt;/h3&gt;
&lt;p&gt;Esta es la manera en la que personalmente más uso el comando. Sería&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cadena&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, vamos a codificar la cadena&amp;nbsp;&amp;#8220;noroute2host.com&amp;#8221;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noroute2host.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64
&lt;span class="nv"&gt;bm9yb3V0ZTJob3N0LmNvbQ&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En función de lo que quieras hacer es muy importante la opción &lt;code&gt;-n&lt;/code&gt; del &lt;code&gt;echo&lt;/code&gt; puesto que esto evitará que se incluya el salto de línea en la cadena codificada. Esto suele ser un punto de error al codificar tokens o cadenas de autorización de APIs. Si no usamos esta opción la cadena codificada será&amp;nbsp;distinta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noroute2host.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64
&lt;span class="nv"&gt;bm9yb3V0ZTJob3N0LmNvbQo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="decodificar-una-cadena-base64"&gt;Decodificar una cadena&amp;nbsp;base64&lt;/h3&gt;
&lt;p&gt;En este caso hay que usar la opción &lt;code&gt;-d&lt;/code&gt;. El comando quedaría&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cadenaBase64&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Probemos a decodificar las dos cadenas obtenidas en el anterior punto. En este caso dejo el prompt para que se vea la diferencia sin y con salto de&amp;nbsp;línea:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bm9yb3V0ZTJob3N0LmNvbQ==&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d
noroute2host.comadrimcgrady@nanopifire3:~$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adrimcgrady@nanopifire3:~$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bm9yb3V0ZTJob3N0LmNvbQo=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d
noroute2host.com
adrimcgrady@nanopifire3:~$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="codificar-un-fichero-en-base64"&gt;Codificar un fichero en&amp;nbsp;base64&lt;/h3&gt;
&lt;p&gt;Si por el contrario, lo que necesitas es codificar en base64 un fichero completo lo único que debes hacer es pasar el fichero como parámetro al&amp;nbsp;comando.&lt;/p&gt;
&lt;p&gt;La sintaxis sería&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;tufichero.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, si tenemos un fichero llamado &amp;#8220;textoplano.txt&amp;#8221; con el siguiente&amp;nbsp;contenido: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Esto es un fichero en texto plano.
Solo servirá para comprobar como funciona el comando base64.
noroute2host.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Se puede codificar de la siguiente&amp;nbsp;manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt&lt;span class="w"&gt; &lt;/span&gt;
RXN0byBlcyB1biBmaWNoZXJvIGVuIHRleHRvIHBsYW5vLgpTb2xvIHNlcnZpcsOhIHBhcmEgY29t
cHJvYmFyIGNvbW8gZnVuY2lvbmEgZWwgY29tYW5kbyBiYXNlNjQuCm5vcm91dGUyaG9zdC5jb20K
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para guardarlo codificado solo tendremos que usar la redirección adecuada para guardar la salida en un&amp;nbsp;fichero:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt.base64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="decodificar-un-fichero-base64"&gt;Decodificar un fichero&amp;nbsp;base64&lt;/h3&gt;
&lt;p&gt;Por último, si ya tienes un fichero codificado en base64 y lo que te hace falta es decodificarlo es muy simple. Es exactamente igual que para codificar pero usando la opción &lt;code&gt;-d&lt;/code&gt;. Es decir, quedaría&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;tufichero.txt.base64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, para decodificar el fichero del apartado&amp;nbsp;anterior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt.base64
Esto&lt;span class="w"&gt; &lt;/span&gt;es&lt;span class="w"&gt; &lt;/span&gt;un&lt;span class="w"&gt; &lt;/span&gt;fichero&lt;span class="w"&gt; &lt;/span&gt;en&lt;span class="w"&gt; &lt;/span&gt;texto&lt;span class="w"&gt; &lt;/span&gt;plano.
Solo&lt;span class="w"&gt; &lt;/span&gt;servirá&lt;span class="w"&gt; &lt;/span&gt;para&lt;span class="w"&gt; &lt;/span&gt;comprobar&lt;span class="w"&gt; &lt;/span&gt;como&lt;span class="w"&gt; &lt;/span&gt;funciona&lt;span class="w"&gt; &lt;/span&gt;el&lt;span class="w"&gt; &lt;/span&gt;comando&lt;span class="w"&gt; &lt;/span&gt;base64.
noroute2host.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para guardarlo decodificado, como antes, solo habrá que usar la redirección a un fichero&amp;nbsp;nuevo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt.base64&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;textoplano.txt.decodificado
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://es.wikipedia.org/wiki/Base64"&gt;Página de wikipedia sobre&amp;nbsp;Base64&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="base64"></category><category term="bash"></category><category term="linux"></category></entry><entry><title>Aplicaciones libres para Android (I): Etar Calendario.</title><link href="https://noroute2host.com/etar-calendario.html" rel="alternate"></link><published>2022-01-14T17:30:00+01:00</published><updated>2023-06-22T19:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2022-01-14:/etar-calendario.html</id><summary type="html">&lt;p&gt;Después de un tiempo sin nuevos artículos regresamos para inaugurar una sección a la que le tenía bastantes ganas: Aplicaciones Libres para Android. Y para comenzar traemos Etar, un calendario con licencia GPLv3 que cumple perfectamente y podemos instalar en nuestros dispositivos Android de manera muy&amp;nbsp;sencilla.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Después de un tiempo sin nuevos artículos regresamos para inaugurar una sección a la que le tenía bastantes ganas: Aplicaciones Libres para Android. La idea de la sección es traer herramientas que sean software libre y con las que podamos reemplazar de manera sencilla o con garantías otras que traen nuestros dispositivos Android por defecto, o simplemente que instalamos por que son las más conocidas pese a que sean&amp;nbsp;privativas.&lt;/p&gt;
&lt;h2 id="etar-calendario-open-source"&gt;Etar - Calendario Open&amp;nbsp;Source&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Etar - Calendario Open Source" src="https://noroute2host.com/images/0009_etar_banner.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Para comenzar esta nueva sección traemos Etar, un calendario con licencia GPLv3 que cumple perfectamente y podemos instalar en nuestros dispositivos Android de manera muy sencilla. De hecho, Etar es simple y directamente una versión mejorada del calendario por defecto del proyecto &lt;span class="caps"&gt;AOSP&lt;/span&gt; (Android Open Source&amp;nbsp;Project).&lt;/p&gt;
&lt;h3 id="ficha-de-la-app"&gt;Ficha de la&amp;nbsp;App&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nombre App:&lt;/strong&gt; Etar - OpenSource&amp;nbsp;Calendar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Página del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/Etar-Group/Etar-Calendar"&gt;https://github.com/Etar-Group/Etar-Calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repositorio del proyecto:&lt;/strong&gt; &lt;a href="https://github.com/Etar-Group/Etar-Calendar"&gt;https://github.com/Etar-Group/Etar-Calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licencia:&lt;/strong&gt;&amp;nbsp;GPLv3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternativa Libre a&amp;#8230;:&lt;/strong&gt; Google Calendar, Otros calendarios cloud o locales de los&amp;nbsp;fabricantes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obtener desde F-Droid o Google Play:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://f-droid.org/es/packages/ws.xsoh.etar"&gt;&lt;img src="/images/0000_comun_badge_fdroid_es.png" alt="Obtener en F-Droid" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar&amp;amp;hl=es"&gt;&lt;img src="/images/0000_comun_badge_google_play_es.png" alt="Obtener en Google Play" style="display:inline-block; height:80px"&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;/h3&gt;
&lt;p&gt;Etar dispone de las opciones que puedes esperar de cualquier aplicación de calendario como pueden ser las vistas varias de Día, Mes, Semana o Agenda. Esta última es muy interesante porque en dicha vista solo aparecen los días que tienes registrados eventos. Además también dispondrás de un widget para la vista de&amp;nbsp;agenda.&lt;/p&gt;
&lt;p&gt;Además a nivel gráfico te permite personalizar la aplicación haciendo uso de modo oscuro, o cambiando el color principal de la aplicación, así como de los eventos de cada calendario para que puedas distinguir rápidamente de donde&amp;nbsp;vienen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vistas Calendarios Etar" src="https://noroute2host.com/images/0009_etar_capturas001.png" style="display:block"&gt;&lt;br&gt;
&lt;img alt="Vistas Calendarios Etar" src="https://noroute2host.com/images/0009_etar_capturas002.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Al crear un evento puedes elegir sobre cual de tus calendarios quieres crearlo, definir el color del evento, establecer varios recordatorios y su tipo, email o notificación. El aviso por email solo está disponible para calendarios tipo cloud como es&amp;nbsp;lógico.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Creación Evento Etar" src="https://noroute2host.com/images/0009_etar_capturas003.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Pero las características más interesantes de Etar tiene que ver con la sincronización o no de calendarios. Porque tienes múltiples&amp;nbsp;opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para empezar puede sincronizar con múltiples proveedores de calendarios como Google Calendar, Exchange,&amp;nbsp;etc&amp;#8230;&lt;/li&gt;
&lt;li&gt;Pero no solo eso, es que puedes integrarlo tu propio servicio de calendarios CalDAV o EteSync. Esto hace la aplicación tremendamente útil para sincronizar con tu propio servicio y no depender o tener la información en&amp;nbsp;otros.&lt;/li&gt;
&lt;li&gt;Y la opción más simple pero muchas veces la mejor para mantener tu privacidad, puedes crear y usar calendarios completamente&amp;nbsp;offline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Creación Evento Etar" src="https://noroute2host.com/images/0009_etar_capturas004.png" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="una-alternativa-libre-para-tu-aplicacion-de-calendario"&gt;Una alternativa libre para tu aplicación de&amp;nbsp;calendario&lt;/h3&gt;
&lt;p&gt;Etar dispone de las opciones necesarias para ser tu aplicación de calendario por defecto y es de código abierto. Ten por seguro que no echarás de menos la aplicación propietaria que seguro que trae tu smartphone por defecto y además no contiene anuncios ni permisos innecesarios. ¿A qué esperas para&amp;nbsp;probarla?&lt;/p&gt;
&lt;h5 id="aplicaciones-libres-para-android"&gt;Aplicaciones Libres para&amp;nbsp;Android&lt;/h5&gt;
&lt;p&gt;Si quieres conocer otras &lt;em&gt;Aplicaciones Libres para Android&lt;/em&gt; y te has quedado con ganas de más en &lt;a href="https://noroute2host.com/aplicaciones-libres-android.html"&gt;este enlace está el índice con todos los artículos de la&amp;nbsp;serie&lt;/a&gt;&lt;/p&gt;</content><category term="android"></category><category term="software libre"></category><category term="calendario"></category><category term="app"></category></entry><entry><title>Script Multiping para Bash</title><link href="https://noroute2host.com/multiping-bash-script.html" rel="alternate"></link><published>2021-11-06T14:15:00+01:00</published><updated>2022-10-03T21:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-11-06:/multiping-bash-script.html</id><summary type="html">&lt;p&gt;Aprovechando temas tratados anteriormente os mostraré un script para hacer un multiping o ping múltiple a una lista de ips. Estas ips pueden venir de un fichero de texto y de parsear la salida del comando &lt;code&gt;prips&lt;/code&gt;. Es cierto que hay herramientas alternativas para esto pero con nuestro script no necesitarás instalar nada en tu sistema linux o unix, solo copiar y ejecutarlo. Además, podrás adaptarlo a tus necesidades&amp;nbsp;fácilmente.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Aprovechando temas tratados anteriormente os mostraré un script para hacer un multiping o ping múltiple a una lista de ips. Estas ips pueden venir de un fichero de texto y de parsear la salida del comando &lt;code&gt;prips&lt;/code&gt;. Es cierto que hay herramientas alternativas y que se suelen usar para esto como &lt;code&gt;nmap&lt;/code&gt;, &lt;code&gt;fping&lt;/code&gt; u otras, pero con nuestro script no necesitarás instalar nada en tu servidor o equipo, solo copiar y ejecutarlo. Y además podrás adaptarlo a tus necesidades fácilmente. Además, podrás adaptarlo a tus necesidades y nos servirá para aplicar soluciones vistas aquí&amp;nbsp;anteriormente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://noroute2host.com/color-bash.html"&gt;Da color a tus scripts en&amp;nbsp;Bash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://noroute2host.com/prips.html"&gt;El comando prips. Obtén y trabaja con la lista de ips de una red en tu&amp;nbsp;terminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/noroute2host/noroute2host-files"&gt;Repositorio público Noroute2host Files en&amp;nbsp;GitLab&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="multiping_bashsh"&gt;multiping_bash.sh&lt;/h2&gt;
&lt;p&gt;multiping_bash.sh, más bien, 0008_multiping_bash.sh será nuestro script. Y como hemos dicho antes lo tenéis disponible en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0008_script_multiping_bash/0008_multiping_bash.sh"&gt;repositorio&lt;/a&gt;. Básicamente lo que hace el script es, a partir de una lista de ips, comprobar cuales responden a ping y cuales no, mostrando un reporte&amp;nbsp;final.&lt;/p&gt;
&lt;h3 id="usando-multiping_bashsh"&gt;Usando&amp;nbsp;multiping_bash.sh&lt;/h3&gt;
&lt;p&gt;La manera de ejecutar el script sería como la de cualquier otro, así que simplemente se copiaría al sistema donde se quiera ejecutar y se lanzaría con &lt;code&gt;./0008_multiping_bash.sh&lt;/code&gt;, &lt;code&gt;bash 0008_multiping_bash&lt;/code&gt; o la manera que cada uno&amp;nbsp;prefiera.&lt;/p&gt;
&lt;p&gt;Pero hay que tener en cuenta que tiene dos modos de funcionamiento dependiendo de como se reciba la lista de&amp;nbsp;ips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vía pipe y el comando &lt;code&gt;prips&lt;/code&gt;. Por ejemplo: &lt;code&gt;prips RED/MASCARA | ./0008_multiping_bash.sh [-r] [-d]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mediante un fichero de ips. Por ejemplo: &lt;code&gt;/0008_multiping_bash.sh -f FICHERO-IPS [-r] [-d]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A continuación se describen las opciones del&amp;nbsp;script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: Si no se pasan las ips vía pipe y &lt;code&gt;prips&lt;/code&gt; esta opción es obligatoria. Así como también es obligatorio indicar una ruta a un fichero de ips que tenga una ip por linea. Puedes consultar un ejemplo &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0008_script_multiping_bash/0008_ejemplo_ips.txt"&gt;aquí&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r&lt;/code&gt;: Parámetro opcional. Se usa para habilitar que el script muestre un reporte&amp;nbsp;final.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Parámetro opcional. Se usa para habilitar la salida de&amp;nbsp;depuración.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt;: Muestra ejemplos de&amp;nbsp;uso.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ejemplos-de-uso"&gt;Ejemplos de&amp;nbsp;uso&lt;/h3&gt;
&lt;p&gt;A continuación os dejo varios ejemplos de uso, así como su&amp;nbsp;salida. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Usar prips para pasar la salida al script de multiping y mostrar reporte final.&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.32/29&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./0008_multiping_bash.sh&lt;span class="w"&gt; &lt;/span&gt;-r
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="multiping con prips" src="https://noroute2host.com/images/0008_multiping_bash_script_001.gif" style="display:block"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Usar un fichero de ips para el script de multiping y mostrar reporte final.&lt;/span&gt;
./0008_multiping_bash.sh&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;0008_ejemplo_ips.txt&lt;span class="w"&gt; &lt;/span&gt;-r
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="multiping con fichero entrada" src="https://noroute2host.com/images/0008_multiping_bash_script_002.gif" style="display:block"&gt;&lt;/p&gt;
&lt;h3 id="codigo-fuente-del-script"&gt;Código fuente del&amp;nbsp;script&lt;/h3&gt;
&lt;p&gt;Como ya os he comentado el script de multiping, así como todos los del blog, lo tenéis en el &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/"&gt;repositorio Noroute2host Files&lt;/a&gt;. Así que mejor que poner por aquí el código, podéis verlo directamenre &lt;a href="https://gitlab.com/noroute2host/noroute2host-files/-/blob/main/0008_script_multiping_bash/0008_multiping_bash.sh"&gt;aquí&lt;/a&gt; y echarle un ojo. ¡Y por supuesto es mejorable, modificable, ampliable y lo que se os&amp;nbsp;ocurra!&lt;/p&gt;</content><category term="scripts"></category><category term="linux"></category><category term="unix"></category><category term="terminal"></category><category term="bash"></category></entry><entry><title>Exportar y dejar de exportar un recurso NFS de manera manual con el comando exportfs</title><link href="https://noroute2host.com/nfs-exportfs-manualmente.html" rel="alternate"></link><published>2021-10-20T18:30:00+02:00</published><updated>2021-10-20T18:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-10-20:/nfs-exportfs-manualmente.html</id><summary type="html">&lt;p&gt;Habitualmente para exportar o dejar de exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; lo normal es proceder a la modificación del fichero &lt;code&gt;/etc/exports&lt;/code&gt; para después acabar haciendo un &lt;code&gt;exportfs -a&lt;/code&gt; o incluso un reload del demonio &lt;span class="caps"&gt;NFS&lt;/span&gt;. Pero recientemente me he visto en la obligación de realizar esto sin tener que asumir el riesgo de afectar al resto de shares. Es decir, lo que os cuento a continuación sirve para exportar/dejar de exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; de manera&amp;nbsp;manual.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Habitualmente para exportar o dejar de exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; lo normal es proceder a la modificación del fichero &lt;code&gt;/etc/exports&lt;/code&gt; para después acabar haciendo un &lt;code&gt;exportfs -a&lt;/code&gt; o incluso un reload del demonio &lt;span class="caps"&gt;NFS&lt;/span&gt;. Pero recientemente me he visto en la obligación de realizar esto sin tener que asumir el riesgo de afectar al resto de shares. Es decir, lo que os cuento a continuación sirve para exportar/dejar de exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; de manera manual y no persistente ante reinicios del servicio &lt;span class="caps"&gt;NFS&lt;/span&gt; o del sistema&amp;nbsp;completo.&lt;/p&gt;
&lt;h3 id="exportar-manualmente-un-directorio-por-nfs"&gt;Exportar manualmente un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Para exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; de manera manual usaremos también el comando &lt;code&gt;exportfs&lt;/code&gt; pero de distinto modo a si quisiéramos que leyera el fichero &lt;code&gt;/etc/exports&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La sintaxis sería&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exportfs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;OPCIONES&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;CLIENTE:/ruta/a/exportar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, el siguiente comando exporta por &lt;span class="caps"&gt;NFS&lt;/span&gt; el directorio /home/testdir al sistema 192.168.0.88 con la opción de&amp;nbsp;lectura/escritura:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exportfs&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;rw&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.88:/home/testdir
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="dejar-de-exportar-manualmente-un-directorio-por-nfs"&gt;Dejar de exportar manualmente un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Si por el contrario, lo que se necesita es dejar de exportar un directorio por &lt;span class="caps"&gt;NFS&lt;/span&gt; de manera manual seguiremos usando &lt;code&gt;exportfs&lt;/code&gt;, pero esta vez con la opción &lt;code&gt;-u&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;La sintaxis sería&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exportfs&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;CLIENTE:/ruta/a/exportar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, el siguiente comando deja de exportar por &lt;span class="caps"&gt;NFS&lt;/span&gt; el directorio /home/testdir al sistema&amp;nbsp;192.168.0.88:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exportfs&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.88:/home/testdir
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="comprobar-que-la-exportacion-o-le-eliminacion-del-export-se-ha-realizado-correctamente"&gt;Comprobar que la exportación o le eliminación del export se ha realizado&amp;nbsp;correctamente.&lt;/h3&gt;
&lt;p&gt;Para comprobar el estado de nuestras exportaciones &lt;span class="caps"&gt;NFS&lt;/span&gt; podemos usar cualquiera de los siguientes métodos para ver el listado de las exportaciones&amp;nbsp;actuales:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Listar todos los recursos exportados por &lt;span class="caps"&gt;NFS&lt;/span&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exportfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Consultar la tabla maestra de recursos&amp;nbsp;exportados:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/var/lib/nfs/etab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://es.wikipedia.org/wiki/Network_File_System"&gt;Página de wikipedia sobre &lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://manpages.ubuntu.com/manpages/focal/man8/exportfs.8.html"&gt;Página de man de exportfs en Ubuntu&amp;nbsp;20.04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="nfs"></category><category term="linux"></category><category term="unix"></category></entry><entry><title>El comando watch</title><link href="https://noroute2host.com/watch.html" rel="alternate"></link><published>2021-10-12T16:00:00+02:00</published><updated>2021-10-12T16:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-10-12:/watch.html</id><summary type="html">&lt;p&gt;¿No os ha pasado nunca que al lanzar un comando que tarda un poquito en ejecutarse os habéis abierto otra sesión y lanzado un comando de comprobación tropecientas veces pulsando ↑ + [Enter]? Pues a mí me pasaba pero ya no. Bueno lo mismo es que soy un poco agonía pero el asunto es que hay una pequeña utilidad que te evitará esto y además te valdrá para otras circunstancias. Esta utilidad es &lt;code&gt;watch&lt;/code&gt;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿No os ha pasado nunca que al lanzar un comando que tarda un poquito en ejecutarse os habéis abierto otra sesión y lanzado un comando de comprobación tropecientas veces pulsando ↑ + [Enter]? Pues a mí me pasaba pero ya no. Bueno lo mismo es que soy un poco agonía pero el asunto es que hay una pequeña utilidad que te evitará esto y además te valdrá para otras&amp;nbsp;circunstancias.&lt;/p&gt;
&lt;h2 id="el-comando-watch"&gt;El comando&amp;nbsp;watch&lt;/h2&gt;
&lt;p&gt;Efectivamente, esta utilidad se llama &lt;code&gt;watch&lt;/code&gt; y te ahorrará lanzar cualquier comando de manera repetida. Este sencilla herramienta lo que hace es básicamente lanzar de manera periódica el comando que le digamos y en el intervalo que le&amp;nbsp;digamos.&lt;/p&gt;
&lt;p&gt;Es decir, podemos estar copiando un fichero de una ubicación a otra, y en vez estar continuamente comprobando el fichero destino para ver como va la copia, podemos decirle a &lt;code&gt;watch&lt;/code&gt; que nos lance un &lt;code&gt;ls -l&lt;/code&gt; del fichero destino cada 5&amp;nbsp;segundos.&lt;/p&gt;
&lt;h3 id="usando-watch"&gt;Usando&amp;nbsp;watch&lt;/h3&gt;
&lt;p&gt;Comenzando con su instalación (si es que tu distribución no lo tiene instalado por defecto), hay que decir que es bastante sencilla. En Debian, Ubuntu o derivados es tan simple&amp;nbsp;como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;watch&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalada, la herramienta es muy sencilla de usar. Su uso básico podría ser&amp;nbsp;así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;watch&lt;span class="w"&gt; &lt;/span&gt;COMANDO_A_EJECUTAR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Además, el comando &lt;code&gt;watch&lt;/code&gt; tiene algunas opciones más que interesantes que lo hacen muy configurable dentro de su&amp;nbsp;simplicidad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-n&lt;/code&gt;: Establece el intervalo con que se ejecuta el comando. Si no se establece, por defecto es 2 segundos (por lo menos en mi&amp;nbsp;distro).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Resalta las diferencias de las sucesivas ejecuciones, muy útil la&amp;nbsp;verdad.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-b&lt;/code&gt;: Emitirá un &lt;em&gt;beep&lt;/em&gt; si el comando lanzado no tiene código de salida&amp;nbsp;0. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hay algunas opciones más como hacer que el comando &lt;code&gt;watch&lt;/code&gt; deje de ejecutarse cuando haya un cambio en la salida, o congelar la salida si el comando devuelve un error o alguna más. Te animo a que le des un vistazo al resto de&amp;nbsp;opciones.&lt;/p&gt;
&lt;h3 id="ejemplos-de-uso"&gt;Ejemplos de&amp;nbsp;uso&lt;/h3&gt;
&lt;p&gt;Como os podéis imaginar podemos usar este comando un multitud de ocasiones y con múltiples objetivos. A continuación muestro algunos ejemplos simples de su uso y su funcionamiento. Para ello combinaremos el comando &lt;code&gt;watch&lt;/code&gt; con &lt;code&gt;date&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ejecutar y mostrar la salida de un comando usando las opciones por defecto.&lt;/span&gt;
watch&lt;span class="w"&gt; &lt;/span&gt;date
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Comando watch defecto" src="https://noroute2host.com/images/0006_watch_001.gif" style="display:block"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ejecutar y mostrar la salida de un comando cada 3 segundos.&lt;/span&gt;
watch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;date
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Comando watch intervalo 3 segundos" src="https://noroute2host.com/images/0006_watch_002.gif" style="display:block"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ejecutar y mostrar la salida de un comando cada 3 segundos y resaltando las diferencias.&lt;/span&gt;
watch&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;date
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Comando watch intervale 3 segundos" src="https://noroute2host.com/images/0006_watch_003.gif" style="display:block"&gt;&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://manpages.debian.org/bullseye/procps/watch.1.en.html"&gt;Página de man de watch en Debian&amp;nbsp;Bullseye&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/procps-ng/procps"&gt;Código Fuente del proyecto procps que incluye a&amp;nbsp;watch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="bash"></category><category term="linux"></category><category term="comandos"></category></entry><entry><title>Actualizando la versión de InfiniTime de PineTime</title><link href="https://noroute2host.com/actualizando-pinetime-infinitime.html" rel="alternate"></link><published>2021-10-02T17:30:00+02:00</published><updated>2021-10-02T17:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-10-02:/actualizando-pinetime-infinitime.html</id><summary type="html">&lt;p&gt;Como ya os conté en el &lt;a href="https://noroute2host.com/pinetime.html"&gt;artículo anterior&lt;/a&gt;, el firmware que estoy usando en mi PineTime es Infinitime. En este artículo os voy a explicar como actualizar la versión de InfiniTime de vuestro PineTime. En concreto, os contaré como actualizar a la versión 1.6 desde cualquier otra versión de InfiniTime 1.0 o superior haciendo uso de GadgetBridge. En cualquier caso, debería funcionar también para cualquier versión superior salvo que en un futuro cambie el mecanismo de&amp;nbsp;actualización.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Como ya os conté en el &lt;a href="https://noroute2host.com/pinetime.html"&gt;artículo anterior&lt;/a&gt;, el firmware que estoy usando en mi PineTime es Infinitime. En este artículo os voy a explicar como actualizar la versión de InfiniTime de vuestro PineTime. En concreto, os contaré como actualizar a la versión 1.6 desde cualquier otra versión de InfiniTime 1.0 o superior haciendo uso de GadgetBridge. En cualquier caso, debería funcionar también para cualquier versión superior salvo que en un futuro cambie el mecanismo de&amp;nbsp;actualización.&lt;/p&gt;
&lt;h2 id="cambios-en-las-versiones-15-y-16-de-infinitime"&gt;Cambios en las versiones 1.5 y 1.6 de&amp;nbsp;Infinitime&lt;/h2&gt;
&lt;p&gt;De manera previa a contaros el proceso de actualización, os muestro cuales son las novedades de las versiones 1.5 y 1.6. Sí, habéis leído bien, de ambas. La comunidad de InfiniTime está haciendo un trabajo fantástico y menos de un mes después de la versión 1.4 que es sobre la que os conté mis impresiones, ya tenia lista la versión 1.5. Pero no se quedó ahí, sino que ¡dos días! después ya estaba publicada la versión 1.6. Estas son las mejoras implementadas en estas&amp;nbsp;versiones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;InfiniTime 1.5 &amp;#8220;Huckleberry&amp;#8221;:&lt;ul&gt;
&lt;li&gt;Nueva aplicación de&amp;nbsp;Alarma.&lt;/li&gt;
&lt;li&gt;Mejoras en las vibraciones de las notificaciones. Ahora cuando desactivas la vibración, se desactiva de las notificaciones pero si usas el metrónomo por ejemplo el reloj sigue haciendo uso de la función de&amp;nbsp;vibración.&lt;/li&gt;
&lt;li&gt;Ahora si reinicias tu PineTime conserva la hora tras el reinicio. Antes tras reiniciar la hora se quedaba en 00:00 si no resincronizabas con la&amp;nbsp;aplicación.&lt;/li&gt;
&lt;li&gt;Mejoras en el código &lt;span class="caps"&gt;BLE&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Correcciones sobre fiabilidad del medidor de batería. Es algo de lo que hablamos en el artículo anterior. La medición de la batería no era muy&amp;nbsp;fiable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;InfiniTime 1.6 &amp;#8220;Ice Apple&amp;#8221;:&lt;ul&gt;
&lt;li&gt;Se ha corregido el principal problema que tenia InfiniTime, las desconexiones del bluetooth y la imposibilidad de recuperar la conectividad sin&amp;nbsp;reiniciar.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como podéis ver son bastantes mejoras y bastante importantes. De hecho, ya os hablé sobre ellas en el &lt;a href="https://noroute2host.com/pinetime"&gt;artículo de impresiones de PineTime&lt;/a&gt;. El trabajo de la comunidad está siendo impresionante y no me canso de&amp;nbsp;repetirlo.&lt;/p&gt;
&lt;h2 id="actualizacion-de-infinitime-mediante-gadgetbridge"&gt;Actualización de InfiniTime mediante&amp;nbsp;GadgetBridge.&lt;/h2&gt;
&lt;h3 id="consideraciones-previas"&gt;Consideraciones&amp;nbsp;Previas&lt;/h3&gt;
&lt;p&gt;La actualización la vamos a realizar haciendo uso de &lt;a href="https://gadgetbridge.org/"&gt;GadgetBridge&lt;/a&gt;, así que lo primero que debéis hacer es instalar la aplicación en vuestro móvil Android desde &lt;a href="https://f-droid.org"&gt;F-Droid&lt;/a&gt; y emparejar vuestro&amp;nbsp;PineTime.&lt;/p&gt;
&lt;p&gt;Es importante también que tengamos el bootloader correcto instalado. Si tenéis la versión 1.x de InfiniTime actualmente no será necesario que hagáis nada. En otro caso, aquí tenéis la &lt;a href="https://github.com/JF002/pinetime-mcuboot-bootloader"&gt;&lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/a&gt; del proyecto del bootloader. Pero lo normal es que no haga falta este&amp;nbsp;paso.&lt;/p&gt;
&lt;p&gt;Suele ser recomendable no actualizar el firmware con la batería llena porque en caso de que se quede bloqueada la actualización o el reloj, habrá que esperar hasta que se acabe la batería para poder&amp;nbsp;re-arrancarlo.&lt;/p&gt;
&lt;h3 id="ficheros-necesarios-para-la-actualizacion"&gt;Ficheros necesarios para la&amp;nbsp;actualización.&lt;/h3&gt;
&lt;p&gt;Puedes encontrar todas las versiones disponibles de InfiniTime en el apartado de &lt;a href="https://github.com/JF002/InfiniTime/releases"&gt;&amp;#8220;Releases&amp;#8221; del proyecto en GitHub&lt;/a&gt;. Mas en concreto en el apartado de &lt;em&gt;Assets&lt;/em&gt; de cada versión. El único fichero que necesitas para la actualización vía GadgetBridge es el fichero &lt;em&gt;dfu&lt;/em&gt;. Por ejemplo, este es el fichero &lt;em&gt;dfu&lt;/em&gt; de la versión 1.6: &lt;a href="https://github.com/JF002/InfiniTime/releases/download/1.6.0/pinetime-mcuboot-app-dfu-1.6.0.zip"&gt;https://github.com/&lt;span class="caps"&gt;JF002&lt;/span&gt;/InfiniTime/releases/download/1.6.0/pinetime-mcuboot-app-dfu-1.6.0.zip&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="actualizando"&gt;Actualizando&lt;/h3&gt;
&lt;p&gt;Para comenzar la actualización lo único que debes hacer es abrir el fichero &lt;em&gt;dfu&lt;/em&gt; en tu móvil Android indicándole que se abra con el actualizador de firmware de GadgetBridge tal y como muestra la siguiente&amp;nbsp;imagen: &lt;/p&gt;
&lt;p&gt;&lt;img alt="Abrir fichero dfu" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_001.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Una vez abierto el fichero con GadgetBridge solo hay que pulsar en&amp;nbsp;instalar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Empezar actualización" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_002.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y la actualización de InfiniTime comenzará inmediatamente. Y podremos ver el progreso tanto en&amp;nbsp;GadgetBridge&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Actualizando InfiniTime" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_003.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y cuando acabe GadgetBridge nos&amp;nbsp;avisará&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Abrir fichero dfu" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_004.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Así como en el propio&amp;nbsp;PineTime&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Actualizando PineTime" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_005.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Y nuestro reloj se reiniciará con la nueva versión. Y si todo ha ido bien deberíamos validar la actualización desde el apartado &lt;em&gt;firmware&lt;/em&gt; como podéis ver a&amp;nbsp;continuación:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Abrir fichero dfu" src="https://noroute2host.com/images/0005_pinetime-02-upgradefw_006.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Si algo ha ido mal, bastará con pulsar en &lt;em&gt;Reset&lt;/em&gt;, y en vez de validar la actualización lo que haremos será volver a la versión&amp;nbsp;anterior.&lt;/p&gt;
&lt;p&gt;Y eso es todo, así que el proceso de actualización es bastante sencillo. Así que no debéis tener miedo a las actualizaciones y a seguir el fantástico ritmo de actualizaciones que lleva la comunidad de&amp;nbsp;InfiniTime.&lt;/p&gt;
&lt;p&gt;Además si con esta explicación no tenéis suficiente u os gusta más el formato vídeo os recomiendo el siguiente &lt;a href="https://fediverse.tv/c/pinetime/videos"&gt;canal de PineTime&lt;/a&gt; en &lt;a href="https://fediverse.tv"&gt;FediverseTV&lt;/a&gt;. En el tenéis bastantes vídeos de PineTime así como vídeos de las actualizaciones a las diferentes versiones de&amp;nbsp;InfiniTime.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pine64.org/pinetime/"&gt;Página oficial de&amp;nbsp;Pinetime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JF002/InfiniTime"&gt;Repositorio en GitHub de&amp;nbsp;InfiniTime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodon.codingfield.com/@JF"&gt;Cuenta de Mastodon del desarrollador de&amp;nbsp;Infinitime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fediverse.tv/c/pinetime/videos"&gt;Canal de PineTime de Don T3rr0rz0n3 en&amp;nbsp;FediverseTV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="pinetime"></category><category term="software libre"></category><category term="hardware libre"></category><category term="infinitime"></category></entry><entry><title>Probando PineTime, el reloj libre.</title><link href="https://noroute2host.com/pinetime.html" rel="alternate"></link><published>2021-09-25T17:30:00+02:00</published><updated>2021-10-12T16:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-09-25:/pinetime.html</id><summary type="html">&lt;p&gt;PineTime es el reloj de software y hardware abierto. En este artículo no se hablará de cuanto pesa, de que tamaño es su pantalla o cualquier otra característica de ese tipo. Aquí vamos a hablar de que es PineTime, de que va y como&amp;nbsp;funciona.&lt;/p&gt;</summary><content type="html">&lt;h2 id="que-es-pinetime"&gt;¿Que es&amp;nbsp;PineTime?&lt;/h2&gt;
&lt;p&gt;Antes de hablar del PineTime, lo primero que debéis saber es que en este artículo no se hablará de cuanto pesa, de que tamaño es su pantalla o cualquier otra característica de ese tipo. Aquí vamos a hablar de que es PineTime, de que va y como&amp;nbsp;funciona.&lt;/p&gt;
&lt;p&gt;El reloj nos llega de la mano de &lt;span class="caps"&gt;PINE64&lt;/span&gt;. Y partiendo de lo anterior, lo primero que hay que decir de PineTime es que es un reloj inteligente basado en software y hardware libre. Y es este concepto el que lo hace interesante para mí. Además os indico que os voy a hablar de la versión &amp;#8220;sellada&amp;#8221; que es la que se puso a la venta este verano y la que estoy&amp;nbsp;probando.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Unboxing PineTime" src="https://noroute2host.com/images/0004_pinetime_001.png" style="display:block"&gt;&lt;/p&gt;
&lt;h2 id="que-si-y-que-no-esperar-de-pinetime"&gt;¿Qué sí y qué no esperar de&amp;nbsp;PineTime?&lt;/h2&gt;
&lt;p&gt;Esto es quizás lo más importante a la otra de evaluar PineTime. De este reloj te puedes esperar que te de la hora, que tenga algunas funciones inteligentes, que vaya creciendo a la vez que su comunidad de desarrolladores o que su concepto libre sea el corazón del&amp;nbsp;mismo.&lt;/p&gt;
&lt;p&gt;Por otro lado hay cosas que no debes esperar cuando recibes tu flamante reloj, como que te espíe, tenga una integración perfecta con tu aplicación deportiva preferida o unas funciones avanzadas de la&amp;nbsp;leche. &lt;/p&gt;
&lt;h2 id="y-que-es-infinitime"&gt;¿Y qué es&amp;nbsp;InfiniTime?&lt;/h2&gt;
&lt;p&gt;PineTime, Infinitime&amp;#8230; ¿Qué es esto? Pues muy fácil: Si PineTime es el reloj, InfiniTime es el Firmware más activo que le puedes instalar. De hecho es el que viene preinstalado en la versión &amp;#8220;sellada&amp;#8221;. En concreto, recibí mi PineTime con la versión 1.2 de InfiniTime y nada más llegarme lo actualicé a la versión 1.4 que ya estaba disponible &lt;em&gt;(Y ahora ya lo tengo en la versión 1.6)&lt;/em&gt;. En &lt;a href="https://github.com/JF002/InfiniTime"&gt;su repositorio de GitHub&lt;/a&gt; puedes estar al día de como va cambiando y de las nuevas releases. Y la verdad es que tienen bastante buen ritmo de&amp;nbsp;actualizaciones.&lt;/p&gt;
&lt;h2 id="lo-tengo-lo-uso-a-diario-y-esto-es-lo-que-os-puedo-contar"&gt;Lo tengo, lo uso a diario y esto es lo que os puedo&amp;nbsp;contar&lt;/h2&gt;
&lt;p&gt;Como ya os he contado, tengo un PineTime en mi muñeca funcionando con la versión 1.4 de InfiniTime. Y os voy a contar que me está pareciendo, como está funcionando, así como que me gusta y que echo de&amp;nbsp;menos.&lt;/p&gt;
&lt;p&gt;En primer lugar, la estética es bastante sencilla y no destaca por nada en concreto pero tengo que decir que no es feo. Es sobrio, sin alardes. No me esperaba más y la verdad es que tengo que reconocer que me gusta. Queda bien en la muñeca y los materiales no decepcionan y la pantalla cumple bastante&amp;nbsp;bien.&lt;/p&gt;
&lt;p&gt;El tema o &amp;#8220;watch face&amp;#8221; que estoy usando es simplemente el Digital. Me gustaría que se pudiera personalizar más pero es lo que hay. Aunque hay que decir que hay uno más personalizable llamado &amp;#8220;PineTimeStyle&amp;#8221;, pero no me acaba de convencer, prefiero el Digital&amp;nbsp;simple.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tema PineTime" src="https://noroute2host.com/images/0004_pinetime_002.png" style="display:block"&gt;&lt;/p&gt;
&lt;p&gt;Dejando a un lado lo estético me centraré en el funcionamiento y empezaré por la integración con mi móvil Android. La realiza mediante la aplicación &lt;a href="https://gadgetbridge.org/"&gt;GadgetBridge&lt;/a&gt; que puedes encontrar en F-Droid. La puesta en marcha es bastante sencilla y esta misma aplicación es la que se usa para actualizar el firmware. &lt;s&gt;El problema es que pasadas unas horas, en torno a 20, el reloj se desconecta de la aplicación y para reconectarlo es necesario reiniciar el reloj, y eliminarlo y emparejarlo de nuevo en GadgetBridge. La buena noticia es que hay un problema reportado en GitHub al respecto y esperemos que quede arreglado en sucesivas revisiones.&lt;/s&gt; &lt;em&gt;El problema de la sincronización y desconexión del Bluetooth se ha solventado en InfiniTime 1.6 gracias al impresionante trabajo de la comunidad.&lt;/em&gt; También puedes integrarlo con tu &lt;span class="caps"&gt;PC&lt;/span&gt; con linux mediante Siglo o Amazfish pero personalmente no las he&amp;nbsp;probado.&lt;/p&gt;
&lt;p&gt;Otra de las cosas que está en desarrollo es que no se puede establecer la hora de manera manual, debe ser mediante sincronización con tu teléfono o tu &lt;span class="caps"&gt;PC&lt;/span&gt;. Por lo que el uso de las aplicaciones anteriormente citadas se hace indispensable. &lt;em&gt;Desde la versión 1.5 de InfiniTime la hora se guarda entre&amp;nbsp;reinicios&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Las notificaciones funcionan bastante bien la verdad. InfiniTime mantiene las últimas 5 para que puedas verlas directamente desde el reloj. Y con GadgetBridge puedes hacer una lista negra de que notificaciones no lleguen a tu teléfono. Es decir, puedes hacer que las notificaciones de recepción de un email te lleguen al reloj, pero que la previsión del tiempo se quede en tu móvil. &lt;s&gt;Aquí la única pega es que no se si es cosa mía o no, pero las llamadas no se notifican en el reloj. Y eso pese a que con GadgetBridge puedo hacer una de prueba y si me aparece directamente. Seguiré investigando esto&amp;#8230;&lt;/s&gt; &lt;em&gt;Confirmado que era problema mío. Le faltaban permisos sobre las llamadas a&amp;nbsp;GadgetBridge&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;En cuanto a la batería, es posiblemente una de las cosas más positivas. Me está durando una semana completa con el uso diario normal, lo que me parece que está bastante bien la verdad. Aunque también necesita algo de mejora aquí porque los porcentajes son algo confusos. Por ejemplo, nunca marca el 100%, o a partir del 33% baja demasiado rápido. &lt;em&gt;Esto ha mejorado algo en la versión 1.5 pero aún necesita&amp;nbsp;mejorar.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Por último, para mí no es un problema pero creo que merece la pena indicar que no tiene posibilidad de usar otro idioma que no sea el&amp;nbsp;inglés.&lt;/p&gt;
&lt;h3 id="aplicaciones-adicionales"&gt;Aplicaciones&amp;nbsp;Adicionales&lt;/h3&gt;
&lt;p&gt;Nuestro PineTime viene adornado con un buen puñado de aplicaciones. Pero hay que tener en cuenta que no dispone de una &amp;#8220;store&amp;#8221; ni nada parecido, sino que están directamente incluidas en el firmware. Son las&amp;nbsp;siguientes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Música: Controla el reproductor de música de tu dispositivo&amp;nbsp;vinculado.&lt;/li&gt;
&lt;li&gt;Pulsómetro: La verdad es que diría que es de las cosas más decepcionantes del reloj. Es bastante impreciso la&amp;nbsp;verdad.&lt;/li&gt;
&lt;li&gt;Navegación: No la he probado, pero se supone que a través de GadgetBridge se envían las indicaciones de navegación al&amp;nbsp;reloj.&lt;/li&gt;
&lt;li&gt;Paddle: Un clon de pong para un&amp;nbsp;jugador.&lt;/li&gt;
&lt;li&gt;Twos: Un clon de&amp;nbsp;2048.&lt;/li&gt;
&lt;li&gt;Cronómetro: Un cronómetro simple que hace su función&amp;nbsp;perfectamente.&lt;/li&gt;
&lt;li&gt;Cuenta atrás: Una aplicación de cuenta&amp;nbsp;atrás.&lt;/li&gt;
&lt;li&gt;Podómetro: Te muestra los pasos realizados en el día. Funciona bien, permite establecer metas diarias pero no se guarda ningún tipo de registro. Espero que evolucione en futuras&amp;nbsp;versiones.&lt;/li&gt;
&lt;li&gt;Metrónomo: Un metrónomo bastante curioso que marca el ritmo con las&amp;nbsp;vibraciones.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Alarma: Desde la Versión 1.5 de InifiniTime tenemos una aplicación para programar&amp;nbsp;alarmas.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="un-detalle-mas"&gt;Un detalle&amp;nbsp;más&lt;/h2&gt;
&lt;p&gt;Este último detalle no es achacable a la gente de Pine64 ni a InfiniTime sino al servicio de correos. Si lo pides desde España te puede pasar lo que a mí y encontrarte con un par de sorpresas. La primera, es que para recoger el pedido tendrás que abonar en la oficina de correos el importe relativo al &lt;span class="caps"&gt;IVA&lt;/span&gt; del producto. Sinceramente esto no me extraña a raíz de las últimas leyes aprobadas, y sinceramente tampoco me molesta. Es lo que hay que hacer y se hace. Pero la segunda sorpresa si me molestó bastante, y es esa nueva &amp;#8220;tasa&amp;#8221; (por llamarla de algún modo) en concepto de gestión de aduanas que está cobrando correos de manera bastante arbitraria. Son 5€ por un trabajo bastante oscuro que no queda claro y que tampoco se a donde va a&amp;nbsp;parar. &lt;/p&gt;
&lt;h2 id="conclusiones"&gt;Conclusiones&lt;/h2&gt;
&lt;p&gt;PineTime es un reloj inteligente del que si sabes lo que esperas no te defraudará. Su espíritu libre y su constante evolución lo hacen perfecto si lo que buscas es eso. Evidentemente no se puede comparar en funciones a otros relojes inteligentes del mercado, pero tampoco creo que sea lo que pretenda. ¿En qué otro reloj puedes compilarte el firmware, cambiarle detalles tú mismo en el código, colaborar en el desarrollo para mejorar cualquiera de las cosas que hemos hablado en este artículo o añadir nuevas&amp;nbsp;funciones?&lt;/p&gt;
&lt;p&gt;Mi PineTime no es perfecto, no es precioso, pero no lo cambio por ningún&amp;nbsp;otro.&lt;/p&gt;
&lt;h5 id="enlaces-de-interes"&gt;Enlaces de&amp;nbsp;interés:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pine64.org/pinetime/"&gt;Página oficial de&amp;nbsp;Pinetime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JF002/InfiniTime"&gt;Repositorio en GitHub de&amp;nbsp;InfiniTime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodon.codingfield.com/@JF"&gt;Cuenta de Mastodon del desarrollador de&amp;nbsp;Infinitime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="pinetime"></category><category term="software libre"></category><category term="hardware libre"></category><category term="infinitime"></category></entry><entry><title>El comando prips. Obtén y trabaja con la lista de ips de una red en tu terminal.</title><link href="https://noroute2host.com/prips.html" rel="alternate"></link><published>2021-09-04T16:00:00+02:00</published><updated>2021-09-04T16:00:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-09-04:/prips.html</id><summary type="html">&lt;p&gt;A veces se necesita obtener de forma rápida información acerca de las ips de una red. Y lo cierto es que ya existe un comando para la terminal de linux que nos puede ayudar con esa tarea: &lt;code&gt;prips&lt;/code&gt;. Esta herramienta te permite tener la información directamente en el terminal y usarla dentro de tus scripts o como entrada para otros&amp;nbsp;comandos.&lt;/p&gt;</summary><content type="html">&lt;h2 id="el-comando-prips"&gt;El comando&amp;nbsp;prips&lt;/h2&gt;
&lt;p&gt;A veces se necesita obtener de forma rápida información acerca de las ips de una red. En mi caso, al final lo que hacía siempre es poner en el buscador &amp;#8220;Network Calculator&amp;#8221; o algo del estilo y usaba cualquier web que pillara. Pero ya existe un comando para la terminal de linux que nos puede ayudar con esa tarea: &lt;code&gt;prips&lt;/code&gt;. Bueno, al menos en parte, aunque también te permite tener la información directamente en el terminal y usarla dentro de tus scripts o como entrada para otros&amp;nbsp;comandos.&lt;/p&gt;
&lt;p&gt;Para tener la funcionalidad completa de una de estas webs se puede complementar &lt;code&gt;prips&lt;/code&gt; con &lt;code&gt;ipcalc&lt;/code&gt;. Pero en este artículo nos centraremos en &lt;code&gt;prips&lt;/code&gt;. &lt;/p&gt;
&lt;h3 id="usando-prips"&gt;Usando&amp;nbsp;prips&lt;/h3&gt;
&lt;p&gt;Comenzando por el principio, su instalación es bastante sencilla, al menos en Debian, Ubuntu o derivados. Es tan simple&amp;nbsp;como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;prips
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalada, la utilidad es bastante simple de usar. Su uso más básico es de una de las dos siguientes&amp;nbsp;formas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;prips&lt;span class="w"&gt; &lt;/span&gt;IP-INICIO&lt;span class="w"&gt; &lt;/span&gt;IP-FIN

prips&lt;span class="w"&gt; &lt;/span&gt;RED/MÁSACARA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Es decir, que &lt;code&gt;prips 192.168.0.0 192.168.0.255&lt;/code&gt; y &lt;code&gt;prips 192.168.0.0/24&lt;/code&gt; producirán la misma&amp;nbsp;salida:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;192.168.0.0
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4

# Salida Truncada...

192.168.0.251
192.168.0.252
192.168.0.253
192.168.0.254
192.168.0.255
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A partir de aquí, &lt;code&gt;prips&lt;/code&gt; añade varias opciones más independientemente de la forma de uso que hayas decidido utilizar. Aunque las opciones son un poco especiales, creo que las más útiles&amp;nbsp;son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-e&lt;/code&gt;: Excluye un rango de la&amp;nbsp;salida.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt;: Establece un incremento para listar las ips solo en intervalos de ese incremento. Por ejemplo si se establece en 2, el comando listará una ip, omitirá la siguiente y así&amp;nbsp;sucesivamente.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;: Devuelve en notación &lt;span class="caps"&gt;CIDR&lt;/span&gt; el bloque de red más pequeño que incluye a las dos ips pasadas como&amp;nbsp;parámetros.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt;: Por defecto, las ips aparecen separadas por un retorno de línea. Pero esta opción te permite cambiar el carácter que separa cada ip de la lista. El problema es que lo que espera la opción no es un carácter directamente sino el código decimal del símbolo &lt;span class="caps"&gt;ASCII&lt;/span&gt; que queremos utilizar. Por ejemplo para usar como separador &amp;#8220;;&amp;#8221; deberemos usar el código &lt;span class="caps"&gt;ASCII&lt;/span&gt; 59. Puedes consultar más ejemplos de caracteres &lt;span class="caps"&gt;ASCII&lt;/span&gt; &lt;a href="https://es.wikipedia.org/wiki/ASCII#Caracteres_imprimibles_ASCII"&gt;aquí&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ejemplos-de-uso"&gt;Ejemplos de&amp;nbsp;uso&lt;/h4&gt;
&lt;p&gt;Se muestran aquí varios ejemplos de uso del comando &lt;code&gt;prips&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Listar todas las ips de la subred 192.168.0.32/29&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.32/29

&lt;span class="c1"&gt;# Listar todas las ips de la subred 192.168.0.32/29 usando &amp;quot;;&amp;quot; como separador&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.32/29

&lt;span class="c1"&gt;# Listar todas las ips de la subred 192.168.0.32/29 excluyendo la dirección de broadcast (192.168.0.39)&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;...39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.32/29

&lt;span class="c1"&gt;# Listar todas las ips de la subred 192.168.0.32/29 en incrementos de 2.&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.32/29

&lt;span class="c1"&gt;# Indicar el bloque CIDR más pequeño que incluye a las ips 192.168.0.18 y 192.168.0.77&lt;/span&gt;
prips&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.18&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.0.77
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En la siguiente imagen se puede observar la salida de todos los comandos&amp;nbsp;anteriores:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ejemplos comando prips" src="https://noroute2host.com/images/0003_prips_ejemplos.png" style="display:inline-block"&gt;&lt;/p&gt;
&lt;p&gt;Enlaces de&amp;nbsp;interés:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devel.ringlet.net/sysutils/prips/"&gt;Web de la utilidad&amp;nbsp;prips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/prips/prips"&gt;Repositorio en Gitlab de&amp;nbsp;prips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="terminal"></category><category term="redes"></category><category term="ip"></category></entry><entry><title>Da color a tus scripts en Bash</title><link href="https://noroute2host.com/color-bash.html" rel="alternate"></link><published>2021-08-29T18:30:00+02:00</published><updated>2021-08-29T18:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-08-29:/color-bash.html</id><summary type="html">&lt;p&gt;Aunque pueda parecer algo sin importancia y solo un adorno, a veces, viene muy bien poder dar color la salida de tus scripts en bash. Hay montones de ocasiones donde esperamos un salida de bastantes lineas y marcar por colores las ocurrencias más interesantes es algo muy útil. En este artículo se explica como dar color a tus scripts y se muestran varios ejemplos de uso en&amp;nbsp;bash.&lt;/p&gt;</summary><content type="html">&lt;h2 id="da-color-a-tus-scripts-en-bash"&gt;Da color a tus scripts en&amp;nbsp;bash&lt;/h2&gt;
&lt;p&gt;Aunque pueda parecer algo sin importancia y solo un adorno, a veces, viene muy bien poder dar color la salida de tus scripts en bash. De hecho, pienso que si no se hace más es porque no todo el mundo sabe lo sencillo que es. Y ciertamente, para scripts donde la salida no es de muchas líneas no tiene demasiado sentido, pero hay montones de ocasiones donde esperamos un salida de bastantes lineas y marcar por colores las ocurrencias más interesantes es algo muy&amp;nbsp;útil. &lt;/p&gt;
&lt;p&gt;El uso de colores en bash es tan sencillo como añadir unos códigos de secuencias de escape antes de la salida que se quiera mostrar por la salida estándar. Eso sí, después no se puede olvidar dejar el formato como estaba antes. Así que lo mejor es empezar por el&amp;nbsp;principio.&lt;/p&gt;
&lt;h3 id="secuencias-de-formato"&gt;Secuencias de&amp;nbsp;formato&lt;/h3&gt;
&lt;p&gt;Los secuencias de formato serán del tipo &lt;code&gt;&amp;lt;Esc&amp;gt;[&lt;/code&gt;&lt;em&gt;Codigo de Formato&lt;/em&gt;&lt;code&gt;m&lt;/code&gt;&lt;/p&gt;
&lt;h4 id="secuencias-de-escape"&gt;Secuencias de&amp;nbsp;escape&lt;/h4&gt;
&lt;p&gt;La secuencia de escape puede expresarse de alguna de las siguientes&amp;nbsp;formas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\e&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\033&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\x1B&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En realidad, no importa cual de estas secuencias usemos para \&amp;lt;Esc&amp;gt;. Personalmente suelo usar &lt;code&gt;\033&lt;/code&gt; o &lt;code&gt;\e&lt;/code&gt;, pero puedes usar la que&amp;nbsp;prefieras.&lt;/p&gt;
&lt;h4 id="codigo-de-formato"&gt;Código de&amp;nbsp;Formato&lt;/h4&gt;
&lt;p&gt;Aquí esta la gracia del asunto: el código de formato. Puede ser un código de color y pueden definir colores tanto para la fuente como para el fondo. O un código de estilo de la fuente. Y como puedes imaginarte estos códigos pueden usarse conjuntamente separando cada uno de ellos por &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id="codigos-de-estilo"&gt;Códigos de&amp;nbsp;estilo&lt;/h5&gt;
&lt;p&gt;Aquí algunos ejemplos de códigos de formato solo con&amp;nbsp;estilos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Negrita: &lt;code&gt;\e[1m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Subrayado: &lt;code&gt;\e[4m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Parpadeo: &lt;code&gt;\e[5m&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="codigos-de-color-de-fuente"&gt;Códigos de color de&amp;nbsp;fuente&lt;/h5&gt;
&lt;p&gt;Y aquí más ejemplos de códigos de estilo solo para el color de la&amp;nbsp;fuente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Color por defecto: &lt;code&gt;\e[39m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Rojo: &lt;code&gt;\e[31m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verde: &lt;code&gt;\e[32m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Amarillo: &lt;code&gt;\e[33m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Azul: &lt;code&gt;\e[34m&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="codigos-de-color-de-fondo"&gt;Códigos de color de&amp;nbsp;fondo&lt;/h5&gt;
&lt;p&gt;Y códigos de color de&amp;nbsp;fondo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rojo: &lt;code&gt;\e[41m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verde: &lt;code&gt;\e[42m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Amarillo: &lt;code&gt;\e[43m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Azul: &lt;code&gt;\e[44m&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="codigos-combinados"&gt;Códigos&amp;nbsp;combinados&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Negrita + Texto amarillo + Fondo azul: &lt;code&gt;\e[1;33;44m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Texto rojo + Fondo azul: &lt;code&gt;\e;31;44m&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="codigos-de-reset"&gt;Códigos de&amp;nbsp;reset&lt;/h5&gt;
&lt;p&gt;Existen códigos específicos para hacer un reset del formato del texto. Hay que usarlos para dejar el formato como estaba. El formato no se restablece al terminar el comando, tenemos que hacerlo&amp;nbsp;manualmente.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reset de todo el formato: &lt;code&gt;\e[0m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reset de negrita: &lt;code&gt;\e[21&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reset del color de fuente: &lt;code&gt;\e[39m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reset del color de fondo: &lt;code&gt;\e[49m&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se pueden combinar los códigos de color y existen muchos más códigos de formato de cualquiera de los anteriores tipos. Como ejemplo os dejo este &lt;a href="https://misc.flogisoft.com/bash/tip_colors_and_formatting"&gt;enlace&lt;/a&gt; donde podéis consultar muchos&amp;nbsp;más. &lt;/p&gt;
&lt;h3 id="uso-de-los-codigos-de-formato"&gt;Uso de los códigos de&amp;nbsp;formato&lt;/h3&gt;
&lt;p&gt;Los códigos de formato puedes usarlos con &amp;#8220;echo -e&amp;#8221; o &amp;#8220;printf&amp;#8221;. Aquí te dejo unos&amp;nbsp;ejemplos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[;31mError: Revisa esto\e[0m&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[4;30;43mSoy la Abeja Maya\e[0m&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[;31mError: Revisa esto\e[0m\n&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y este es el&amp;nbsp;resultado:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ejemplo de colores en Bash" src="https://noroute2host.com/images/0002_colores_bash_script1.png" style="display:inline-block"&gt;&lt;/p&gt;
&lt;p&gt;Por último, para usar estos códigos de manera más cómoda en tus scripts puedes definir variables al comienzo de los mismos con los formatos ya preestablecidos. A continuación tienes un pequeño script que en función de si el número de segundos de la hora actual es par o no muestra el texto en un formato u&amp;nbsp;otro.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;ROJO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[;31m&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;CELESTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[;36m&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;NOCOLOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\e[0m&amp;quot;&lt;/span&gt;

&lt;span class="nv"&gt;SEGUNDOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;..5&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;SEGUNDOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+%S&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SEGUNDOS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CELESTE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$SEGUNDOS&lt;/span&gt;&lt;span class="s2"&gt; es número PAR&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ROJO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$SEGUNDOS&lt;/span&gt;&lt;span class="s2"&gt; es número IMPAR&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En la siguiente imagen se puede ver la salida de la ejecución del script&amp;nbsp;anterior:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ejemplo de colores en Bash" src="https://noroute2host.com/images/0002_colores_bash_script2.png" style="display:inline-block"&gt;&lt;/p&gt;</content><category term="terminal"></category><category term="bash"></category><category term="scripts"></category></entry><entry><title>¡Abierto por vacaciones! Arranca noroute2host.com</title><link href="https://noroute2host.com/arrancamos.html" rel="alternate"></link><published>2021-08-27T22:30:00+02:00</published><updated>2021-08-27T22:30:00+02:00</updated><author><name>AdriMcGrady</name></author><id>tag:noroute2host.com,2021-08-27:/arrancamos.html</id><summary type="html">&lt;p&gt;Arranca &lt;a href="https://noroute2host.com"&gt;noroute2host.com&lt;/a&gt; cargado de buenas intenciones, ganas de hablar de Linux, Software Libre, Cloud, DevOps y todo lo que&amp;nbsp;surja.&lt;/p&gt;</summary><content type="html">&lt;h2 id="abierto-por-vacaciones"&gt;Abierto por&amp;nbsp;vacaciones&lt;/h2&gt;
&lt;p&gt;Coincidiendo con las vacaciones y pensando en que podía hacer para seguir aprendiendo, caí en la cuenta de un blog o página de artículos principalmente técnicos cumpliría las dos cosas fundamentales que tenía en la cabeza. Por un lado me permitiría descubrir nuevas tecnologías libres y por otro, compartirlas con todo aquel al que le parezcan&amp;nbsp;interesantes.&lt;/p&gt;
&lt;p&gt;Pero ¡oye! Estamos en 2021 y existe stackoverflow. Y muy bien que está, pero no todo es eso. Por un lado la puesta en marcha de este sitio me ha permitido aprender o afianzar tecnologías como python, pelican, jinja2, css, bootstrap, git, gitlab pages, Markdown y alguna que otra más. Además, y pese a que soy de los que casi siempre prefiere buscar la documentación en inglés puesto que después hace más fácil identificar lo que estás buscando, es cierto que el contenido en español en temas técnicos es menor y me apetecía hacer algo al&amp;nbsp;respecto.&lt;/p&gt;
&lt;p&gt;Evidentemente esto no tiene nada que ver con stackoverflow o &lt;span class="caps"&gt;UNIX&lt;/span&gt; Stack Exchange. Esto es simplemente un repositorio de información en la que aparecerá lo que me pique en cada momento, lo que me parezca interesante o simplemente lo que me apetezca. Para que nos vamos a&amp;nbsp;engañar&amp;#8230;&lt;/p&gt;
&lt;p&gt;También reconozco que el proyecto tiene algo de nostalgia puesto que ya hubo un noroute2host.com de la mano de amigos que admiro y aprecio. Así que esto es como su reencarnación en pleno 2021. Si tienes curiosidad por ver como fue aquello siempre nos quedará la &lt;a href="http://web.archive.org/web/20120416213935/http://www.noroute2host.com/"&gt;WayBack Machine&lt;/a&gt; de &lt;a href="https://archive.org"&gt;archive.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Así que esto arranca cargado de buenas intenciones, ganas de hablar de Linux, Software Libre, Cloud, DevOps y todo lo que surja. Arranca &lt;a href="https://noroute2host.com"&gt;noroute2host.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Por último, si quieres estar al tanto de lo que vaya apareciendo por aquí o incluso proponer algún tema puedes comenzar a seguirnos en nuestras redes&amp;nbsp;sociales:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mastodon.social/@noroute2host"&gt;Mastodon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/noroute2hostcom"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O simplemente suscribirte a nuestro &lt;a href="http://noroute2host.com/feeds/all.atom.xml"&gt;&lt;span class="caps"&gt;RSS&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;</content><category term="noroute2host"></category><category term="noroute2host"></category><category term="inicio"></category></entry></feed>