【问题标题】:Find broken symlinks with Python使用 Python 查找损坏的符号链接
【发布时间】:2008-08-21 19:00:52
【问题描述】:

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

【问题讨论】:

    标签: python linux symlink


    【解决方案1】:

    这不是原子的,但它可以工作。

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

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

    os.path.exists(路径)

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

    它还说:

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

    所以如果你担心权限,你应该添加其他子句。

    【讨论】:

    • +1 for os.path.islink(filename) and not os.path.exists(filename) 这对我有帮助,并表示 RTFM 中的 F 非常棒。
    【解决方案2】:

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

    一个典型的错误是这样写

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

    如果在你的 if 测试之后有其他东西删除了第二个调用 (os.unlink),它可能会失败,引发一个异常,并停止你的函数的其余部分执行。 (您可能认为这在现实生活中不会发生,但上周我们刚刚从我们的代码库中发现了另一个类似的错误 - 正是这种错误让一些程序员摸不着头脑并声称为“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
    

    【讨论】:

    • 如果符号链接误用文件作为目录,readlink 也可能设置 errno == ENOTDIR。
    • os.readlink(path) 如果链接 'path' 被赋予到其目标的相对路径,则可能无法获得实际路径。例如,如果路径链接到 '../target',当您运行脚本时不在链接所在的路径中,os.path.exists(os.readlink(path)) 将返回 false,因为在路径中在你的脚本中,它的上层目录没有名为“目标”的文件或文件夹。避免这种情况的安全方法是使用 os.path.exists(os.path.realpath(path))。
    • 即使这样也不够好。 realpath 将解释符号链接相对于当前运行脚本的当前运行目录的路径,而符号链接由操作系统相对于符号链接所在的文件夹进行解释。你需要做的是这样的事情: link_target=os.readlink(path) dir=os.path.dirname(path) if not os.path.isabs(link_target): link_target=os.path.join(dir, link_target) if os.path.exists(link_target): # 用这个坏的符号链接做你喜欢的事
    • 如果链接指向自身,errno.ELOOP 也是一种可能性
    【解决方案3】:

    我使用了这个变体,当符号链接被破坏时,它会返回 false 为 path.exists 和 true 为 path.islink,所以结合这两个事实我们可以使用以下:

    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"
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我可以提到没有 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,这在指向常规文件的符号链接的情况下可用作测试是否可以读取它的目标。

        【讨论】:

          【解决方案6】:

          os.path

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

          (我目前无法尝试,所以你必须尝试一下,看看你会得到什么)

          【讨论】:

            【解决方案7】:

            我不是 python 人,但它看起来像 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 >
            

            【讨论】:

              【解决方案8】:

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

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

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

              【讨论】:

                猜你喜欢
                • 2015-07-21
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-12-14
                • 1970-01-01
                • 2016-04-19
                • 2018-11-04
                • 2011-08-26
                相关资源
                最近更新 更多