Algunas personas tienen esa noción errónea de que read
es el comando de leer una línea. No es.
read
lee palabras de una línea (posiblemente barra invertida), donde las palabras están $IFS
delimitadas y la barra invertida puede usarse para escapar de los delimitadores (o líneas continuas).
La sintaxis genérica es:
read word1 word2... remaining_words
read
lee la entrada estándar de un byte a la vez hasta que encuentra un carácter de nueva línea sin escape (o al final de la entrada), se divide que de acuerdo con las reglas complejas y almacena el resultado de esa división en $word1
, $word2
... $remaining_words
.
Por ejemplo en una entrada como:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
y con el valor predeterminado de $IFS
, read a b c
asignaría:
$a
⇐ foo
$b
⇐ bar baz
$c
⇐ blah blahwhatever whatever
Ahora, si se pasa solo un argumento, ese no se convierte read line
. Aun esta read remaining_words
. El procesamiento de barra invertida todavía se realiza, los caracteres de espacio en blanco IFS aún se eliminan desde el principio y el final.
La -r
opción elimina el procesamiento de la barra diagonal inversa. Entonces, el mismo comando anterior con en -r
su lugar asignaría
$a
⇐ foo
$b
⇐ bar\
$c
⇐ baz bl\ah blah\
Ahora, para la parte de división, es importante darse cuenta de que hay dos clases de caracteres para $IFS
: los caracteres de espacio en blanco IFS (a saber, espacio y tabulación (y nueva línea, aunque aquí eso no importa a menos que use -d), que también sucede estar en el valor predeterminado de $IFS
) y los demás. El tratamiento para esas dos clases de personajes es diferente.
Con IFS=:
( :
no siendo un carácter de espacio en blanco IFS), al igual que una entrada :foo::bar::
se divide en ""
, "foo"
, ""
, bar
y ""
(y un extra ""
con algunas implementaciones, aunque eso no importa a excepción de read -a
). Mientras que si reemplazamos eso :
con espacio, la división se realiza solo en foo
y bar
. Eso es líder y los posteriores se ignoran, y sus secuencias se tratan como una sola. Hay reglas adicionales cuando se combinan los espacios en blanco y los no espacios en blanco $IFS
. Algunas implementaciones pueden agregar / eliminar el tratamiento especial duplicando los caracteres en IFS ( IFS=::
o IFS=' '
).
Entonces, aquí, si no queremos que se eliminen los caracteres de espacio en blanco sin escape iniciales y finales, debemos eliminar esos caracteres de espacio en blanco IFS de IFS.
Incluso con caracteres IFS que no sean espacios en blanco, si la línea de entrada contiene uno (y solo uno) de esos caracteres y es el último carácter de la línea (como IFS=: read -r word
en una entrada como foo:
) con shells POSIX (no zsh
ni algunas pdksh
versiones), esa entrada se considera como una foo
palabra porque en esos shells, los caracteres $IFS
se consideran terminadores , por word
lo que contendrán foo
, no foo:
.
Entonces, la forma canónica de leer una línea de entrada con el read
incorporado es:
IFS= read -r line
(tenga en cuenta que para la mayoría de las read
implementaciones, eso solo funciona para líneas de texto ya que el carácter NUL no es compatible, excepto en zsh
).
El uso de la var=value cmd
sintaxis asegura que IFS
solo se configure de manera diferente durante la duración de ese cmd
comando.
Nota de la historia
La read
construcción fue introducida por el shell Bourne y ya era para leer palabras , no líneas. Hay algunas diferencias importantes con los modernos proyectiles POSIX.
El shell Bourne read
no admitía una -r
opción (que fue introducida por el shell Korn), por lo que no hay forma de deshabilitar el procesamiento de barra diagonal inversa que no sea el preprocesamiento de la entrada con algo como sed 's/\\/&&/g'
eso.
El shell Bourne no tenía esa noción de dos clases de caracteres (que nuevamente fue presentada por ksh). En el shell Bourne todos los caracteres se someten al mismo tratamiento que los espacios en blanco IFS pueden hacer en ksh, es decir IFS=: read a b c
en una entrada como foo::bar
asignaría bar
a $b
, no la cadena vacía.
En el shell Bourne, con:
var=value cmd
Si cmd
está integrado (como read
es), var
permanece configurado value
después de que cmd
haya terminado. Eso es particularmente crítico $IFS
porque, en el shell Bourne, $IFS
se usa para dividir todo, no solo las expansiones. Además, si elimina el carácter de espacio $IFS
en el shell Bourne, "$@"
ya no funciona.
En el shell Bourne, la redirección de un comando compuesto hace que se ejecute en un subshell (en las versiones más antiguas, incluso cosas como read var < file
o exec 3< file; read var <&3
no funcionaban), por lo que era raro en el shell Bourne usar read
cualquier cosa que no fuera la entrada del usuario en el terminal (donde el manejo de continuación de línea tenía sentido)
Algunos Unices (como HP / UX, también hay uno util-linux
) todavía tienen un line
comando para leer una línea de entrada (que solía ser un comando estándar de UNIX hasta la versión 2 de la especificación UNIX única ).
Eso es básicamente lo mismo, head -n 1
excepto que lee un byte a la vez para asegurarse de que no lea más de una línea. En esos sistemas, puede hacer:
line=`line`
Por supuesto, eso significa generar un nuevo proceso, ejecutar un comando y leer su salida a través de una tubería, por lo que es mucho menos eficiente que el de ksh IFS= read -r line
, pero aún mucho más intuitivo.