En `while IFS = read..`, ¿por qué IFS no tiene ningún efecto?

12

Puede que tenga algo absolutamente incorrecto, pero me parece convincente, que establecer IFS como uno de los comandos en la lista pre-do / done no tiene absolutamente ningún efecto.
El IFS externo (fuera de la whileconstrucción) prevalece en todos los ejemplos que se muestran en el script a continuación.

¿Que está pasando aqui? ¿Tengo una idea equivocada de lo que hace IFS en esta situación? Esperaba que los resultados de la división de matriz fueran como se muestran en la columna "esperada".


#!/bin/bash
xifs() { echo -n "$(echo -n "$IFS" | xxd -p)"; } # allow for null $IFS 
show() { x=($1) 
         echo -ne "  (${#x[@]})\t |"
         for ((j=0;j<${#x[@]};j++)); do 
           echo -n "${x[j]}|"
         done
         echo -ne "\t"
         xifs "$IFS"; echo
}
data="a  b   c"
echo -e "-----   --  -- \t --------\tactual"
echo -e "outside        \t  IFS    \tinside" 
echo -e "loop           \t Field   \tloop" 
echo -e "IFS     NR  NF \t Split   \tIFS (actual)" 
echo -e "-----   --  -- \t --------\t-----"
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 1'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 2'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 3'; show "$REPLY"; done
IFS=" ";      xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 4'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 5'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 6'; show "$REPLY"; done
IFS=;         xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 7'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t 8'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 9'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t10'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t11'; show "$REPLY"; done
echo -e "-----   --  -- \t --------\t-----"

Salida:

-----   --  --   --------       actual   
outside           IFS           inside                assigned   
loop             Field          loop    #              inner
IFS     NR  NF   Split          IFS     #  expected    IFS
-----   --  --   --------       -----   #  ---------  --------
20090a   1  (3)  |a|b|c|        20090a  #                              
20090a   2  (3)  |a|b|c|        20090a  #  |a  b   c|  IFS=
20090a   3  (3)  |a|b|c|        20090a  #  |a  |   c|  IFS=b
20       4  (3)  |a|b|c|        20      #                          
20       5  (3)  |a|b|c|        20      #  |a  b   c   IFS=
20       6  (3)  |a|b|c|        20      #  |a  |   c|  IFS=b
         7  (1)  |a  b   c|             #                          
         8  (1)  |a  b   c|             #  |a|b|c|     IFS=" "
         9  (1)  |a  b   c|             #  |a  |   c|  IFS=b
62      10  (2)  |a  |   c|     62      #  |a  b   c|  IFS=
62      11  (2)  |a  |   c|     62      #  |a|b|c|     IFS=" "
-----   --  --   --------       -----      ---------   -------                        
Peter.O
fuente

Respuestas:

17

(Lo siento, larga explicación)

Sí, la IFSvariable in while IFS=" " read; do …no tiene efecto en el resto del código.

Primero, vamos a precisar que la línea de comandos del shell presenta dos tipos diferentes de variables:

  • variables de shell (que solo existen dentro de un shell y son locales al shell)
  • variables de entorno, que existen para cada proceso. Esos generalmente se conservan fork()y exec(), por lo tanto, los procesos secundarios los heredan.

Cuando llamas a un comando con:

  A=foo B=bar command

el comando se ejecuta dentro de un entorno donde la variable (entorno) Ase establece fooy Bse establece en bar. Pero con esta línea de comando, las variables de shell actuales Ay Bse dejan sin cambios .

Esto es diferente de:

A=foo; B=bar; command

Aquí, las variables de shell Ay Bse definen y el comando se ejecuta sin variables de entorno Ay se Bdefine. Los valores de Ay Bson inaccesibles desde command.

Sin embargo, si algunas variables de shell son export-ed, las variables de entorno correspondientes se sincronizan con sus respectivas variables de shell. Ejemplo:

export A
export B
A=foo; B=bar; command

Con este código, las variables de shell y las variables de entorno de shell se establecen en fooy bar. Como las subprocesos heredan las variables de entorno, commandpodrán acceder a sus valores.

Para volver a su pregunta original, en:

IFS='a' read

solo readse ve afectado. Y de hecho, en este caso, readno le importa el valor de la IFSvariable. Solo se usa IFScuando solicita que la línea se divida (y se almacene en varias variables), como en:

echo "a :  b :    c" | IFS=":" read i j k; \
    printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"

IFSno se usa a readmenos que se llame con argumentos. ( Editar: Esto no es exactamente cierto: los caracteres de espacio en blanco, es decir, el espacio y la tabulación, presentes IFSsiempre se ignoran al principio / al final de la línea de entrada).

Stéphane Gimenez
fuente
¡Qué gran explicación! ¡Es tan simple! He estado desconcertado por esa sintaxis 'sin punto y coma' durante meses; ¡y es simplemente un caso de que significa una variable local! ... rozcietrzewiacz me abrió el camino (a lo grande) en la otra pregunta ... y acabas de poner la guinda del pastel ... He estado ¡Estuve despierto toda la noche en este caso, y ciertamente ha valido la pena por respuestas tan buenas y claras! .. Gracias ..
Peter.O
Uhm Tuve que leer ese comentario de edición varias veces antes de que lo recibiera. ¿Quiere decir que los espacios en blanco que están presentes $IFSse eliminan al principio / al final de la línea de entrada, supongo? (Así es como funciona.)
zrajm
El valor de IFS es importante incluso cuando se lee una sola variable, porque el shell todavía divide las palabras en la entrada. Así, por ejemplo, al escribir los caracteres a<tab>ben read vardará lugar a var tener el valor a<space>b, pero si por el contrario tiene IFS='<newline>' read varentonces el valor de var será a<tab>b.
John Hascall
8

En pocas palabras, debe leer más de una variable a la vez para que la IFS=<something> read ...construcción tenga un efecto visible en sus ejemplos 1 .

Echas de menos el alcance de readen los ejemplos. No hay modificación de IFS dentro del ciclo en sus casos de prueba. Permítanme señalar exactamente, ¿dónde tiene efecto el segundo IFS en cada una de sus líneas:

 IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo ...
                                                      ^      ^
                                                      |      |
                                          from here --'       `- to here :)

Es igual que con cualquier programa ejecutado en el shell. La variable que (re) define en la línea de comandos afecta la ejecución del programa. Y solo eso (ya que no exportas). Por lo tanto, para hacer un uso de redefinido IFSen dicha línea, tendría que pedir readasignar valores a más de una variable . Echa un vistazo a estos ejemplos:

 $ data="a  b   c"
 $ echo "$data" | while           read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a|b||c|
 $ echo "$data" | while IFS=      read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a b c||||
 $ echo "$data" | while IFS='a'   read A B C; do echo \|$A\|$B\|\|$C\|; done
 || b c|||
 $ echo "$data" | while IFS='ab'  read A B C; do echo \|$A\|$B\|\|$C\|; done
 || || c|

1 Como acabo de aprender de Gilles , en realidad podría ser beneficioso establecer IFS=''(en blanco) al leer solo un campo: evita el truncamiento del espacio en blanco al comienzo de la línea.

rozcietrzewiacz
fuente
Bien ... Gracias ... Lo tengo esta vez ... y me encanta tu boceto :)
Peter.O
Bien, ahora he leído tu comentario y veo que no has notado mi respuesta a ese problema en la otra pregunta. ¿Tal vez podría revertir el otro y eliminar esto, ya que realmente es un problema general?
rozcietrzewiacz
Sí, las dos preguntas tienen un tema relacionado, pero el título de la otra es "¿Por qué se IFS= readusa con preferencia para volver a configurar la variable de entorno IFS"? No tenía conciencia, entonces, que las variables locales podrían ser establecidas por el llamante de un comando. Esa fue la respuesta a esa pregunta. Evolucionó aún más para abordar el punto principal de esta pregunta, pero cuando me di cuenta de eso, ya había hecho esta pregunta ... Tal vez las dos preguntas son tan similares como dos sedpreguntas, por lo que siento que lo mantengo así ... Más títulos para googlers a google.
Peter.O