Por que verificar a existência do arquivo antes de procurá-lo?

13

Ao tentar originar um arquivo, você não gostaria de um erro dizendo que o arquivo não existe para saber o que corrigir?

Por exemplo, a nvm recomenda adicionar isso ao seu perfil / rc:

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

Com o exposto acima, se nvm.shnã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.

JBallin
fonte
3
Você não deveria. É atrevido. Tente a fonte e lide com o erro, se houver #
Mikel
2
@Mikel certo! então por que eu vejo isso em todo lugar?
JBallin
3
@FaheemMitha, bem, se o que você está fazendo é sensível à segurança (ou seja, seu programa funciona em nome de outra pessoa), então você realmente precisa se preocupar com as condições da corrida ( TOCTOU ). Provavelmente não é o caso aqui, já que você tem problemas maiores se alguém modificar arquivos HOME.
ilkkachu
8
@FaheemMitha Nunca é melhor verificar a existência primeiro. A situação pode mudar entre teste e uso, gerando falsos positivos e falsos negativos: e você ainda precisa lidar com a falha no uso. E, como você mencionou o desempenho, o teste antes do uso é endemicamente duas vezes mais lento do que isso e deixa o sistema fazer, o que será feito de qualquer maneira. Não pode ser assim.
user207421
1
@ SimonRichter, neste caso, a nvm não funcionará. O usuário precisaria descobrir que o arquivo está ausente por conta própria.
JBallin

Respostas:

25

Nos shells POSIX, .é um built-in especial, portanto, sua falha faz com que o shell seja encerrado (em alguns shells bash, isso é feito apenas no modo POSIX).

O que qualifica como um erro depende do shell. Nem todos saem com um erro de sintaxe ao analisar o arquivo, mas a maioria sai quando o arquivo de origem não pode ser encontrado ou aberto. Não conheço nenhum que sairia se o último comando no arquivo de origem retornasse com um status de saída diferente de zero (a menos que a errexitopção esteja ativada).

Aqui fazendo:

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

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

Ou seja, não deve ser considerado um erro (erro fatal nos shells POSIX) se o arquivo não estiver lá, esse arquivo será 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 seria que o shell sairia com um erro se o arquivo fosse removido entre [e ., mas eu diria que é válido considerar um erro que esse arquivo de caminho fixo desapareça repentinamente enquanto o script é corrida.

Por outro lado,

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

em que command¹ remove o atributo especial do .comando (para que ele não saia do shell por erro) não funcionaria como:

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

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

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

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

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

    Uma versão um pouco 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 $file(no caso anterior, se você receber 1, não sabe 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 do que foi dito acima: tudo bem se o arquivo não estiver lá e, para shells POSIX, falhas ao abrir (ou analisar) o arquivo são relatadas, mas não são fatais (o que pode ser mais desejável para ~/.profile ).


¹ Nota: No zshentanto, você não pode usar commandassim, a menos que seja shemulado; note que no shell Korn, sourcena verdade é um alias para command ., uma variante não especial de.

Stéphane Chazelas
fonte
Interessante! Eu não sabia disso sobre o POSIX sh. Mas a questão era sobre .bash_profile. Acho melhor prevenir do que remediar, mas o bash está sempre no modo POSIX quando .bash_profileé fornecido?
Mikel
(Eu sei que você poderia interpretar essa questão como aplicar de forma mais ampla a todos os conchas POSIX com base na leitura do link da fonte nvm na pergunta.)
Mikel
@ Mikel, minha resposta ainda se aplica a bashquando não estiver no modo POSIX. Você desejaria [ -e /file ] && . /filese não considerasse um erro quando o arquivo não existe. A fonte try então lida com o erro, se não for possível fazer aqui.
Stéphane Chazelas
1
@ Mikel, isso é contraproducente. 1) que não impede a saída com erro com shells POSIX (ou bash no modo POSIX) 2), que duplica a mensagem de erro (sua no stdout), .já reportará um erro (no stderr). E se a intenção é não considerar um erro quando o arquivo não existe, isso não está correto (e não é possível, no status de saída, dizer se .falhou porque o arquivo não existia ou não era legível ou estava não pode ser analisado ou o último comando falhou), que são os pontos que estou fazendo aqui nesta resposta.
Stéphane Chazelas
1
Com relação à condição de corrida - é muito menos um problema no IMHO para o login falhar uma vez (enquanto algo estranho está acontecendo no sistema) do que para o logon falhar consistentemente (mesmo se houver algo não muito certo na configuração do usuário -arquivos). Portanto, uma condição de corrida nessa verificação ainda é uma melhoria por não ter a verificação.
Ruakh
5

Mantenedor de nvm resposta de:

é fácil desinstalar o nvm simplesmente excluindo o arquivo; forçar um trabalho adicional (para rastrear onde as linhas estão nessa fonte nvm) não parece particularmente valioso.

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

É mais simples e seguro.

Ele defende contra shells POSIX que saem na inicialização devido a um arquivo ausente (por vários motivos). Aqueles que usam shells não POSIX (por exemplo, bash) podem remover o condicional, se preferirem.

JBallin
fonte
1
Oponho-me à sua interpretação de que é "voltada para iniciantes". É defensivo. Você não deseja que o shell de login de um usuário termine inesperadamente na inicialização, apenas porque esse arquivo está ausente (por qualquer motivo). Se essa linha de código estiver em um dos arquivos init do shell /etc, isso permitirá que alguns usuários tenham o arquivo e outros não. IMHO, a nvmresposta do mantenedor está apenas tocando em um aspecto.
Kusalananda
1

Como JBallin e Stéphane Chazelas apontaram, nos shells do POSIX, o fornecimento de um arquivo que não existe causaria falha no login.

Porém, adicionar um teste para ver se o arquivo existe e, em seguida, tentar originar ele pode causar algo chamado condição de corrida. Se algo mudar nvm.shentre o [ -s nvm.sh ]e o . nvm.sh, causará exatamente o erro que eles estão tentando impedir, embora muito mais raramente.

Em geral, a maneira de impedir as condições da corrida é apenas tentar o que você deseja fazer e depois 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, . falhar fará com que o shell saia imediatamente, antes que qualquer manipulação de erro possa ser executada.

Minha resposta argumenta que as conchas POSIX não são relevantes para esta questão, porque .bash_profile nunca devem ser executados no modo POSIX. Assim, 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 esteja desativado usando a técnica descrita em /unix//a/383581/3169 .

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

Mikel
fonte