如果我调用os.stat()
一个 broken symlink
,python 会抛出一个OSError
异常。这对于找到它们很有用。但是,还有一些其他原因os.stat()
可能会引发类似的异常。有没有更精确的方法symlinks
在 Linux 下用 Python 检测损坏?
8 回答
一个常见的 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
这不是原子的,但它有效。
os.path.islink(filename) and not os.path.exists(filename)
事实上,通过RTFM (阅读精彩的手册)我们看到
os.path.exists(路径)
如果 path 引用现有路径,则返回 True。对于损坏的符号链接返回 False。
它还说:
在某些平台上,如果未授予对请求的文件执行 os.stat() 的权限,即使路径物理存在,此函数也可能返回 False。
因此,如果您担心权限,则应添加其他子句。
os.lstat()可能会有所帮助。如果 lstat() 成功而 stat() 失败,那么它可能是一个断开的链接。
我可以提到没有 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
如果它不能打开,在指向常规文件的符号链接的情况下,它可以作为目标是否可以读取的测试。
我使用了这个变体,当符号链接被破坏时,它将为 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"
我不是 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 >
我有一个类似的问题:如何捕获损坏的符号链接,即使它们出现在某些父目录中?我还想记录所有这些(在处理大量文件的应用程序中),但没有太多重复。
这是我想出的,包括单元测试。
文件实用程序.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()