Quando posso usar um IFS temporário para divisão de campos?

19

No bash, diga que você tem var=a.b.c., então:

$ IFS=. printf "%s\n" $var
a.b.c

No entanto, esse uso IFSentra em vigor ao criar uma matriz:

$ IFS=. arr=($var)
$ printf "%s\n" "${arr[@]}"
a
b
c

Isso é muito conveniente, com certeza, mas onde isso está documentado? Uma leitura rápida das seções em Matrizes ou Divisão de palavras na documentação do Bash não fornece nenhuma indicação. Uma busca por IFSmeio da documentação de uma única página não fornece quaisquer sugestões sobre este efeito também.

Não tenho certeza de quando posso fazer com segurança:

IFS=x do something

E espere que IFSisso afete a divisão do campo.

muru
fonte

Respostas:

28

A idéia básica é que os VAR=VALUE some-commandconjuntos VARpara VALUEa execução de some-commandquando some-commandé um comando externo, e ele não fica mais chique do que isso. Se você combinar essa intuição com algum conhecimento de como um shell funciona, na maioria dos casos, você encontrará a resposta certa. A referência do POSIX é "Comandos simples" no capítulo "Linguagem de comando do shell" .

Se some-commandé um comando externo , VAR=VALUE some-commandé equivalente a env VAR=VALUE some-command. VARé exportado no ambiente de some-commande seu valor (ou falta de valor) no shell não muda.

Se some-commandé uma função , então VAR=VALUE some-commandé equivalente a VAR=VALUE; some-command, ou seja, a atribuição permanece em vigor após o retorno da função e a variável não é exportada para o ambiente. A razão disso tem a ver com o design do shell Bourne (e posteriormente com a compatibilidade com versões anteriores): ele não tinha capacidade para salvar e restaurar valores variáveis ​​em torno da execução de uma função. Não exportar a variável faz sentido, pois uma função é executada no próprio shell. No entanto, ksh (incluindo ATT ksh93 e pdksh / mksh), bash e zsh implementam o comportamento mais útil, onde VARé definido apenas durante a execução da função (também é exportado). No ksh , isso é feito se a função for definida com a sintaxe kshfunction NAME …, não se for definido com a sintaxe padrão NAME (). No bash , isso é feito apenas no modo bash, não no modo POSIX (quando executado com POSIXLY_CORRECT=1). No zsh , isso é feito se a posix_builtinsopção não estiver configurada; esta opção não está definida por padrão, mas está ativada por emulate shou emulate ksh.

Se some-commandfor um builtin, o comportamento depende do tipo de builtin. Construções especiais se comportam como funções. Built-ins especiais são os que precisam ser implementados dentro do shell porque afetam o estado do shell (por exemplo break, cdafeta o fluxo de controle, afeta o diretório atual, setafeta parâmetros e opções posicionais ...). Outros componentes internos são integrados apenas para desempenho e conveniência (principalmente - por exemplo, o recurso bash printf -vpode ser implementado apenas por um componente interno) e se comportam como um comando externo.

A atribuição ocorre após a expansão do alias; portanto, se some-commandfor um alias , expanda-o primeiro para descobrir o que acontece.

Observe que, em todos os casos, a atribuição é executada após a análise da linha de comando, incluindo qualquer substituição de variável na própria linha de comando. Então , var=a; var=b echo $varimprime a, porque $varé avaliado antes da atribuição. E, assim, IFS=. printf "%s\n" $varusa o IFSvalor antigo para dividir $var.

Eu cobri todos os tipos de comandos, mas há mais um caso: quando não há comando para executar , ou seja, se o comando consiste apenas em atribuições (e possivelmente redirecionamentos). Nesse caso, a atribuição permanece no local . VAR=VALUE OTHERVAR=OTHERVALUEé equivalente a VAR=VALUE; OTHERVAR=OTHERVALUE. Então IFS=. arr=($var), depois , IFSpermanece definido como .. Como você pode usar $IFSna atribuição arrcom a expectativa de que ele já tenha seu novo valor, faz sentido que o novo valor de IFSseja usado para a expansão de $var.

Em resumo, você pode usar apenas IFSpara divisão temporária de campos:

  • iniciando um novo shell ou um subshell (por exemplo, third=$(IFS=.; set -f; set -- $var; echo "$3")é uma maneira complicada de fazer, third=${var#*.*.}exceto que eles se comportam de maneira diferente quando o valor de varcontém menos de dois .caracteres);
  • em ksh, com IFS=. some-functiononde some-functioné definido com a sintaxe ksh function some-function …;
  • no bash e zsh, IFS=. some-functiondesde que estejam operando no modo nativo em oposição ao modo de compatibilidade.
Gilles 'SO- parar de ser mau'
fonte
" IFSpermanece definido como ." Eek. Depois de ler a primeira parte, isso faz sentido, mas antes de postar este Q, eu não esperava isso.
muru
1
Esta é uma resposta a uma pergunta diferente
Schily
Alguma explicação adicional nesta resposta de alguns anos atrás .
Ti Strga
6

A resposta do @Gilles é realmente ótima, ele explica (em detalhes) uma questão complexa.

No entanto, acredito que a resposta para o porquê deste comando:

$ IFS=. printf "%s\n" $var
a.b.c

funciona como é a ideia simples de que toda a linha de comando é analisada antes de ser executada. E que cada "palavra" é processada uma vez pelo shell.
As atribuições, como IFS=., estão atrasadas (a etapa 4 é a última):

4.- Cada atribuição de variável deve ser expandida ...

até pouco antes do comando ser executado e todas as expansões nos argumentos serem processadas primeiro para criar esta linha executável:

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c

O valor de $varé expandido com o IFS "antigo" para a.b.cantes que o comando printfreceba os argumentos "%s\n"e a.b.c.

Eval

Um nível de atraso pode ser introduzido por eval:

$ IFS=. eval printf "'%s\n'" \$var
a
b
c

A linha é analisada (1ª vez) e 'IFS =.' está definido no ambiente da seguinte maneira:

$ printf '%s\n' $var

Em seguida, é analisado novamente para isso:

$ printf '%s\n' a b c

E executado para isso:

a
b
c

O valor de $var(abc) é dividida com o valor de FI em uso: ..

Meio Ambiente

A parte complexa e complicada é o que é válido no ambiente quando !!!

Isso é explicado muito bem na primeira parte da resposta de Gilles.

Com um detalhe adicional.

Quando este comando é executado:

$ IFS=. arr=($var)

O valor do IFS é retido no ambiente atual, sim:

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 

IFS para uma única declaração.

Mas poderia ser evitado: Configurando o IFS para uma única instrução

$ IFS=. command eval arr\=\(\$var\)

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 
Comunidade
fonte
2

Sua pergunta sobre

var=a.b.c
IFS=. printf "%s\n" $var

é uma caixa de canto.

Isso ocorre porque o macro expansioncomando acontece antes da variável shell IFS=.ser configurada.

Em outras palavras: quando $varé expandido, o IFSvalor anterior ativo e, em seguida, IFSé definido como '.'.

esperto
fonte