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.