2008-08-21 7 views
16

symlinkos.stat()을 호출하면 파이썬은 OSError 예외를 발생시킵니다. 이것은 그것을 찾는 데 유용합니다. 그러나 os.stat()에서 비슷한 예외가 발생할 수있는 몇 가지 이유가 있습니다. 리눅스에서 파이썬으로 깨진 symlinks을 탐지하는 더 정확한 방법이 있습니까?파이썬과의 잘못된 심볼릭 링크 찾기

답변

20

일반적인 파이썬 속담이보다 용서를 물어 쉽게이다 허가. 실생활에서 나는이 진술의 팬이 아니지만, 많은 경우에 적용됩니다. 일반적으로 코드에서 두 번의 호출 사이에 파일에 어떤 일이 생길지 모르기 때문에 같은 파일에서 두 개의 시스템 호출을 연결하는 코드를 피하려고합니다. 뭔가 다른 사용자의 경우 테스트 후를 삭제 한 경우, 실패 예외를 발생하고, 나머지를 중지 할 수 있습니다

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

두 번째 호출 (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 

는 여기 성가심 그 합계 단지가 아닌 심볼릭 링크와 깨진 동일한 오류 코드를 반환이다 symlink.

그래서, 난 당신이 자성을 중단하는 것보다 선택의 여지가 추측하고

if not os.path.exists(os.readlink(path)): 
    print 'path %s is a broken symlink' % path 
+1

readlink를도 == ENOTDIR errno를 설정할 수있다. –

+3

os.readlink (path)는 'path'링크에 target에 대한 상대 경로가 주어지면 실제 경로를 얻지 못할 수도 있습니다. 예를 들어 path가 '../target'에 링크되어있는 경우 링크가없는 경로에서 스크립트를 실행하면 os.path.exists (os.readlink (path))는 경로에서 false를 반환합니다. 스크립트의 상위 디렉토리에는 'target'이라는 파일이나 폴더가 없습니다. 이것을 피하는 안전한 방법은 os.path.exists (os.path.realpath (path))를 사용하는 것입니다. – AplusG

+0

이렇게해도 충분하지 않습니다. realpath는 현재 실행중인 스크립트의 현재 실행 디렉토리와 관련하여 symlink의 경로를 해석하지만 심볼 링크는 심볼 링크가있는 폴더에 상대적으로 운영 체제에 의해 해석됩니다. 당신이해야 할 일은 다음과 같이하십시오. link_target = os.readlink (경로) dir = os.path.dirname (경로) os.path가 아닌 경우.isabs (link_target) : link_target = os.path.join (DIR, link_target) 경우 os.path.exists (link_target) : #은 당신이이 나쁜 심볼릭 링크 –

3

파이썬없이 하드 링크 테스트를 언급 할 수 있습니까?/bin/test에는 파일이 inode를 공유 할 때 true 인 FILE1 -ef FILE2 조건이 있습니다.

따라서 find . -type f -exec test \{} -ef /path/to/file \; -print과 같은 것은 특정 파일에 대한 하드 링크 테스트에 사용할 수 있습니다. man test을 읽는 날을 제공하고, 해당 파일이 심볼릭 링크 인 경우 하나 개의 파일에 모두 작업과 대상이없는 경우를 알려하지 않는 그러나, true를 반환하는 -L-h의 언급

. 일반 파일에 대한 심볼릭 링크의 경우는 타겟팅 할 수 있는지 여부에 대한 테스트로 작동

내가 파일을 열 수있는 경우 head -0 FILE10의 종료 코드를 반환 찾을 않았다 및 1이 할 수없는 경우, 읽을 수 있습니다.

1

저는 파이썬 녀석이 아니지만 os.readlink()처럼 보입니까? 내가 펄에서 사용할 논리는 readlink()를 사용하여 타겟을 찾고 stat()를 사용하여 타겟이 존재하는지 테스트하는 것이다.

편집 : 필자는 readlink 데모 링크가있는 일부 펄을 썼습니다. 나는 펄의 합계와 readlink를하고 파이썬의 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

임을 가리킨다. – 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()