Obviamente entiendo que uno puede agregar valor a la variable de separador de campo interno. Por ejemplo:
$ IFS=blah
$ echo "$IFS"
blah
$
También entiendo que read -r line
guardará datos de stdin
una variable llamada line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Sin embargo, ¿cómo puede un comando asignar un valor variable? ¿Y primero almacena datos de stdin
a variable line
y luego le da valor line
a IFS
?
bash
shell-script
Martín
fuente
fuente
Respuestas:
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
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:
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 estaread 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 deread -a
). Mientras que si reemplazamos eso:
con espacio, la división se realiza solo enfoo
ybar
. 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=::
oIFS=' '
).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 comofoo:
) con shells POSIX (nozsh
ni algunaspdksh
versiones), esa entrada se considera como unafoo
palabra porque en esos shells, los caracteres$IFS
se consideran terminadores , porword
lo que contendránfoo
, nofoo:
.Entonces, la forma canónica de leer una línea de entrada con el
read
incorporado es:(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 enzsh
).El uso de la
var=value cmd
sintaxis asegura queIFS
solo se configure de manera diferente durante la duración de esecmd
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 comosed '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 comofoo::bar
asignaríabar
a$b
, no la cadena vacía.En el shell Bourne, con:
Si
cmd
está integrado (comoread
es),var
permanece configuradovalue
después de quecmd
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
oexec 3< file; read var <&3
no funcionaban), por lo que era raro en el shell Bourne usarread
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 unline
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: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.fuente
sh
diferencias regulares también es útil para escribir scripts portátiles!)bash-4.4.19
,while read -r; do echo "'$REPLY'"; done
funciona comowhile IFS= read -r line; do echo "'$line'"; done
.read
para leer una línea es erróneo, debe haber algo más. ¿Cuál podría ser esa noción no errónea? ¿O es esa primera afirmación técnicamente correcta, pero en realidad la noción no errónea es: "leer es el comando para leer palabras de una línea. Debido a que es tan poderoso, puede usarlo para leer líneas de un archivo haciendo:IFS= read -r line
"La teoría
Hay dos conceptos que están en juego aquí:
IFS
es el separador de campo de entrada, lo que significa que la lectura de la cadena se dividirá en función de los caracteres enIFS
. En una línea de comando,IFS
normalmente hay cualquier espacio en blanco, es por eso que la línea de comando se divide en espacios.VAR=value command
significa "modificar el entorno de comando para queVAR
tenga el valorvalue
". Básicamente, el comandocommand
veráVAR
que tiene el valorvalue
, pero cualquier comando ejecutado después de eso seguiráVAR
teniendo su valor anterior. En otras palabras, esa variable se modificará solo para esa declaración.En este caso
Entonces, al hacer
IFS= read -r line
, lo que está haciendo es establecerIFS
una cadena vacía (no se usará ningún carácter para dividir, por lo tanto, no se producirá división) para queread
lea la línea completa y la vea como una palabra que se asignará a laline
variable. Los cambiosIFS
solo afectan a esa declaración, de modo que los siguientes comandos no se verán afectados por el cambio.Como nota al margen
Mientras que el comando es correcto y funciona según lo previsto, estableciendo
IFS
en este casono esla fuerza 1 no sea necesario. Como está escrito en labash
página del manual en laread
sección integrada:Como solo tiene la
line
variable, todas las palabras se le asignarán de todos modos, por lo que si no necesita ninguno de los caracteres de espacio en blanco anteriores y finales 1 , simplemente podría escribirread -r line
y terminar con eso.[1] Solo como un ejemplo de cómo un valor
unset
predeterminado$IFS
haráread
que se considere el espacio en blanco IFS inicial / final , puede intentar:Ejecútelo y verá que los caracteres anteriores y finales no sobrevivirán si
IFS
no está desarmado. Además, algunas cosas extrañas podrían suceder si$IFS
se modificara en algún lugar anterior en el script.fuente
Debe leer esta declaración en dos partes, la primera de ellas despeja el valor de la variable IFS, es decir, es equivalente a la más legible
IFS=""
, el segundo es la lectura de laline
variable de entrada estándar,read -r line
.Lo que es específico en esta sintaxis es que la afectación de IFS es transciente y solo válida para el
read
comando.A menos que me falte algo, en ese caso en particular, la limpiezaIFS
no tiene ningún efecto, ya que lo que sea queIFS
esté configurado, toda la línea se leerá en laline
variable. Hubiera habido un cambio en el comportamiento solo en el caso de que se haya pasado más de una variable como parámetro a laread
instrucción.Editar:
El
-r
está allí para permitir la entrada termina con\
no para ser procesado especialmente, es decir, para la barra invertida para ser incluido en laline
variable y no como un carácter de continuación para permitir la entrada de múltiples líneas.El borrado de IFS tiene el efecto secundario de evitar que la lectura recorte potenciales espacios iniciales y finales o caracteres de tabulación, por ejemplo:
Gracias a rici por señalar esa diferencia.
fuente
read -r line
recortará los espacios en blanco iniciales y finales antes de asignar la entrada a laline
variable.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"
mostrará-aa bb--