Frage

Wenn ich anrufe os.stat() auf einem kaputten symlink, Python wirft eine OSError Ausnahme.Dies macht es nützlich, sie zu finden.Es gibt jedoch noch einige andere Gründe dafür os.stat() könnte eine ähnliche Ausnahme auslösen.Gibt es eine genauere Möglichkeit, defekte Geräte zu erkennen? symlinks mit Python unter Linux?

War es hilfreich?

Lösung

Ein weit verbreitetes Python-Sprichwort besagt, dass es einfacher ist, um Vergebung als um Erlaubnis zu bitten.Obwohl ich im wirklichen Leben kein Fan dieser Aussage bin, trifft sie in vielen Fällen zu.Normalerweise möchten Sie Code vermeiden, der zwei Systemaufrufe für dieselbe Datei verkettet, da Sie nie wissen, was mit der Datei zwischen Ihren beiden Aufrufen in Ihrem Code passieren wird.

Ein typischer Fehler besteht darin, so etwas zu schreiben:

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

Der zweite Aufruf (os.unlink) schlägt möglicherweise fehl, wenn er nach Ihrem if-Test von etwas anderem gelöscht wurde, eine Ausnahme auslöst und die Ausführung des Rests Ihrer Funktion stoppt.(Man denkt vielleicht, dass das im wirklichen Leben nicht passiert, aber wir haben letzte Woche gerade einen weiteren Fehler wie diesen aus unserer Codebasis gefischt – und es war die Art von Fehler, die einige Programmierer dazu veranlasste, sich den Kopf zu kratzen und „Heisenbug“ dafür zu behaupten letzten paar Monate)

In Ihrem speziellen Fall würde ich also wahrscheinlich Folgendes tun:

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

Das Ärgerliche hier ist, dass stat den gleichen Fehlercode für einen Symlink, der einfach nicht vorhanden ist, und einen defekten Symlink zurückgibt.

Ich schätze, Sie haben keine andere Wahl, als die Atomizität zu durchbrechen und so etwas zu tun

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

Andere Tipps

os.lstat() kann hilfreich sein.Wenn lstat() erfolgreich ist und stat() fehlschlägt, liegt wahrscheinlich ein defekter Link vor.

Das ist nicht atomar, aber es funktioniert.

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

Tatsächlich von RTFM(liest das fantastische Handbuch) Wir sehen

os.path.exists(Pfad)

Gibt „True“ zurück, wenn der Pfad auf einen vorhandenen Pfad verweist.Gibt „False“ für defekte symbolische Links zurück.

Es heißt auch:

Auf einigen Plattformen gibt diese Funktion möglicherweise „False“ zurück, wenn keine Berechtigung zum Ausführen von os.stat() für die angeforderte Datei erteilt wird, selbst wenn der Pfad physisch vorhanden ist.

Wenn Sie sich also Sorgen um Berechtigungen machen, sollten Sie weitere Klauseln hinzufügen.

Kann ich das Testen auf Hardlinks ohne Python erwähnen?/bin/test hat die Bedingung FILE1 -ef FILE2, die wahr ist, wenn Dateien einen Inode gemeinsam nutzen.

Daher so etwas wie find . -type f -exec test \{} -ef /path/to/file \; -print Funktioniert für Hardlink-Tests zu einer bestimmten Datei.

Was mich zum Lesen bringt man test und die Erwähnungen von -L Und -h Beide funktionieren mit einer Datei und geben true zurück, wenn es sich bei dieser Datei um einen symbolischen Link handelt. Das sagt Ihnen jedoch nicht, ob das Ziel fehlt.

Das habe ich gefunden head -0 FILE1 würde einen Exit-Code von zurückgeben 0 ob die Datei geöffnet werden kann und a 1 Wenn dies nicht möglich ist, dient dies im Fall eines symbolischen Links zu einer regulären Datei als Test dafür, ob das Ziel gelesen werden kann.

os.path

Sie können versuchen, realpath() zu verwenden, um herauszufinden, worauf der Symlink verweist, und dann versuchen, mithilfe von is file festzustellen, ob es sich um eine gültige Datei handelt.

(Ich bin im Moment nicht in der Lage, das auszuprobieren, Sie müssen also ein wenig damit herumspielen und sehen, was Sie bekommen.)

Ich bin kein Python-Typ, aber es sieht aus wie os.readlink()?Die Logik, die ich in Perl verwenden würde, besteht darin, readlink() zu verwenden, um das Ziel zu finden, und stat() zu verwenden, um zu testen, ob das Ziel existiert.

Bearbeiten:Ich habe ein paar Perlen rausgeholt, die den Demo-Readlink haben.Ich glaube, dass stat und readlink von Perl und os.stat() und os.readlink() von Python beide Wrapper für die Systemaufrufe sind, daher sollte sich dies als Proof-of-Concept-Code gut übersetzen lassen:

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 >

Ich hatte ein ähnliches Problem:Wie kann ich defekte Symlinks erkennen, auch wenn sie in einem übergeordneten Verzeichnis auftreten?Ich wollte auch alle protokollieren (in einer Anwendung, die eine ziemlich große Anzahl von Dateien verarbeitet), aber ohne zu viele Wiederholungen.

Hier ist, was ich mir ausgedacht habe, einschließlich Unit-Tests.

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)

Unit-Tests:

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()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top