代码之家  ›  专栏  ›  技术社区  ›  postfuturist

用python查找断开的符号链接

  •  20
  • postfuturist  · 技术社区  · 17 年前

    如果我打电话 os.stat() 破碎的 symlink ,python抛出一个 OSError 例外。这对找到它们很有用。但是,还有其他一些原因 状态() 可能会引发类似的异常。有更精确的方法来检测破损吗 symlinks 使用Linux下的python?

    7 回复  |  直到 8 年前
        1
  •  22
  •   slayton    12 年前

    巨蟒常说,请求宽恕比允许更容易。虽然我在现实生活中并不喜欢这种说法,但它在很多情况下都适用。通常,您希望避免在同一个文件上链接两个系统调用的代码,因为您永远不知道在您的代码中的两个调用之间文件会发生什么。

    典型的错误是写 :

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

    第二个调用(os.unlink)可能会失败,如果在if测试后有其他东西删除了它,引发异常,并停止执行函数的其余部分。(你可能认为这在现实生活中不会发生,但上周我们从代码库中发现了另一个这样的bug——正是这种bug让一些程序员抓耳挠腮,在过去的几个月里声称是“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为一个不存在的symlink和一个断开的symlink返回相同的错误代码。

    所以,我想你别无选择,只能打破原子性,做一些类似的事情

    if not os.path.exists(os.readlink(path)):
        print 'path %s is a broken symlink' % path
    
        2
  •  12
  •   esmit    10 年前

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

        3
  •  10
  •   am70    10 年前

    这不是原子的,但它起作用。

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

    确实由 RTFM (阅读神奇手册)我们看到了

    os.path.exists(路径)

    如果路径引用现有路径,则返回true。对于断开的符号链接返回false。

    它还说:

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

    因此,如果您担心权限问题,那么应该添加其他子句。

        4
  •  4
  •   dlamblin    17 年前

    我能提到没有python的硬链接测试吗?/bin/test的file1-ef file2条件在文件共享inode时为真。

    因此,有点像 find . -type f -exec test \{} -ef /path/to/file \; -print 用于对特定文件进行硬链接测试。

    这让我开始阅读 man test 提到 -L -h 它在一个文件上工作,如果该文件是一个符号链接,则返回true,但这不会告诉您目标是否丢失。

    我确实发现了 head -0 FILE1 将返回退出代码 0 如果文件可以打开并且 1 如果它不能,在一个指向常规文件的符号链接的情况下,它将作为一个测试是否可以读取它的目标。

        5
  •  2
  •   Jason Baker    17 年前

    os.path

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

    (我现在还不能试一下,所以你得到处玩,看看能得到什么。)

        6
  •  1
  •   jj33    17 年前

    我不是巨蟒,但看起来像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
  •   Pierre D    8 年前

    我有一个类似的问题:如何捕捉断开的符号链接,即使它们出现在某个父目录中?我还希望记录所有这些文件(在处理大量文件的应用程序中),但不要重复太多。

    这是我想到的,包括单元测试。

    软疣 :

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