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

12

Eu posso ter algo absolutamente errado, mas me parece convincente, que definir o IFS como um dos comandos na lista de tarefas / tarefas não tem absolutamente nenhum efeito.
O IFS externo (fora da whileconstrução) prevalece em todos os exemplos mostrados no script abaixo.

O que está acontecendo aqui? Eu entendi errado o que o IFS faz nessa situação? Eu esperava que os resultados da divisão de matriz fossem mostrados na coluna "esperado".


#!/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-----"

Resultado:

-----   --  --   --------       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
fonte

Respostas:

17

(Desculpe, longa explicação)

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

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

  • variáveis ​​de shell (que existem apenas dentro de um shell e são locais no shell)
  • variáveis ​​de ambiente, que existem para todos os processos. Esses geralmente são preservados fork()e exec(), portanto, os processos filhos os herdam.

Quando você chama um comando com:

  A=foo B=bar command

o comando é executado dentro de um ambiente em que a variável (ambiente) Aestá definida como fooe Bestá definida como bar. Mas com esta linha de comando, as variáveis ​​atuais do shell Ae Bsão mantidas inalteradas .

Isso é diferente de:

A=foo; B=bar; command

Aqui, as variáveis ​​shell Ae Bsão definidas e o comando é executado sem variáveis ​​de ambiente Ae Bdefinidas. Valores Ae Bsão inacessíveis a partir de command.

No entanto, se algumas variáveis ​​de shell estiverem em ordem export, as variáveis ​​de ambiente correspondentes serão sincronizadas com suas respectivas variáveis ​​de shell. Exemplo:

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

Com esse código, as variáveis ​​de shell e as variáveis ​​de ambiente do shell são definidas como fooe bar. Como as variáveis ​​de ambiente são herdadas por subprocessos, commandpoderão acessar seus valores.

Para voltar à sua pergunta original, em:

IFS='a' read

somente readé afetado. E, de fato, neste caso, readnão se importa com o valor da IFSvariável. Ele é usado IFSsomente quando você solicita que a linha seja dividida (e armazenada em várias 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"

IFSnão é usado por, a readmenos que seja chamado com argumentos. ( Editar: isso não é exatamente verdade: caracteres de espaço em branco, como espaço e tabulação, presentes em IFSsempre são ignorados no início / fim da linha de entrada.)

Stéphane Gimenez
fonte
Que ótima explicação! É tão simples! Estou confuso com a sintaxe "sem ponto e vírgula" há meses; e é simplesmente um caso de uma variável local! .. rozcietrzewiacz abriu o caminho para mim (grande momento) na outra pergunta ... e você acabou de colocar a cereja no topo do bolo ... eu estive acordado a noite toda, e certamente valeu a pena por respostas tão boas e claras! .. Obrigado ..
Peter.O
Uhm. Eu tive que ler esse comentário de edição várias vezes antes de obtê-lo - você quer dizer que os caracteres de espaço em branco presentes $IFSsão removidos no início / fim da linha de entrada, presumo? (Que é como ele funciona.)
zrajm
Por favor, dê uma olhada nisto: unix.stackexchange.com/questions/382963/…
O valor do IFS é importante mesmo ao ler uma única variável, porque o shell ainda faz a divisão de palavras na entrada. Por exemplo, digitar os caracteres a<tab>bem read varresultará em var com o valor a<space>b, mas se você tiver IFS='<newline>' read var, o valor de var será a<tab>b.
9788 Johnscall
8

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

Você perde o escopo dos readexemplos. Não modificação do IFS dentro do loop nos seus casos de teste. Permita-me apontar exatamente onde o segundo IFS tem 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 qualquer programa executado no shell. A variável que você (re) define na linha de comando afeta a execução do programa. E apenas isso (já que você não exporta). Portanto, para usar o redefinido IFSnessa linha, você precisará pedir readpara atribuir valores a mais de uma variável . Veja estes 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 acabei de aprender com Gilles , pode haver um benefício em definir IFS=''(em branco) ao ler apenas um campo: evita truncamento de espaço em branco no início da linha.

rozcietrzewiacz
fonte
Bom .. Obrigado ... eu consegui desta vez .. e eu adoro o seu esboço :)
Peter.O
OK, agora eu li o seu comentário e vejo que você não percebeu a minha resposta para esse problema na outra pergunta. Talvez você possa reverter o outro e excluir isso, pois é realmente um problema geral?
rozcietrzewiacz
Sim, as duas perguntas têm um tema relacionado, mas o título da outra é "Por que é IFS= readusado preferencialmente apenas para redefinir a variável de ambiente IFS". Eu não sabia, então, que variáveis ​​locais poderiam ser definidas pelo chamador de um comando. Essa foi a resposta para essa pergunta. Ele evoluiu ainda mais para abordar o ponto principal dessa pergunta, mas quando percebi isso, eu já tinha feito essa pergunta ... Talvez as duas perguntas sejam tão parecidas quanto duas sed, então meu sentimento é mantê-lo como está ... Mais títulos para os googlers no Google.
precisa saber é o seguinte