GNU/Linux >> Tutoriales Linux >  >> Linux

¿Entendiendo "ifs=Read -r Line"?

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 a la 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 dar el valor de line a IFS ?

Respuesta aceptada:

En shells POSIX, read , sin ninguna opción no lee una línea , lee palabras de una línea (posiblemente continuación de una barra invertida), donde las palabras son $IFS se puede usar delimitado y barra invertida para escapar de los delimitadores (o líneas continuas).

La sintaxis genérica es:

read word1 word2... remaining_words

read lee stdin un byte a la vez¹ hasta que encuentra un carácter de nueva línea sin escape (o fin de entrada), lo divide de acuerdo con 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   blah   blah
whatever whatever

y con el valor predeterminado de $IFS , read a b c asignaría:

  • $afoo
  • $bbar baz
  • $cblah blahwhatever whatever

Ahora, si se pasa solo un argumento, eso no se convierte en read line . Todavía es read remaining_words . El procesamiento de la barra invertida aún se realiza, los caracteres de espacio en blanco² de IFS aún se eliminan del principio y el final.

El -r La opción elimina el procesamiento de la barra invertida. Así que el mismo comando anterior con -r en su lugar asignaría

  • $afoo
  • $bbar
  • $cbaz blah 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 de IFS² (incluidos el espacio y la tabulación (y la nueva línea, aunque aquí eso no importa a menos que use -d), que también están en el valor predeterminado de $IFS ) y los otros. El tratamiento para esas dos clases de personajes es diferente.

Con IFS=: (: no siendo un carácter de espacio en blanco IFS), una entrada como :foo::bar:: se dividiría en "" , "foo" , "" , bar y "" (y un "" adicional con algunas implementaciones, aunque eso no importa excepto para read -a ). Mientras que si reemplazamos ese : con espacio, la división se realiza solo en foo y bar . Es decir, los principales y los posteriores se ignoran, y las secuencias de ellos se tratan como una sola. Hay reglas adicionales cuando los espacios en blanco y los caracteres que no son espacios en blanco se combinan en $IFS . Algunas implementaciones pueden agregar/eliminar el tratamiento especial duplicando los caracteres en IFS (IFS=:: o IFS=' ' ).

Así que aquí, si no queremos que se eliminen los espacios en blanco iniciales y finales sin escape, debemos eliminar esos espacios 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 algunos pdksh versiones), esa entrada se considera como una foo word porque en esos shells, los caracteres $IFS se consideran terminadores , entonces word contendrá foo , no foo: .

Entonces, la forma canónica de leer una línea de entrada con read integrado es:

IFS= read -r line

(tenga en cuenta que para la mayoría de read implementaciones, que solo funcionan para líneas de texto ya que el carácter NUL no es compatible excepto en zsh ).

Relacionado:Linux:¿compartir archivos entre el host de Linux y el invitado de Windows?

Usando var=value cmd la sintaxis se asegura de que IFS solo se establece de manera diferente durante la duración de ese cmd comando.

Nota de historia

El read builtin fue introducido por el shell Bourne y ya estaba para leer words , no líneas. Hay algunas diferencias importantes con los shells POSIX modernos.

read del shell de Bourne no admitía un -r opción (que fue introducida por el shell Korn), por lo que no hay forma de deshabilitar el procesamiento de barra invertida que no sea preprocesar la entrada con algo como sed 's/\/&&/g' allí.

El shell Bourne no tenía esa noción de dos clases de caracteres (que nuevamente fue introducido por ksh). En el shell de Bourne, todos los caracteres reciben el mismo tratamiento que los espacios en blanco de IFS 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 caparazón de Bourne, con:

var=value cmd

Si cmd es un integrado (como read es), var permanece establecido en value después de cmd ha terminado Eso es particularmente crítico con $IFS porque en el shell de Bourne, $IFS se usa para dividir todo, no solo las expansiones. Además, si elimina el carácter de espacio de $IFS en el shell de Bourne, "[email protected]" ya no funciona.

En el shell de Bourne, la redirección de un comando compuesto hace que se ejecute en un subshell (en las primeras versiones, incluso cosas como read var < file o exec 3< file; read var <&3 no funcionó), por lo que era raro en el shell de Bourne usar read para cualquier cosa que no sea la entrada del usuario en la terminal (donde el manejo de continuación de línea tenía sentido)

Algunos Unices (como HP/UX, también hay uno en util-linux ) todavía tiene una line Comando para leer una línea de entrada (que solía ser un comando UNIX estándar hasta la versión 2 de la especificación UNIX única).

Eso es básicamente lo mismo que 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 IFS= read -r line de ksh. , pero mucho más intuitivo.


Linux
  1. Comprender YAML para Ansible

  2. ¿Entendiendo si?

  3. ¿Leer un archivo orientado a líneas que puede no terminar con una nueva línea?

  4. Leer línea por línea en bash script

  5. ¿Escucha e intérprete del puerto serie de Linux?

Comprender los permisos de archivos de Linux

Comando de lectura Bash

Cómo leer un archivo línea por línea en Bash

Comando Dif en Linux

Comprender los procesos en Linux

¿Cómo leer argumentos de línea de comando en scripts de Shell?