Veamos un ejemplo, con un texto de entrada cuidadosamente elaborado:
text=' hello world\
foo\bar'
Son dos líneas, la primera comienza con un espacio y termina con una barra diagonal inversa. Primero, echemos un vistazo a lo que sucede sin precauciones read
(pero utilizando printf '%s\n' "$text"
para imprimir cuidadosamente $text
sin ningún riesgo de expansión). (A continuación, se $
encuentra el indicador de comandos de la shell).
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read
comió las barras diagonales inversas: la barra diagonal inversa-nueva línea hace que la nueva línea se ignore, y la barra diagonal inversa-cualquier cosa ignora esa primera barra diagonal inversa. Para evitar el tratamiento especial de las barras invertidas, utilizamos read -r
.
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Eso es mejor, tenemos dos líneas como se esperaba. Las dos líneas casi contienen el contenido deseado: el doble espacio entre hello
y world
se ha retenido, porque está dentro de la line
variable. Por otro lado, el espacio inicial fue devorado. Esto se debe a que read
lee tantas palabras como le pasa variables, excepto que la última variable contiene el resto de la línea, pero aún comienza con la primera palabra, es decir, los espacios iniciales se descartan.
Por lo tanto, para leer cada línea literalmente, debemos asegurarnos de que no haya división de palabras . Hacemos esto estableciendo la IFS
variable en un valor vacío.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Tenga en cuenta cómo establecemos IFS
específicamente para la duración de la read
incorporada . Los IFS= read -r line
conjuntos de la variable de entorno IFS
(a un valor vacío) específicamente para la ejecución de read
. Esta es una instancia de la sintaxis de comando simple general : una secuencia (posiblemente vacía) de asignaciones de variables seguida de un nombre de comando y sus argumentos (también, puede lanzar redireccionamientos en cualquier punto). Como read
es una función integrada, la variable nunca termina en el entorno de un proceso externo; no obstante, el valor de $IFS
es lo que estamos asignando allí mientras se read
esté ejecutando¹. Tenga en cuenta que read
no es una función integrada especial , por lo que la asignación solo dura su duración.
Por lo tanto, nos encargamos de no cambiar el valor de IFS
otras instrucciones que puedan depender de él. Este código funcionará sin importar en qué se haya configurado IFS
inicialmente el código circundante , y no causará ningún problema si el código dentro del bucle se basa IFS
.
Contraste con este fragmento de código, que busca archivos en una ruta separada por dos puntos. La lista de nombres de archivos se lee desde un archivo, un nombre de archivo por línea.
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
Si el bucle fuera while IFS=; read -r name; do …
, entonces for dir in $PATH
no se dividiría $PATH
en componentes separados por dos puntos. Si el código fuera IFS=; while read …
, sería aún más obvio que IFS
no está configurado :
en el cuerpo del bucle.
Por supuesto, sería posible restaurar el valor de IFS
después de ejecutar read
. Pero eso requeriría conocer el valor anterior, que es un esfuerzo adicional. IFS= read
es el camino simple (y, convenientemente, también el camino más corto).
¹ Y, si read
se interrumpe por una señal atrapada, posiblemente mientras la trampa se está ejecutando, POSIX no lo especifica y depende del shell en la práctica.
while IFS=X read
no se separaX
, perowhile IFS=X; read
sí ...