PROBLEMA:
Al hacer bucles for dentro de un script sobre los nombres de ficheros de un directorio, si los nombres de los ficheros no tienen espacios en blanco el bucle itera correctamente, pero si los nombres de los ficheros contienen espacios en blanco entonces el bucle parte los nombres de los ficheros. Un ejemplo del problema es el siguiente:
[francisco@Terminator prueba]$ ls
fichero_1.txt fichero_2.txt fichero 3.txt fichero 4.txt
[francisco@Terminator prueba]$ for i in $(ls *); do echo $i; done
fichero_1.txt
fichero_2.txt
fichero
3.txt
fichero
4.txt
En el ejemplo el bucle simplemente lista los ficheros, pero se puede ver como lo hace mal. En scripts complejos esto es una fuente de errores constante, ya que aunque en Linux se desaconseja tener nombres de ficheros que contengan espacios, en la práctica es algo prácticamente inevitable.
SOLUCIÓN:
Usar la variable especial IFS, esta variable contiene los caracteres que son usados para partir las lineas en palabras que llegan desde la entrada estandar. El valor por defecto de esta variable es espacio, <tabulador><nueva línea>. Se puede imprimir su valor usando el comando:
cat -etv <<<"$IFS"
La solución pasa por sobreescribir el valor de IFS, y darle el valor <tabulador><nueva linea>. Esto se puede hacer de la siguiente forma:
IFS=$'\x0A'$'\x0D'
\x0D no es mas que 13 en hexadecimal y \x0A representa 10 en hexadecimal. En la tabla ASCII se corresponden con los caracteres CR y LF. O también si se prefiere, se puede hacer de forma menos matemática:
IFS=$(echo -en "\n\b");
Con las modificaciones pertinentes sobre la variable IFS, el script quedaría de la siguiente manera:
francisco@Terminator prueba]$ IFS=$(echo -en "\n\b"); for i in $(ls *); do echo $i; done
fichero_1.txt
fichero_2.txt
fichero 3.txt
fichero 4.txt
Si va a usar esto dentro de un script y luego pretende trabajar en la misma shell, tal vez le interese salvar el valor de IFS y volverlo a poner en la propia variable una vez termine el bucle. Tal y como se ilustra a continuación:
#!/bin/bash
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in *
do
echo "$f"
done
IFS=$SAVEIFS
Como se puede observar el resultado no tiene nada que ver con el resultado que ofrecía el bucle al principio.
Fuentes: