Найти сломанные символические ссылки с Python

Если я вызываю os.stat()сломанный symlink, Python выдает OSErrorисключение. Это делает его полезным для их поиска. Однако есть несколько других причин, которые os.stat()могут вызвать подобное исключение. Есть ли более точный способ обнаружения ошибок в symlinksPython под Linux?

21.08.2008 19:00:52
7 ОТВЕТОВ
РЕШЕНИЕ

Распространенное высказывание на Python гласит: просить прощения легче, чем разрешение. Хотя я не фанат этого утверждения в реальной жизни, оно действительно применяется во многих случаях. Обычно вы хотите избежать кода, который объединяет два системных вызова в одном и том же файле, потому что вы никогда не знаете, что произойдет с файлом между двумя вызовами в вашем коде.

Типичная ошибка - написать что-то вроде :

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

Второй вызов (os.unlink) может завершиться ошибкой, если что-то еще удалило его после теста if, вызовет исключение и остановит выполнение остальной части вашей функции. (Вы можете подумать, что этого не происходит в реальной жизни, но мы просто выловили еще одну ошибку, подобную этой, из нашей базы кода на прошлой неделе - и это была та ошибка, из-за которой несколько программистов почесали голову и заявили «Heisenbug» за последние несколько месяцев)

Итак, в вашем конкретном случае я бы, вероятно, сделал:

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

Раздражает то, что stat возвращает тот же код ошибки для символической ссылки, которой просто нет, и поврежденную символическую ссылку.

Итак, я думаю, у вас нет выбора, кроме как нарушить атомарность и сделать что-то вроде

if not os.path.exists(os.readlink(path)):
    print 'path %s is a broken symlink' % path
26
23.10.2012 13:25:16
readlink также может установить errno == ENOTDIR, если символическая ссылка пропустит файл как dir.
Johan Boulé 20.07.2010 23:41:37
os.readlink (path) может не получить фактический путь, если для ссылки 'path' указан относительный путь к ее цели. Например, если путь связан с «../target», когда вы запускаете сценарий не по пути, где находится ссылка, os.path.exists (os.readlink (path)) вернет false, потому что в пути вашего скрипта, в каталоге верхнего уровня нет файла или папки с именем 'target'. безопасный способ избежать этого - использовать os.path.exists (os.path.realpath (path)).
AplusG 9.05.2014 05:38:22
Даже этого недостаточно. realpath будет интерпретировать путь символической ссылки относительно текущего каталога, в котором выполняется текущий скрипт, тогда как символическая ссылка интерпретируется ОС относительно папки, в которой находится символическая ссылка. Что вам нужно сделать, это сделать что-то вроде этого: link_target = os.readlink (путь) dir = os.path.dirname (путь), если не os.path.isabs (link_target): link_target = os.path.join (dir, link_target), если os.path.exists (link_target): # делайте что хотите с этой плохой символической
Mark Veltzer 4.10.2016 17:40:51
errno.ELOOP также возможна, если ссылка указывает на себя
gnr 6.09.2017 13:37:28

Могу ли я упомянуть тестирование жестких ссылок без Python? / bin / test имеет условие FILE1 -ef FILE2, которое выполняется, когда файлы совместно используют индекс.

Таким образом, что-то подобное find . -type f -exec test \{} -ef /path/to/file \; -printработает для тестирования жестких ссылок на конкретный файл.

Что приводит меня к чтению man testи упоминаниям, -Lи -hкоторые оба работают с одним файлом и возвращают true, если этот файл является символической ссылкой, однако это не говорит вам, если цель отсутствует.

Я обнаружил, head -0 FILE1что возвращал бы код выхода, 0если файл может быть открыт, и 1если он не может, который в случае символической ссылки на обычный файл работает как проверка того, можно ли прочитать его цель.

4
21.08.2008 19:13:46

Я не парень с питоном, но это выглядит как os.readlink ()? Логика, которую я бы использовал в perl, состоит в том, чтобы использовать readlink (), чтобы найти цель, и использовать stat (), чтобы проверить, существует ли цель.

Изменить: я ударил некоторый Perl, который демонстрирует readlink. Я полагаю, что perl stat и readlink, а также python os.stat () и os.readlink () являются обертками для системных вызовов, поэтому это должно быть разумно переведено как подтверждение концепции кода:

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 >
1
21.08.2008 19:56:37

os.lstat () может быть полезным. Если lstat () завершается успешно, а stat () завершается с ошибкой, это, вероятно, неработающая ссылка.

12
2.07.2015 19:23:20

os.path

Вы можете попробовать использовать realpath (), чтобы получить то, на что указывает символическая ссылка, а затем попытаться определить, является ли это действительный файл, использующий файл.

(Я не могу попробовать это в данный момент, так что вам придется поиграть с этим и посмотреть, что вы получите)

2
21.08.2008 19:19:24

Это не атомарно, но работает.

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

Действительно RTFM (читая фантастическое руководство) мы видим

os.path.exists (путь)

Верните True, если путь ссылается на существующий путь. Возвращает False для неработающих символических ссылок.

Это также говорит:

На некоторых платформах эта функция может возвращать False, если не предоставлено разрешение на выполнение os.stat () для запрошенного файла, даже если путь физически существует.

Так что, если вы беспокоитесь о разрешениях, вы должны добавить другие пункты.

11
28.06.2015 16:49:13
+1 за os.path.islink(filename) and not os.path.exists(filename)который мне помог, и за то, что предположил, что F в RTFM - Фантастика.
esmit 2.07.2015 18:38:22

У меня была похожая проблема: как перехватить битые символические ссылки, даже если они встречаются в родительском каталоге? Я также хотел зарегистрировать их все (в приложении, работающем с довольно большим количеством файлов), но без слишком большого количества повторов.

Вот что я придумал, в том числе юнит-тесты.

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)

Модульные тесты:

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()
0
27.10.2016 01:49:11