Encontre links simbólicos quebrados com Python

votos
16

Se eu chamar os.stat()em uma quebrada symlink, python lança uma OSErrorexceção. Isto o torna útil para encontrá-los. No entanto, existem algumas outras razões que os.stat()pode lançar uma exceção similar. Existe uma maneira mais precisa de detectar quebrado symlinkscom Python no Linux?

Publicado 21/08/2008 em 20:00
fonte usuário
Em outras línguas...                            


7 respostas

votos
20

Um ditado comum Python é que é mais fácil pedir perdão do que permissão. Enquanto eu não sou um fã desta declaração na vida real, que se aplica em muitos dos casos. Normalmente você quer evitar código que cadeias de duas chamadas de sistema no mesmo arquivo, porque você nunca sabe o que vai acontecer com o arquivo entre suas duas chamadas em seu código.

Um erro típico é escrever algo como :

if os.path.exists(path):
    os.unlink(path)

A segunda chamada (os.unlink) pode falhar se outra coisa excluí-la após o seu caso de teste, levantar uma exceção, e parar o resto de sua função de executar. (Você pode pensar que isso não acontece na vida real, mas nós apenas pescou outro bug assim fora de nossa base de código na semana passada - e foi o tipo de erro que deixou alguns programadores coçar a cabeça e dizendo 'Heisenbug' para o últimos meses)

Assim, no seu caso particular, eu provavelmente faria:

try:
    os.stat(path)
except OSError, e:
    if e.errno == errno.ENOENT:
        print 'path %s does not exist or is a broken symlink' % path
    else:
        raise e

O aborrecimento aqui é que do stat retorna o mesmo código de erro para um link simbólico que só não está lá e um link simbólico quebrado.

Então, acho que você não tem escolha do que quebrar a atomicidade, e fazer algo como

if not os.path.exists(os.readlink(path)):
    print 'path %s is a broken symlink' % path
Respondeu 25/08/2008 em 22:32
fonte usuário

votos
11

os.lstat () pode ser útil. Se lstat () bem-sucedida e stat () falhar, então provavelmente é um link quebrado.

Respondeu 21/08/2008 em 20:15
fonte usuário

votos
8

Isto não é atômica, mas ele funciona.

os.path.islink(filename) and not os.path.exists(filename)

Na verdade por RTFM (ler o manual fantástica), vemos

os.path.exists (caminho)

Return true se o caminho refere-se a um caminho existente. Retorna False para links simbólicos quebrados.

Ele também diz:

Em algumas plataformas, esta função pode retornar False se a permissão não é concedido para executar os.stat () no arquivo solicitado, mesmo se o caminho existe fisicamente.

Então, se você está preocupado com as permissões, você deve adicionar outras cláusulas.

Respondeu 28/06/2015 em 16:49
fonte usuário

votos
3

Posso mencionar testes para hardlinks sem python? / Bin / teste tem a condição FILE1 -ef FILE2 que é verdadeiro quando os arquivos compartilhar um inode.

Portanto, algo como find . -type f -exec test \{} -ef /path/to/file \; -printobras para testes hard link para um arquivo específico.

O que me traz à leitura man teste as menções de -Le -hque ambos trabalham em um arquivo e retornar true se o arquivo é um link simbólico, no entanto, que não lhe diz se o alvo está faltando.

Eu achei que head -0 FILE1iria retornar um código de saída 0se o arquivo pode ser aberto e 1, se não puder, o que no caso de uma ligação simbólica para um arquivo regular funciona como um teste para saber se ele é alvo pode ser lido.

Respondeu 21/08/2008 em 20:13
fonte usuário

votos
2

os.path

Você pode tentar usar realpath () para obter o que os pontos de ligação simbólica para, em seguida, tentando determinar se ele é um arquivo válido usando é arquivo.

(Eu não sou capaz de experimentar isso no momento, então você vai ter que brincar com ele e ver o que você obtém)

Respondeu 21/08/2008 em 20:19
fonte usuário

votos
1

Eu não sou um cara de python, mas parece que os.readlink ()? A lógica que eu usaria em perl é usar readlink () para encontrar o alvo e a estatística de uso () para testar para ver se existe o alvo.

Edit: Eu bati algumas perl que demos readlink. Eu acredito de perl stat e readlink e os.stat de python () e os.readlink () são os dois wrappers para as chamadas de sistema, por isso deve traduzir razoável bem como código de prova de conceito:

wembley 0 /home/jj33/swap > cat p
my $f = shift;

while (my $l = readlink($f)) {
  print "$f -> $l\n";
  $f = $l;
}

if (!-e $f) {
  print "$f doesn't exist\n";
}
wembley 0 /home/jj33/swap > ls -l | grep ^l
lrwxrwxrwx    1 jj33  users          17 Aug 21 14:30 link -> non-existant-file
lrwxrwxrwx    1 root     users          31 Oct 10  2007 mm -> ../systems/mm/20071009-rewrite//
lrwxrwxrwx    1 jj33  users           2 Aug 21 14:34 mmm -> mm/
wembley 0 /home/jj33/swap > perl p mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p mmm
mmm -> mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p link
link -> non-existant-file
non-existant-file doesn't exist
wembley 0 /home/jj33/swap >
Respondeu 21/08/2008 em 20:14
fonte usuário

votos
0

Eu tive um problema semelhante: como pegar links simbólicos quebrados, mesmo quando eles ocorrem em alguns dir pai? Eu também queria log todos eles (em um aplicativo lidar com um número bastante grande de arquivos), mas sem muitas repetições.

Aqui está o que eu vim com, incluindo testes de unidade.

fileutil.py :

import os
from functools import lru_cache
import logging

logger = logging.getLogger(__name__)

@lru_cache(maxsize=2000)
def check_broken_link(filename):
    """
    Check for broken symlinks, either at the file level, or in the
    hierarchy of parent dirs.
    If it finds a broken link, an ERROR message is logged.
    The function is cached, so that the same error messages are not repeated.

    Args:
        filename: file to check

    Returns:
        True if the file (or one of its parents) is a broken symlink.
        False otherwise (i.e. either it exists or not, but no element
        on its path is a broken link).

    """
    if os.path.isfile(filename) or os.path.isdir(filename):
        return False
    if os.path.islink(filename):
        # there is a symlink, but it is dead (pointing nowhere)
        link = os.readlink(filename)
        logger.error('broken symlink: {} -> {}'.format(filename, link))
        return True
    # ok, we have either:
    #   1. a filename that simply doesn't exist (but the containing dir
           does exist), or
    #   2. a broken link in some parent dir
    parent = os.path.dirname(filename)
    if parent == filename:
        # reached root
        return False
    return check_broken_link(parent)

Os testes de unidade:

import logging
import shutil
import tempfile
import os

import unittest
from ..util import fileutil


class TestFile(unittest.TestCase):

    def _mkdir(self, path, create=True):
        d = os.path.join(self.test_dir, path)
        if create:
            os.makedirs(d, exist_ok=True)
        return d

    def _mkfile(self, path, create=True):
        f = os.path.join(self.test_dir, path)
        if create:
            d = os.path.dirname(f)
            os.makedirs(d, exist_ok=True)
            with open(f, mode='w') as fp:
                fp.write('hello')
        return f

    def _mklink(self, target, path):
        f = os.path.join(self.test_dir, path)
        d = os.path.dirname(f)
        os.makedirs(d, exist_ok=True)
        os.symlink(target, f)
        return f

    def setUp(self):
        # reset the lru_cache of check_broken_link
        fileutil.check_broken_link.cache_clear()

        # create a temporary directory for our tests
        self.test_dir = tempfile.mkdtemp()

        # create a small tree of dirs, files, and symlinks
        self._mkfile('a/b/c/foo.txt')
        self._mklink('b', 'a/x')
        self._mklink('b/c/foo.txt', 'a/f')
        self._mklink('../..', 'a/b/c/y')
        self._mklink('not_exist.txt', 'a/b/c/bad_link.txt')
        bad_path = self._mkfile('a/XXX/c/foo.txt', create=False)
        self._mklink(bad_path, 'a/b/c/bad_path.txt')
        self._mklink('not_a_dir', 'a/bad_dir')

    def tearDown(self):
        # Remove the directory after the test
        shutil.rmtree(self.test_dir)

    def catch_check_broken_link(self, expected_errors, expected_result, path):
        filename = self._mkfile(path, create=False)
        with self.assertLogs(level='ERROR') as cm:
            result = fileutil.check_broken_link(filename)
            logging.critical('nothing')  # trick: emit one extra message, so the with assertLogs block doesn't fail
        error_logs = [r for r in cm.records if r.levelname is 'ERROR']
        actual_errors = len(error_logs)
        self.assertEqual(expected_result, result, msg=path)
        self.assertEqual(expected_errors, actual_errors, msg=path)

    def test_check_broken_link_exists(self):
        self.catch_check_broken_link(0, False, 'a/b/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/x/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/f')
        self.catch_check_broken_link(0, False, 'a/b/c/y/b/c/y/b/c/foo.txt')

    def test_check_broken_link_notfound(self):
        self.catch_check_broken_link(0, False, 'a/b/c/not_found.txt')

    def test_check_broken_link_badlink(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_link.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_link.txt')

    def test_check_broken_link_badpath(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_path.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_path.txt')

    def test_check_broken_link_badparent(self):
        self.catch_check_broken_link(1, True, 'a/bad_dir/c/foo.txt')
        self.catch_check_broken_link(0, True, 'a/bad_dir/c/foo.txt')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir/c')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir')

if __name__ == '__main__':
    unittest.main()
Respondeu 27/10/2016 em 01:49
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more