viernes, 26 de diciembre de 2014

Aplicando unzip masivo a archivos con espacios

Tenía una carpeta llena de archivos ZIP, cuyos nombres contenían espacios, paréntesis, apóstrofes y otros caracteres especiales.
Carpeta
+ Primer Archivo.zip
+ Archivo (Paréntesis).zip
+ Archivo 'Apóstrofe.zip
Y mi intención era descomprimirlos todos de una sola vez, para que otra aplicación importase los archivos internos, manteniendo la jerarquía de carpetas.
Mi primer intento fue
unzip *.zip
Error. Unzip tomaba cada fragmento separado por espacio como un parámetro individual.
Luego vino
find *.zip -type f -exec unzip {} \;
Error. Unzip seguía tomando cada fragmento separado por espacio como un parámetro individual.
Entonces intente con un null como separador, no conseguí el comando de unzip que le permitiese entender el separador null, así que incorporé xargs al comando, incluso intenté con comillas.
find *.zip -type f -print0 | xargs -0 unzip {}
find *.zip -type f -print0 | xargs -0 unzip "{}"
Mismo error.
Descubrí que ls -b da como salida una lista de nombres de archivo con los espacios escapados.
ls -b *.zip | xargs unzip {}
Mismo error. Probé con echo, y sospecho que xargs se come las escapadas de los espacios.
Empiezo el camino de admisión de derrota al redactar un pequeño guión bash
#!/bin/sh

for file $(ls -b *.zip)
do
    unzip $file
done
Mismo error. Resulta que el for toma cada pedazo separado por espacios.
Quedaban dos opciones de admisión total de derrota ante el reto de descomprimir masivamente archivos ZIP con espacios en el nombre:
  1. Eliminar los espacios de los nombres de archivo
  2. Lanzar muchos comandos de descompresión
Me fui por la segunda opción. No era una aceptable escribir cada comando manualmente, así que construí un comando que elaborase un guión bash especializado.
echo "#!/bin/sh" > descomprimir.sh
ls -b *.zip | sed 's/^/unzip /' >> descomprimir.sh
chmod +x descomprimir.sh
./descomprimir.sh
El archivo resultante, descomprimir.sh queda de la forma:
#!/bin/sh
unzip Primer\ Archivo.zip
unzip Archivo\ (Paréntesis).zip
unzip Archivo\ 'Apóstrofe.zip
Eso hizo 90% del trabajo. Me di cuenta de que los paréntesis y los apóstrofes seguían dando problemas. Como ya había descomprimido parte de los archivos, decidí generar el archivo anterior nuevamente de forma que unzip no reescribiese los archivos ya extraídos, lo cual se logró con unzip -n. Escapar el apóstrofe requirió utilizar el valor en hexadecimal.
echo "#!/bin/sh" > descomprimir.sh
ls -b *.zip | sed 's/^/unzip -n /' |\
sed 's/(/\\(/' | \sed 's/)/\\)/' | sed "s|\x27|\x5c\'|">> descomprimir.sh
chmod +x descomprimir.sh
./descomprimir.sh
Con esto, el archivo quedó de la forma
#!/bin/sh
unzip -n Primer\ Archivo.zip
unzip -n Archivo\ \(Paréntesis\).zip
unzip -n Archivo\ \'Apóstrofe.zip
Éxito. Se descomprimieron todos los archivos.
Como reflexión final, quizás hubiese sido más sencillo renombrar masivamente los archivos para eliminar los espacios, o quizás los paréntesis y los apóstrofes igual me hubiesen llevado por el mismo camino. Otro día investigaré más a fondo las posibilidades de xargs o algún otro comando de escapar todos los caracteres especiales.

sábado, 25 de octubre de 2014

Desduplicar archivos idénticos en Linux

Recientemente tuve que reconstruir mi colección de música a partir de mi reproductor varios respaldos parciales (en un reproductor de música, en una memoria USB, una computadora vieja, etc), y aparte del problema de tener que categorizar de nuevo algunas canciones, me encontré con que un mismo archivo estaba en diferentes carpetas o rutas en los diversos soportes, por lo cual colocar todas las subcarpetas en un mismo directorio resultaba en un que una misma canción podía estar repetida varias veces.

Inicialmente, quise utilizar los metadatos de las canciones para detectar los archivos duplicados, pero era un trabajo manual muy tedioso ordenar por artista e ir leyendo cada una, así que decidí hacer un guión en bash.

A continuación el método a grandes rasgos:
  1. Para detectar si un archivo está repetido, se hará una comparación de su huella SHA256, una función robusta de resumen criptográfico.
  2. Se comparan dos carpetas a la vez (en mi caso una carpeta con una colección parcial que estaba mejor estructurada que otra carpeta con canciones sueltas).
  3. Se ejecuta la función de SHA256 sobre todos los archivos de una carpeta y se almacena el nombre y la huella de cada archivo. Luego se ejecuta la operación con la otra carpeta.
  4. Se combinan ambos listados y para determinar si alguna huella está repetida.
  5. Para los casos cuando un archivo esté duplicado, en una carpeta se conservará y en la otra será eliminado. Por simplicidad, en una carpeta siempre se conservará y en la otra siempre se eliminará.
  6. Se utiliza la información de las huellas repetidas para determinar a cuales archivos se corresponden en la carpeta para eliminar.
  7. Se borran masivamente los archivos repetidos en la carpeta para borrar.
El guión quedó así:
#!/bin/sh

# Autor: J. Navarro
# Fecha: 2014
# Este programa se distribuye bajo licencia GPLv2
# Puede obtener el texto completo de la licencia en:
# http://www.gnu.org/licenses/gpl-2.0.html


#Inicialización de variables

NOBORRARDIR=<carpeta donde se conservan los duplicados>;
NOBORRARTMP="/tmp/noborrar.sha256";
NOBORRARREP="/tmp/noborrar.sha256.repetido";

BORRABLEDIR=<carpeta de donde se eliminan los duplicados>;
BORRABLETMP="/tmp/borrable.sha256";
BORRABLEREP="/tmp/borrable.sha256.repetido"

REPETIDOTMP="/tmp/repetido.sha256"
REPETIDOMIX="/tmp/repetido-mix.sha256"

#Ejecución de comandos

echo "Generando resumen criptografico de $NOBORRARDIR"

find $NOBORRARDIR -type f -exec sha256sum {} + > $NOBORRARTMP

echo "Generando resumen criptografico de $BORRABLEDIR"

find $BORRABLEDIR -type f -exec sha256sum {} + > $BORRABLETMP

echo "Detectando archivos repetidos"

 #listado de resumenes criptograficos de archivos repetidos
cat $NOBORRARTMP $BORRABLETMP | cut -f1 -d\ | sort | uniq -d \
> $REPETIDOTMP


#listado combinado donde los archivos repetidos estan juntos
cat $NOBORRARTMP $BORRABLETMP | sort | egrep -f $REPETIDOTMP \
> $REPETIDOMIX

# Cuando grep genera el error "Referencia hacia atrás inválida"
# cat $NOBORRARTMP $BORRABLETMP | grep -F -f $REPETIDOTMP | sort \

#> $REPETIDOMIX

#visualizar el listado de archivos repetidos
pager $REPETIDOMIX

echo "Archivos repetidos a conservar"

egrep $NOBORRARTMP -f $REPETIDOTMP > $NOBORRARREP
pager $NOBORRARREP

echo "Archivos repetidos a eliminar"

egrep $BORRABLETMP -f $REPETIDOTMP > $BORRABLEREP
pager $BORRABLEREP

# Se toman solamente los nombres de archivo,
# se encierran entre comillas y se borran.
cat /tmp/borrable.sha256.repetido | perl -pe "s/[^ ]+[ ]+//" | \
perl -pe 'chomp $_; $_="\"$_\"\n"' | xargs rm

domingo, 22 de junio de 2014

Permitir que un usuario regular apague o reinicie la computadora aun si alguien más está autenticado

Como el computista de la casa, la administración de las computadoras domésticas recae sobre mí. Esta responsabilidad es exclusiva y excluyente para evitar que otros miembros del hogar desconfiguren el equipo o instalen algún software indeseable.

El problema es que a veces abro la sesión en Ubuntu (12.04 LTS) y dejo algo descargando o algo instalando toda la noche y se me olvida cerrar mi sesión. No ha pasado que requiera que mi sesión sobreviva más de una noche. Entonces llega otra persona en el hogar, se autentica con su cuenta hace que lo vaya a hacer y desea apagar el equipo (sobre todo cuando me voy de viaje) y no puede porque el sistema le indica que debe introducir una contraseña de administrador para poder apagar o reiniciar la computadora porque alguien más está autenticado.

Algunas opciones que descarté:
  • Aunque utilizo TeamViewer para dar soporte remoto cuando estoy de viaje, no me atrae el prospecto de tener que conectarme para algo tan sencillo como apagar un equipo.
  • Si bien apagar o reiniciar el equipo físicamente es una opción, no es una buena práctica, por lo tanto no me parece conveniente crear ese hábito entre los miembros de la casa que no son computistas.
  • Programar la desautenticación automática de mi usuario le permitiría a los otros miembros del hogar apagar el equipo una vez mi sesión halla expirado. Pero esto me quitaría la flexibilidad de dejar la computadora haciendo alguna operación que se demore mucho, que de otra sólo requeriría avisarle a los demás que no la apaguen y ya.
La solución que apliqué y me funcionó , la encontré en un hilo sobre este problema en Launchpad.
Primero, hay que editar, con permisos de administrador, el archivo /usr/share/polkit-1/actions/org.freedesktop.consolekit.policy
Para que los usuarios puedan apagar el equipo, cambiar la siguiente sección en el archivo
 <action id="org.freedesktop.consolekit.system.stop-multiple-users">
    <description>Stop the system when multiple users are logged in</description>
    <message>System policy prevents stopping the system when other users are logged in</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>auth_admin_keep</allow_active>
    </defaults>
  </action>
a
<action id="org.freedesktop.consolekit.system.stop-multiple-users">
    <description>Stop the system when multiple users are logged in</description>
    <message>System policy prevents stopping the system when other users are logged in</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>yes</allow_active>
    </defaults>
  </action>
Para que los usuarios puedan reiniciar el equipo, cambiar la siguiente sección en el archivo:
<action id="org.
freedesktop.consolekit.system.restart-multiple-users">
    <description>Restart the system when multiple users are logged in</description>
    <message>System policy prevents restarting the system when other users are logged in</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>auth_admin_keep</allow_active>
    </defaults>
  </action>
a
  <action id="org.freedesktop.consolekit.system.restart-multiple-users">
    <description>Restart the system when multiple users are logged in</description>
    <message>System policy prevents restarting the system when other users are logged in</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>yes</allow_active>
    </defaults>
  </action>
Listo!

Aunque a mí me funcionó, aparentemente hay una mejor forma de hacerlo, la cual probablemente ponga en práctica cuando deba instalar Ubuntu 14.04 LTS en la casa.

La solución correcta (que no he aplicado todavía) para que todos los usuarios puedan apagar el equipo, según AskUbuntu sería crear, con permiso de administrador, un archivo en la ruta /etc/polkit-1/localauthority/50-local.d/ con extensión .pkla (por ejemplo allow_all_users_to_shutdown.pkla) cuyo contenido sea:
[Allow all users to shutdown]
Identity=unix-user:*
Action=org.freedesktop.consolekit.system.stop-multiple-users
ResultInactive=no
ResultActive=yes
La solución correcta, según la misma fuente, para que todos los usuarios puedan reiniciar el equipo sería crear, con permiso  de administrador, un archivo llamado /etc/polkit-1/localauthority/50-local.d/ con extensión .pkla (por ejemplo allow_all_users_to_restart.pkla) cuyo contenido sea:
[Allow all users to restart]
Identity=unix-user:*
Action=org.freedesktop.consolekit.system.restart-multiple-users
ResultInactive=no
ResultActive=yes

domingo, 1 de junio de 2014

Conectarse a una VPN Microsoft desde Ubuntu

El otro día tenía que hacer unas cosas del trabajo desde la casa, para lo cual hacía falta conectarme a mi estación de trabajo por Red Privada Virtual (VPN).

Ingenuamente pensé que iba ser un proceso del tipo "siguiente, siguiente, siguiente" como cuando uno configura una conexión con OpenVPN. No fue así.

Traduciendo los pasos delineados en otro blog, esto fue lo que hice:
  • Clic en el ícono de red en la esquina superior derecha del escritorio
  • Ir al menú de Conexiones VPN
  • Seleccionar Configurar VPN
  • Agregar una nueva conexión de tipo PPTP
  • Configurar en la pestaña de VPN:
    • Nombre de la Conexión (al gusto)
    • Deseleccionar Conectar automáticamente (se puede cambiar luego)
    • Pasarela (dirección del servidor VPN)
    • Usuario (en caso de pertenecer a un dominio, escribirlo como dominio\usuario)
    • NO fijar la Contraseña; SI cambiar el valor del menú desplegable a Preguntar Siempre
    • NO fijar el Dominio NT
    • Deseleccionar Disponible para todos los usuarios (no es obligatorio pero es buena práctica)
  • En el botón Avanzado de la pestaña VPN
    • Deseleccionar todos los métodos de autenticación excepto MSCHAPv2
    • Seleccionar Usar cifrado punto a punto (MPPE)
    • Dejar el valor del menú desplegable Seguridad en Todas las disponibles (predeterminado).
    • Seleccionar Permitir cifrado de estado completo
    • Deseleccionar Permitir compresión de datos BSD
    • Deseleccionar Permitir compresión de datos Deflate
    • Deseleccionar Usar compresión de cabeceras TCP
    • Deseleccionar Mandar paquetes eco PPP (funciona de ambas maneras, seleccionarlo para depuración de errores)

viernes, 28 de febrero de 2014

Actualizar versión de Ubuntu por consola

Como administrador de la computadora de mis padres, es mi responsabilidad mantener actualizados tanto el sistema operativo como las aplicaciones.
Esta labor me llevó a migrar la computadora parental a Ubuntu, para quitarme de encima las constantes limpiezas de virus que sufría con Windows.
Así estuve, actualizando de una versión LTS de Ubuntu a la siguiente, hasta la 10.04, ya que Ubuntu 12.04 introdujo el controversial entorno de escritorio Unity. Ya había hecho el esfuerzo de entrenar a mis padres durante la migración para que re-aprendiesen a realizar sus labores en Gnome, y llegó la gente de Gnome y Canonical a deprecar el entorno Gnome2 sin ofrecer una interfaz compatible para sustituirla.
Vi nacer MATE y Cinammon, pero estuve esperando que maduraran las opciones, hasta que se hizo sentir la obsolescencia de las aplicaciones disponibles en el repositorio de 10.04.
Lo bueno es que me sentía satisfecho con Gnome Classic como entorno de escritorio en Ubuntu cuando surgió la necesidad de actualizar el sistema, así que podía proceder sin temor de tener que re-entrenar a mis padres de nuevo, o de que ellos se rechazasen a la actualización (que igual iba a ejecutar, así fuese por las malas jajaja).
Pero Update-Manager me falló y no quiso hacer la actualización por GUI, con lo cual acudí a Google y ejecuté los siguientes comandos:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
sudo apt-get install update-manager-core
sudo do-release-upgrade