Em 'while IFS = read ..', por que o IFS não tem efeito?

11

Eu posso ter algo absolutamente errado, mas parece convincente para mim, que definir o IFS como um dos comandos na lista pré-do / feito não tem absolutamente nenhum efeito.
O IFS externo (fora da construção while ) prevalece em todos os exemplos mostrados no script abaixo.

O que está acontecendo aqui? Eu tenho a idéia errada do que a IFS faz nessa situação? Eu esperava que os resultados da divisão de array fossem mostrados na coluna "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-----"

Saída:

-----   --  --   --------       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=" "
-----   --  --   --------       -----      ---------   -------                        
    
por Peter.O 17.08.2011 / 21:09

2 respostas

15

(Desculpe, longa explicação)

Sim, a variável IFS em while IFS=" " read; do … não tem efeito no restante do código.

Vamos primeiro precisar que a linha de comando do shell apresenta dois tipos diferentes de variáveis:

  • variáveis de shell (que só existem em um shell e são locais no shell)
  • variáveis de ambiente, que existem para todos os processos. Geralmente, eles são preservados em fork() e exec() , de modo que os processos filhos os herdam.

Quando você chama um comando com:

  A=foo B=bar command

o comando é executado em um ambiente no qual a variável (ambiente) A está definida como foo e B está definida como bar . Mas com esta linha de comando, as variáveis atuais do shell A e B são deixadas inalteradas .

Isso é diferente de:

A=foo; B=bar; command

Aqui, as variáveis de shell A e B são definidas e o comando é executado sem as variáveis de ambiente A e B defined. Os valores de A e B são inacessíveis em command .

No entanto, se algumas variáveis do shell forem export -ed, as variáveis de ambiente correspondentes serão sincronizadas com suas respectivas variáveis do shell. Exemplo:

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

Com este código, as duas variáveis shell e as variáveis ambiente shell são definidas como foo e bar . Como as variáveis de ambiente são herdadas por subprocessos, command poderá acessar seus valores.

Para voltar à sua pergunta original, em:

IFS='a' read

apenas read é afetado. E, de fato, nesse caso, read não se importa com o valor da variável IFS . Ele usa IFS somente quando você pede que a linha seja dividida (e armazenada em diversas variáveis), como em:

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

IFS não é usado por read , a menos que seja chamado com argumentos. ( Editar: Isso não é exatamente verdade: caracteres em branco, ou seja, espaço e tabulação, presentes em IFS , são sempre ignorados no início / fim da linha de entrada.)

    
por 17.08.2011 / 22:17
6

Simplifique, você deve ler para mais de uma variável por vez para que a construção IFS=<something> read ... tenha um efeito visível em seus exemplos 1 . p>

Você perdeu o escopo de read nos exemplos. Não há nenhuma modificação do IFS dentro do loop em seus casos de teste. Permita-me apontar exatamente onde o segundo IFS tem seu efeito em cada uma de suas linhas:

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

É como acontece com qualquer programa executado no shell. A variável que você (re) define na linha de comando afeta a execução do programa. E somente que (desde que você não exporta). Portanto, para fazer um uso de IFS redefinido nessa linha, você teria que pedir a read para atribuir valores a mais de uma variável . Dê uma olhada nestes exemplos:

 $ 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 eu acabei de aprender de Gilles , pode realmente haver um benefício de definir IFS='' (blank) ao ler apenas um campo: evita truncamento de espaço em branco no início da linha.

    
por 17.08.2011 / 22:25