2008-08-21 13 views
16

os.stat()を壊れたsymlinkに電話すると、pythonはOSError例外をスローします。これはそれを見つけるのに役立ちます。しかし、os.stat()が同様の例外をスローする可能性がある他の理由がいくつかあります。 LinuxでPythonを使って壊れたsymlinksを検出するより正確な方法はありますか?Pythonで壊れているシンボリックリンクを見つけよう

答えて

20

共通Pythonのことわざは、それがより許しを依頼することが容易であるということです許可。私は実生活でこの声明のファンではありませんが、それは多くの場合に当てはまります。通常は、コード内の2つの呼び出しの間にファイルに何が起こるかを決して知らないので、同じファイルに対して2つのシステム呼び出しを連結するコードを避けたいとします。何かがあなたの場合は、テスト後にそれを削除した場合、失敗する例外を発生させ、残りを停止することがあります

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

2回目の呼び出し(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 
+1

READLINKもerrnoに== ENOTDIRを設定することができます。 –

+3

リンク 'path'にターゲットへの相対パスが指定されている場合、os.readlink(path)は実際のパスを取得しない可能性があります。たとえば、pathが '../target'にリンクされている場合、リンクが存在するパスにないスクリプトを実行すると、os.path.exists(os.readlink(path))はパスにfalseが返されますその上位ディレクトリには 'target'という名前のファイルやフォルダはありません。これを回避する安全な方法は、os.path.exists(os.path.realpath(path))を使用することです。 – AplusG

+0

これでさえ十分ではありません。 realpathは、現在実行中のスクリプトの現在の実行ディレクトリに対するシンボリックリンクのパスを解釈しますが、シンボリックリンクはシンボリックリンクがあるフォルダに対するOSによって解釈されます。あなたがする必要があるのは、次のようなことです: link_target = os.readlink(パス) dir = os.path.dirname(パス) os.pathでない場合は、次のようにします。あなたがこの悪いシンボリックリンクで好きなことをしてください –

3

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

したがって、find . -type f -exec test \{} -ef /path/to/file \; -printのようなものは、特定のファイルへのハードリンクテストに使用できます。 man testを読みに私をもたらし、そのファイルがシンボリックリンクの場合は、1つのファイルの両方の仕事とは、ターゲットが欠落しているかどうかを教えてくれないことが、trueを返した-L-hの言及

。通常のファイルへのシンボリックリンクの場合には、それが標的にすることができるのかどうかのためのテストとして働く

私は、ファイルを開くことができる場合head -0 FILE10の終了コードを返すことがわかりましたと1それができない場合は、読まれる。

1

私はpythonの人ではありませんが、os.readlink()のように見えますか?私がperlで使うロジックは、readlink()を使ってターゲットを探し、stat()を使ってターゲットが存在するかどうかを調べることです。

編集:私はいくつかのperlを試してreadlinkをデモしました。私は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 > 
11

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

2

os.path

あなたがへのシンボリックリンクのポイントは、それが使用して有効なファイルかどうかを決定しようとしているものを手に入れるのrealpath()を使用してみてはファイルです。

(あなたがそれで遊んで、あなたが得るかを確認する必要がありますので、私は、現時点ではそれを試してみることができないんだけど)

8

これはアトミックではありませんが、それが動作するような何かを行います。 RTFM (幻想マニュアルを読む)によって、実際

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

パスは、既存のパスを参照している場合、我々は真

os.path.exists(パス)

リターンを参照してください。壊れたシンボリックリンクに対してFalseを返します。

また、言う:許可がパスが物理的に存在する場合でも、要求されたファイルに()はos.statを実行するために付与されていない場合、一部のプラットフォームで

は、この関数はFalseを返す場合があります。

権限が不安な場合は、他の句を追加する必要があります。

+0

+1を指しています。 – esmit

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) 

ユニットテスト:シンボリックリンクがディレクトリのファイルをmissuses場合

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() 
関連する問題