Question

Si j'appelle os.stat() sur un cassé symlink, python lance un OSError exception.Cela le rend utile pour les trouver.Cependant, il existe quelques autres raisons pour lesquelles os.stat() pourrait lancer une exception similaire.Existe-t-il un moyen plus précis de détecter les bris symlinks avec Python sous Linux ?

Était-ce utile?

La solution

Un dicton courant en Python est qu'il est plus facile de demander pardon que la permission.Même si je ne suis pas fan de cette affirmation dans la vraie vie, elle s'applique dans de nombreux cas.Habituellement, vous souhaitez éviter le code qui enchaîne deux appels système sur le même fichier, car vous ne savez jamais ce qui arrivera au fichier entre vos deux appels dans votre code.

Une erreur typique est d'écrire quelque chose comme:

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

Le deuxième appel (os.unlink) peut échouer si quelque chose d'autre le supprime après votre test if, déclenche une exception et arrête l'exécution du reste de votre fonction.(Vous pensez peut-être que cela n'arrive pas dans la vraie vie, mais nous venons de repêcher un autre bug comme celui-là dans notre base de code la semaine dernière - et c'est le genre de bug qui a laissé quelques programmeurs se gratter la tête et réclamer "Heisenbug" pour le ces derniers mois)

Donc, dans votre cas particulier, je ferais probablement :

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

Le problème ici est que stat renvoie le même code d'erreur pour un lien symbolique qui n'est tout simplement pas là et un lien symbolique rompu.

Donc, je suppose que vous n'avez pas d'autre choix que de briser l'atomicité et de faire quelque chose comme

if not os.path.exists(os.readlink(path)):
    print 'path %s is a broken symlink' % path

Autres conseils

os.lstat() peut être utile.Si lstat() réussit et stat() échoue, il s'agit probablement d'un lien rompu.

Ce n'est pas atomique mais ça marche.

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

En effet par RTFM(en lisant le manuel fantastique) on voit

os.path.exists(chemin)

Renvoie True si path fait référence à un chemin existant.Renvoie False pour les liens symboliques rompus.

Il dit aussi :

Sur certaines plateformes, cette fonction peut renvoyer False si l'autorisation n'est pas accordée pour exécuter os.stat() sur le fichier demandé, même si le chemin existe physiquement.

Donc, si vous êtes préoccupé par les autorisations, vous devez ajouter d'autres clauses.

Puis-je mentionner les tests de liens physiques sans python ?/bin/test a la condition FILE1 -ef FILE2 qui est vraie lorsque les fichiers partagent un inode.

Par conséquent, quelque chose comme find . -type f -exec test \{} -ef /path/to/file \; -print fonctionne pour les tests de liens physiques vers un fichier spécifique.

Ce qui m'amène à lire man test et les mentions de -L et -h qui fonctionnent tous deux sur un fichier et renvoient true si ce fichier est un lien symbolique, mais cela ne vous indique pas si la cible est manquante.

j'ai trouvé ça head -0 FILE1 renverrait un code de sortie de 0 si le fichier peut être ouvert et un 1 si ce n'est pas le cas, ce qui, dans le cas d'un lien symbolique vers un fichier normal, fonctionne comme un test pour savoir si sa cible peut être lue.

os.chemin

Vous pouvez essayer d'utiliser realpath() pour obtenir ce vers quoi pointe le lien symbolique, puis essayer de déterminer s'il s'agit d'un fichier valide en utilisant ce fichier.

(Je ne suis pas en mesure de l'essayer pour le moment, vous devrez donc jouer avec et voir ce que vous obtenez)

Je ne suis pas un gars python mais ça ressemble à os.readlink() ?La logique que j'utiliserais en Perl est d'utiliser readlink() pour trouver la cible et d'utiliser stat() pour tester si la cible existe.

Modifier:J'ai sorti du Perl dont les démos lisaient le lien.Je crois que stat et readlink de Perl et os.stat() et os.readlink() de python sont tous deux des wrappers pour les appels système, donc cela devrait se traduire raisonnablement comme un code de preuve de concept :

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 >

J'avais un problème similaire:comment détecter les liens symboliques rompus, même lorsqu'ils apparaissent dans un répertoire parent ?Je voulais aussi tous les loguer (dans une application traitant un assez grand nombre de fichiers), mais sans trop de répétitions.

Voici ce que j'ai proposé, y compris les tests unitaires.

fichierutil.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)

Tests unitaires :

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top