使用Python查找损坏的符号链接

时间:2008-08-21 19:00:52

标签: python linux symlink

如果我在损坏的os.stat()上调用symlink,则python会抛出OSError异常。这使它有助于找到它们。但是,os.stat()可能会引发类似的异常,还有其他一些原因。有没有更精确的方法在Linux下用Python检测损坏的symlinks

8 个答案:

答案 0 :(得分:22)

一个常见的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

答案 1 :(得分:12)

os.lstat()可能会有所帮助。如果lstat()成功并且stat()失败,那么它可能是一个断开的链接。

答案 2 :(得分:10)

这不是原子的,但它有效。

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

确实是RTFM  (阅读精彩的手册)我们看到了

  

os.path.exists(路径)

     

如果path指的是现有路径,则返回True。对于损坏的符号链接,返回False。

它还说:

  

在某些平台上,如果未授予对所请求文件执行os.stat()的权限,则此函数可能返回False,即使路径实际存在。

因此,如果您担心权限,则应添加其他子句。

答案 3 :(得分:4)

我可以提一下没有python的硬链接测试吗? / bin / test具有FILE1 -ef FILE2条件,当文件共享inode时为true。

因此,像find . -type f -exec test \{} -ef /path/to/file \; -print这样的东西适用于对特定文件的硬链接测试。

这使我开始阅读man test以及-L-h的提及,它们都在一个文件上工作,如果该文件是符号链接则返回true,但是不是告诉你目标是否缺失。

我确实发现如果文件可以打开,head -0 FILE1会返回0的退出代码,如果不能打开则返回1,如果是符号链接,则{常规文件用于测试是否可以读取目标。

答案 4 :(得分:2)

os.path

您可以尝试使用realpath()来获取符号链接指向的内容,然后尝试使用is file确定它是否是有效文件。

(我现在无法尝试,所以你必须玩弄它,看看你得到了什么)

答案 5 :(得分:2)

我使用了这个变体,当symlink断开时,path.exists将返回false,path.islink将返回true,因此结合这两个事实,我们可以使用以下内容:

def kek(argum):
    if path.exists("/root/" + argum) == False and path.islink("/root/" + argum) == True:
        print("The path is a broken link, location: " + os.readlink("/root/" + argum))
    else:
        return "No broken links fond"

答案 6 :(得分:1)

我不是蟒蛇人,但它看起来像os.readlink()?我在perl中使用的逻辑是使用readlink()来查找目标,并使用stat()来测试目标是否存在。

编辑:我敲了一些演示readlink的perl。我相信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 >

答案 7 :(得分:0)

我有一个类似的问题:如何捕获破坏的符号链接,即使它们出现在某个父目录中?我还想记录所有这些(在处理相当多文件的应用程序中),但没有太多重复。

这是我提出的,包括单元测试。

<强> 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()