29

os.stat()壊れたを呼び出すとsymlink、python がOSError例外をスローします。これは、それらを見つけるのに役立ちます。os.stat()ただし、同様の例外をスローする可能性のある他の理由がいくつかあります。symlinksLinux で Python の破損を検出するより正確な方法はありますか?

4

8 に答える 8

32

よくある Python の格言は、許可よりも許しを求める方が簡単だというものです。私は実生活でこの声明のファンではありませんが、多くの場合に当てはまります. 通常、コード内の 2 つのシステム コールの間でファイルに何が起こるかわからないため、同じファイルに対して 2 つのシステム コールをチェーンするコードは避けたいと考えます。

典型的な間違いは、次のように書くことです:

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

2 番目の呼び出し (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
于 2008-08-25T21:32:20.550 に答える
12

os.lstat()が役立つ場合があります。lstat() が成功し、stat() が失敗した場合、リンクが壊れている可能性があります。

于 2008-08-21T19:15:33.627 に答える
4

Python を使用しないハードリンクのテストについて言及できますか? /bin/test には、ファイルが i ノードを共有する場合に真となる FILE1 -ef FILE2 条件があります。

find . -type f -exec test \{} -ef /path/to/file \; -printしたがって、特定のファイルへのハード リンク テストでは次のようなことが機能します。

どちらも1つのファイルで機能し、そのファイルがシンボリックリンクの場合はtrueman testを返しますが、ターゲットが見つからないかどうかはわかりません。-L-h

通常のファイルへのシンボリックリンクの場合、ターゲットが読み取れるかどうかのテストとして機能しますhead -0 FILE101

于 2008-08-21T19:13:46.390 に答える
2

os.path

realpath() を使用してシンボリックリンクが指すものを取得してから、 is file を使用して有効なファイルかどうかを判断しようとする場合があります。

(私は現時点でそれを試すことができないので、いろいろ試してみて何が得られるか見てみる必要があります)

于 2008-08-21T19:19:24.537 に答える
1

私は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 >
于 2008-08-21T19:14:01.367 に答える
0

私は同様の問題を抱えていました:親ディレクトリで発生した場合でも、壊れたシンボリックリンクをキャッチする方法は? また、それらすべてを (かなり多数のファイルを扱うアプリケーションで) ログに記録したいと考えていましたが、繰り返しが多すぎないようにしました。

これが、単体テストを含む、私が思いついたものです。

ファイル util.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()
于 2016-10-27T01:49:11.007 に答える