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 lineguardará datos de stdinuna 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 stdina variable liney luego le da valor linea IFS?
bash
shell-script
Martín
fuente
fuente

Respuestas:
Algunas personas tienen esa noción errónea de que
reades el comando de leer una línea. No es.readlee palabras de una línea (posiblemente barra invertida), donde las palabras están$IFSdelimitadas y la barra invertida puede usarse para escapar de los delimitadores (o líneas continuas).La sintaxis genérica es:
readlee 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 casignaría:$a⇐foo$b⇐bar baz$c⇐blah blahwhatever whateverAhora, 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
-ropción elimina el procesamiento de la barra diagonal inversa. Entonces, el mismo comando anterior con en-rsu 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","",bary""(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 enfooybar. 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 worden una entrada comofoo:) con shells POSIX (nozshni algunaspdkshversiones), esa entrada se considera como unafoopalabra porque en esos shells, los caracteres$IFSse consideran terminadores , porwordlo que contendránfoo, nofoo:.Entonces, la forma canónica de leer una línea de entrada con el
readincorporado es:(tenga en cuenta que para la mayoría de las
readimplementaciones, 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 cmdsintaxis asegura queIFSsolo se configure de manera diferente durante la duración de esecmdcomando.Nota de la historia
La
readconstrucció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
readno admitía una-ropció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 cen una entrada comofoo::barasignaríabara$b, no la cadena vacía.En el shell Bourne, con:
Si
cmdestá integrado (comoreades),varpermanece configuradovaluedespués de quecmdhaya terminado. Eso es particularmente crítico$IFSporque, en el shell Bourne,$IFSse usa para dividir todo, no solo las expansiones. Además, si elimina el carácter de espacio$IFSen 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 < fileoexec 3< file; read var <&3no funcionaban), por lo que era raro en el shell Bourne usarreadcualquier 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 unlinecomando 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 1excepto 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
shdiferencias regulares también es útil para escribir scripts portátiles!)bash-4.4.19,while read -r; do echo "'$REPLY'"; donefunciona comowhile IFS= read -r line; do echo "'$line'"; done.readpara 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í:
IFSes 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,IFSnormalmente hay cualquier espacio en blanco, es por eso que la línea de comando se divide en espacios.VAR=value commandsignifica "modificar el entorno de comando para queVARtenga el valorvalue". Básicamente, el comandocommandveráVARque tiene el valorvalue, pero cualquier comando ejecutado después de eso seguiráVARteniendo 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 establecerIFSuna cadena vacía (no se usará ningún carácter para dividir, por lo tanto, no se producirá división) para quereadlea la línea completa y la vea como una palabra que se asignará a lalinevariable. Los cambiosIFSsolo 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
IFSen este casono esla fuerza 1 no sea necesario. Como está escrito en labashpágina del manual en lareadsección integrada:Como solo tiene la
linevariable, 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 liney terminar con eso.[1] Solo como un ejemplo de cómo un valor
unsetpredeterminado$IFSharáreadque 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
IFSno está desarmado. Además, algunas cosas extrañas podrían suceder si$IFSse 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 lalinevariable 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
readcomando.A menos que me falte algo, en ese caso en particular, la limpiezaIFSno tiene ningún efecto, ya que lo que sea queIFSesté configurado, toda la línea se leerá en lalinevariable. 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 lareadinstrucción.Editar:
El
-restá allí para permitir la entrada termina con\no para ser procesado especialmente, es decir, para la barra invertida para ser incluido en lalinevariable 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 linerecortará los espacios en blanco iniciales y finales antes de asignar la entrada a lalinevariable.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"mostrará-aa bb--