Por que verificar a existência de arquivos antes de fazer o sourcing?

12

Ao tentar criar um arquivo, você não quer um erro dizendo que o arquivo não existe, então você sabe o que corrigir?

Por exemplo, nvm recomenda adicioná-lo ao seu perfil / rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Com acima, se nvm.sh não existir, você receberá um "erro silencioso". Mas se você tentar . "$NVM_DIR/nvm.sh" , a saída será FILE_PATH: No such file or directory .

    
por JBallin 17.06.2018 / 03:17

3 respostas

24

Em shells POSIX, . é um builtin especial, então sua falha faz com que o shell saia (em alguns shells como bash , ele é feito somente no modo POSIX).

O que se qualifica como erro depende do shell. Nem todos saem com um erro de sintaxe ao analisar o arquivo, mas a maioria sai quando o arquivo originado não pode ser encontrado ou aberto. Eu não sei de nenhum que saia se o último comando no arquivo originado retornar com um status de saída diferente de zero (a menos que a opção errexit esteja ligada, é claro).

Aqui fazendo:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

É um caso em que você deseja criar o arquivo se ele estiver lá e não, se não estiver (ou está vazio aqui com -s ).

Isto é, não deve ser considerado um erro (erro fatal em shells POSIX) se o arquivo não estiver lá, esse arquivo é considerado um arquivo opcional.

Ainda seria um erro (fatal) se o arquivo não fosse legível ou fosse um diretório ou (em alguns shells) se houvesse um erro de sintaxe ao analisá-lo, o que seriam condições reais de erro que deveriam ser relatadas.

Alguns argumentam que há uma condição de corrida. Mas a única coisa que isso significa é que o shell sairá com um erro se o arquivo for removido entre o [ e o . , mas eu diria que é válido considerá-lo um erro que esse arquivo de caminho fixo desaparecem de repente enquanto o script está sendo executado.

Por outro lado,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

onde command ¹ remove o atributo especial para o comando . (para não sair do shell em erro) não funcionaria como:

  • esconderia os erros de . , mas também os erros dos comandos executados no arquivo de origem
  • também ocultaria condições reais de erro, como o arquivo com permissões erradas.

Outras sintaxes comuns (veja por exemplo grep -r /etc/default /etc/init* nos sistemas Debian para os scripts init que ainda não foram convertidos em systemd (onde EnvironmentFile=-/etc/default/service é usado para especificar um arquivo de ambiente opcional)) include:

  • [ -e "$file" ] && . "$file"

    Verifique se o arquivo está lá, continue a acessá-lo se estiver vazio. Ainda erro fatal se não puder ser aberto (mesmo que esteja lá ou esteja lá). Você pode ver mais variantes como [ -f "$file" ] (existe e é um arquivo regular ), [ -r "$file" ] (é legível) ou combinações desses.

  • [ ! -e "$file" ] || . "$file"

    Uma versão ligeiramente melhor. Torna mais claro que o arquivo não existente é um caso OK. Isso também significa que $? refletirá o status de saída do último comando executado em $file (no caso anterior, se você obtiver 1 , não saberá se é porque $file não existia ou se esse comando falhou).

  • command . "$file"

    Espere que o arquivo esteja lá, mas não saia se não puder ser interpretado.

  • [ ! -e "$file" ] || command . "$file"

    Combinação dos itens acima: tudo bem se o arquivo não estiver lá, e para os POSIX shells, falhas para abrir (ou analisar) o arquivo são reportadas, mas não são fatais (o que pode ser mais desejável para ~/.profile ). / p>

¹ Nota: Em zsh , no entanto, você não pode usar command , a não ser em sh emulation; Observe que no shell Korn, source é, na verdade, um alias para command . , uma variante não especial de .

    
por 17.06.2018 / 08:15
5

Mantenedor da resposta de nvm :

it's easy to uninstall nvm by simply deleting the file; forcing additional work (to track down where the line(s) are that source nvm) doesn't seem particularly valuable.

Minha interpretação (combinada com a excelente explicação de Stéphane e o comentário de Kusalananda):

É mais simples e seguro.

Defende-se contra shells POSIX que saem na inicialização devido a um arquivo ausente (por várias razões). Aqueles que usam shells não POSIX (por exemplo, bash) podem remover o condicional, se preferirem.

    
por 17.06.2018 / 09:32
1

Como JBallin e Stéphane Chazelas apontou, em shells POSIX, o fornecimento de um arquivo que não existe causaria o login para falhar.

Mas adicionar um teste para ver se o arquivo existe e, em seguida, tentar obtê-lo pode causar algo chamado de condição de corrida. Se algo alterar nvm.sh entre o [ -s nvm.sh ] e o . nvm.sh , ele causará exatamente o erro que está tentando evitar, embora muito mais raramente.

Em geral, a maneira de evitar condições de corrida é tentar o que você deseja fazer e, em seguida, lidar com o erro se ele falhar, por exemplo,

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Acontece que isso não funciona em shells POSIX, porque, como acima, . failing fará com que o shell saia imediatamente, antes que qualquer tratamento de erro possa ser executado.

Minha resposta argumenta que as shells POSIX não são relevantes para esta questão, porque .bash_profile nunca deve ser executado no modo POSIX. Então, podemos apenas fazer o código acima de qualquer maneira.

Para ser mais seguro, podemos garantir que o modo POSIX não esteja em vigor ou garantir que o modo POSIX seja desativado usando a técnica descrita em link .

A resposta de Stéphane tem algumas sugestões úteis sobre como lidar com todos os shells POSIX, que eu acho que foi a intenção do autor nvm, mas foi sutilmente diferente do que a pergunta aqui foi perguntando, por isso temos várias abordagens possíveis, dependendo do que seu objetivo é.

    
por 20.06.2018 / 19:13