Eu sempre estou realmente hesitante em mexer com $IFS
isso, porque está derrotando um global.
Mas muitas vezes torna o carregamento de seqüências de caracteres em uma matriz do bash agradável e conciso, e para scripts do bash, a concisão é difícil de encontrar.
Então, acho que seria melhor do que nada se eu tentar "salvar" o conteúdo inicial de $IFS
outra variável e depois restaurá-lo imediatamente depois que terminar de usar $IFS
algo.
Isso é prático? Ou é essencialmente inútil e eu deveria IFS
voltar diretamente ao que precisa para seus usos subseqüentes?
bash
shell-script
Steven Lu
fonte
fonte
$' \t\n'
se você estiver usando o bash.unset $IFS
simplesmente nem sempre restaura o que você espera ser o padrão.Respostas:
Você pode salvar e atribuir ao IFS conforme necessário. Não há nada errado em fazer isso. Não é incomum salvar seu valor para restauração após uma modificação temporária e rápida, como o exemplo de atribuição de matriz.
Como @llua menciona em seu comentário à sua pergunta, simplesmente desabilitar o IFS restaurará o comportamento padrão, equivalente a atribuir uma nova linha de aba de espaço.
Vale a pena considerar como pode ser mais problemático não definir / desabilitar explicitamente o IFS do que fazê-lo.
Na edição POSIX 2013, 2.5.3 Shell Variables :
Um shell invocado compatível com POSIX pode ou não herdar o IFS de seu ambiente. A partir disso, segue-se:
"$*"
), mas que pode ser executado em um shell que inicializa o IFS a partir do ambiente, deve explicitamente definir / desabilitar o IFS para se defender contra invasões ambientais.NB É importante entender que, para esta discussão, a palavra "invocado" tem um significado particular. Um shell é chamado apenas quando é chamado explicitamente usando seu nome (incluindo um
#!/path/to/shell
shebang). Um subshell - como pode ser criado por$(...)
oucmd1 || cmd2 &
- não é um shell invocado e seu IFS (junto com a maior parte de seu ambiente de execução) é idêntico ao do pai. Um shell invocado define o valor de$
para seu pid, enquanto os subshells o herdam.Isso não é meramente uma disquisição pedante; existe divergência real nessa área. Aqui está um breve script que testa o cenário usando vários shells diferentes. Ele exporta um IFS modificado (definido como
:
) para um shell chamado que, em seguida, imprime seu IFS padrão.O IFS geralmente não é marcado para exportação, mas, se assim for, observe como o bash, ksh93 e mksh ignoram o ambiente
IFS=:
, enquanto o dash e o busybox o honram.Algumas informações da versão:
Embora bash, ksh93 e mksh não inicializem o IFS do ambiente, eles reexportam o IFS modificado.
Se, por qualquer motivo, você precisar passar o IFS de maneira portável pelo ambiente, não poderá fazê-lo usando o próprio IFS; você precisará atribuir o valor a uma variável diferente e marcar essa variável para exportação. As crianças precisarão atribuir explicitamente esse valor ao seu IFS.
fonte
IFS
valor na maioria das situações em que ele deve ser usado e, portanto, muitas vezes não é terrivelmente produtivo tentar "preservar" seu valor original.read
sem aspas , substituições de comandos sem aspas , expansões aritméticas sem aspas , s ou referências com aspas duplas$*
. Essa lista está no topo da minha cabeça, por isso pode não ser abrangente (especialmente quando se considera as extensões POSIX dos shells modernos).Em geral, é uma boa prática retornar as condições ao padrão.
No entanto, neste caso, nem tanto.
Por quê?:
$' \t\n'
.unset IFS
faz agir como se estivesse definido como padrão .Além disso, o armazenamento do valor do IFS tem um problema.
Se o IFS original não tiver sido
IFS="$OldIFS"
definido , o código definirá o IFS para""
, não o desmarcado.Para realmente manter o valor do IFS (mesmo que não esteja definido), use o seguinte:
fonte
bash
,unset IFS
falha ao desconfigurar o IFS se ele tiver sido declarado local em um contexto pai (contexto de função) e não no contexto atual.Você tem razão em hesitar em derrotar um global. Não tema, é possível escrever um código de trabalho limpo, sem nunca modificar o global real
IFS
, ou executar uma dança de salvar / restaurar complicada e propensa a erros.Você pode:
configure o IFS para uma única chamada:
ou
defina o IFS dentro de um subshell:
Exemplos
Para obter uma string delimitada por vírgula de uma matriz:
Nota: Isso
-
serve apenas para proteger uma matriz vaziaset -u
, fornecendo um valor padrão quando desativado (esse valor é a sequência vazia neste caso) .A
IFS
modificação é aplicável apenas dentro do subshell gerado pela$()
substituição do comando . Isso ocorre porque os subshells têm cópias das variáveis do shell de chamada e, portanto, podem ler seus valores, mas quaisquer modificações realizadas pelo subshell afetam apenas a cópia do subshell e não a variável pai.Você também pode estar pensando: por que não pular o subshell e apenas fazer o seguinte:
Não há chamada de comando aqui e, em vez disso, esta linha é interpretada como duas atribuições de variáveis subsequentes independentes, como se fossem:
Por fim, vamos explicar por que essa variante não funcionará:
O
echo
comando será realmente chamado com suaIFS
variável configurada como,
, masecho
não se importa ou usaIFS
. A mágica de expandir"${array[*]}"
para uma string é feita pelo próprio (sub) shell antesecho
mesmo de ser invocada.Para ler em um arquivo inteiro (que não contém
NULL
bytes) em uma única variável chamadaVAR
:Nota:
IFS=
é o mesmo queIFS=""
eIFS=''
, todos os quais configuram o IFS para a cadeia vazia, o que é muito diferente deunset IFS
: seIFS
não estiver definido, o comportamento de todas as funcionalidades do bash usadas internamenteIFS
é exatamente o mesmo que seIFS
tivesse o valor padrão de$' \t\n'
.Definir
IFS
a sequência vazia garante que os espaços em branco à esquerda e à direita sejam preservados.O
-d ''
or-d ""
tell read apenas interrompe sua chamada atual em umNULL
byte, em vez da nova linha usual.Para dividir
$PATH
seus:
delimitadores:Este exemplo é puramente ilustrativo. No caso geral em que você está dividindo ao longo de um delimitador, é possível que os campos individuais contenham (uma versão de escape) esse delimitador. Pense em tentar ler uma linha de um
.csv
arquivo cujas colunas possam conter vírgulas (escapadas ou citadas de alguma forma). O snippet acima não funcionará como planejado para esses casos.Dito isto, é improvável que você encontre esses
:
caminhos de contenção$PATH
. Embora os nomes de caminho do UNIX / Linux tenham permissão para conter a:
, parece que o bash não seria capaz de lidar com esses caminhos de qualquer maneira, se você tentar adicioná-los ao seu$PATH
e armazenar arquivos executáveis neles, pois não há código para analisar dois pontos escapados / citados : código fonte do bash 4.4 .Por fim, observe que o trecho anexa uma nova linha à direita no último elemento da matriz resultante (como chamado por @ StéphaneChazelas nos comentários agora excluídos) e que, se a entrada for a string vazia, a saída será um elemento único array, onde o elemento consistirá em uma nova linha (
$'\n'
).Motivação
A
old_IFS="${IFS}"; command; IFS="${old_IFS}"
abordagem básica que toca o globalIFS
funcionará conforme o esperado para os scripts mais simples. No entanto, assim que você adiciona alguma complexidade, ela pode se separar facilmente e causar problemas sutis:command
é uma função bash que também modifica a globalIFS
(diretamente ou, oculta da vista, dentro de outra função que ela chama), e, ao fazer isso por engano, usa a mesmaold_IFS
variável global para salvar / restaurar, você recebe um erro.IFS
foi desabilitado, o ingênuo salvar e restaurar não funcionará e até resultará em falhas definitivas se a opção de shell comumente usadaset -u
( mis)set -o nounset
está em vigor.help trap
). Se esse código também modifica o globalIFS
ou assume que ele possui um valor específico, você pode obter erros sutis.Você pode criar uma seqüência mais robusta de salvar / restaurar (como a proposta nesta outra resposta para evitar alguns ou todos esses problemas. No entanto, você precisará repetir esse pedaço de código clichê barulhento sempre que precisar temporariamente de um costume
IFS
. reduz a legibilidade e a manutenção do código.Considerações adicionais para scripts do tipo biblioteca
IFS
é especialmente uma preocupação para autores de bibliotecas de funções de shell que precisam garantir que seu código funcione de maneira robusta, independentemente do estado global (IFS
, opções de shell, ...) imposto por seus invocadores e também sem perturbar esse estado (os invocadores podem confiar para permanecer sempre estático).Ao escrever o código da biblioteca, você não pode confiar em
IFS
ter qualquer valor específico (nem mesmo o padrão) ou mesmo estar definido. Em vez disso, você precisa definir explicitamenteIFS
para qualquer snippet cujo comportamento dependeIFS
.Se
IFS
for explicitamente definido como o valor necessário (mesmo que esse seja o padrão) em cada linha de código em que o valor importa usando qualquer um dos dois mecanismos descritos nesta resposta que seja apropriado para localizar o efeito, o código será ambos independente do estado global e evita estragar tudo. Essa abordagem tem o benefício adicional de torná-lo muito explícito para uma pessoa que está lendo o script queIFS
importa exatamente para esse comando / expansão a um custo textual mínimo (comparado até mesmo ao salvar / restaurar mais básico).Qual código é afetado de
IFS
qualquer maneira?Felizmente, não existem muitos cenários onde isso
IFS
importa (supondo que você sempre cite suas expansões ):"$*"
e"${array[*]}"
expansõesread
direcionamento interno para várias variáveis (read VAR1 VAR2 VAR3
) ou uma variável de matriz (read -a ARRAY_VAR_NAME
)read
segmentar uma única variável quando se trata de caracteres em branco à esquerda / à esquerda ou em branco que aparecemIFS
.fonte
:
quando:
é o delimitador?:
é um caractere válido para usar em um nome de arquivo na maioria dos sistemas de arquivos UNIX / Linux, portanto, é perfeitamente possível ter um diretório com um nome que contenha:
. Talvez algumas conchas têm uma disposição para escapar:
no caminho usando algo parecido\:
, e então você iria ver colunas aparecendo que não são delimitadores de reais (Parece que o bash não permite tais escapar. A função de baixo nível usado quando iteração sobre$PATH
apenas pesquisas para:
em uma sequência C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
exemplo de divisão:
mais claro.Por que arriscar um erro de digitação no IFS
$' \t\n'
quando tudo o que você precisa fazer éComo alternativa, você pode chamar um subshell se não precisar de nenhuma variável definida / modificada dentro de:
fonte
IFS
foi inicialmente desativado.