tl; dr
is_path_exists_or_creatable()
아래 정의 된 함수를 호출하십시오 .
Strictly Python 3. 이것이 바로 우리가하는 방식입니다.
두 가지 질문에 대한 이야기
"경로 이름의 유효성과 유효한 경로 이름의 경우 해당 경로의 존재 또는 쓰기 가능성을 어떻게 테스트합니까?" 분명히 두 가지 질문입니다. 둘 다 잘, 흥미, 및도 여기에 진정으로 만족스러운 답변을받은 ... 또는 어디서나 내가 grep을 할 수있다.
vikki 의 대답은 아마도 가장 가깝지만 다음과 같은 현저한 단점이 있습니다.
- 불필요 하게 파일 핸들 을 열고 ( ... 그런 다음 안정적으로 닫지 못함 ).
- 불필요하게 0 바이트 파일 쓰기 ( ... 그런 다음 신뢰할 수있는 닫기 또는 삭제 실패 ).
- 무시할 수없는 잘못된 경로 이름과 무시할 수있는 파일 시스템 문제를 구분하는 OS 관련 오류를 무시합니다. 당연히 이것은 Windows에서 중요합니다. ( 아래 참조. )
- 테스트 할 경로 이름의 상위 디렉토리를 동시에 (제거) 이동하는 외부 프로세스로 인한 경쟁 조건을 무시합니다. ( 아래 참조. )
- 부실, 느리거나 일시적으로 액세스 할 수없는 파일 시스템에있는이 경로 이름으로 인한 연결 시간 초과를 무시합니다. 이는 공공 서비스를 잠재적 인 DoS 기반 공격에 노출시킬 수 있습니다. ( 아래 참조. )
우리는 모든 것을 고칠 것입니다.
질문 # 0 : 경로 이름 유효성이 다시 무엇입니까?
우리의 연약한 고기 옷을 비단뱀이 가득한 고통의 모 쉬핏에 던지기 전에 우리는 아마도 "경로 이름 유효성"이라는 의미를 정의해야 할 것입니다. 타당성을 정확히 정의하는 것은 무엇입니까?
"경로 이름 유효성"이란 현재 시스템 의 루트 파일 시스템 에 대한 경로 이름 의 구문 적 정확성 을 의미 합니다. 해당 경로 또는 부모 디렉토리가 물리적으로 존재하는지 여부에 관계없이. 루트 파일 시스템의 모든 구문 요구 사항을 준수하는 경우 경로 이름은이 정의에서 구문 적으로 정확합니다.
"루트 파일 시스템"은 다음을 의미합니다.
- POSIX 호환 시스템에서 파일 시스템은 루트 디렉토리 (
/
)에 마운트됩니다 .
- Windows에서 파일 시스템
%HOMEDRIVE%
은 현재 Windows 설치를 포함하는 콜론 접미어가 붙은 드라이브 문자 인에 마운트됩니다 (일반적으로 반드시 그런 것은 아님 C:
).
차례로 "구문 정확성"의 의미는 루트 파일 시스템의 유형에 따라 다릅니다. 의 경우 ext4
(대부분 만 하지 모든 POSIX 호환) 파일 시스템, 경로 이름 구문이 올바른지 여부와 해당 경로의 경우 :
- null 바이트를 포함하지 않습니다 (예 :
\x00
Python). 이것은 모든 POSIX 호환 파일 시스템에 대한 어려운 요구 사항입니다.
- 255 바이트보다 긴 경로 구성 요소를 포함하지 않습니다 (예 :
'a'*256
Python). 경로 성분이 전혀 포함하지 않는 경로의 최장 부분 문자열 /
문자 (예를 들어, bergtatt
, ind
, i
, 및 fjeldkamrene
패스 명에 /bergtatt/ind/i/fjeldkamrene
).
구문 정확성. 루트 파일 시스템. 그게 다야.
질문 # 1 : 이제 경로 이름 유효성을 어떻게 수행해야합니까?
Python에서 경로 이름의 유효성을 검사하는 것은 놀랍도록 직관적이지 않습니다. 나는 여기서 Fake Name 과 확고하게 동의 합니다. 공식 os.path
패키지는 이에 대한 즉시 사용 가능한 솔루션을 제공해야합니다. 알 수없는 (아마도 설득력이없는) 이유 때문에 그렇지 않습니다. 다행스럽게도 자신의 임시 솔루션을 펼치는 것은 굉장한 일 이 아닙니다 ...
네, 사실입니다. 털이 많아요. 끔찍하다. 그것은 아마 졸졸 울리고 빛날 때 낄낄 거린다. 하지만 어떻게 할 건가요? Nuthin '.
우리는 곧 저수준 코드의 방사능 심연으로 내려갈 것입니다. 하지만 먼저 높은 수준의 가게에 대해 이야기합시다. 표준 os.stat()
및 os.lstat()
함수는 잘못된 경로 이름을 전달할 때 다음 예외를 발생시킵니다.
- 존재하지 않는 디렉토리에있는 경로 이름의 경우
FileNotFoundError
.
- 기존 디렉토리에있는 경로 이름의 경우 :
- Windows에서 속성이 (예 :)
WindowsError
인 인스턴스 .winerror
123
ERROR_INVALID_NAME
- 다른 모든 OS에서 :
- null 바이트 (예 :)를 포함하는 경로 이름
'\x00'
의 경우 TypeError
.
- 255 바이트보다 긴 경로 구성 요소를 포함하는 경로 이름
OSError
의 경우 해당 errcode
속성의 인스턴스 는 다음과 같습니다.
- SunOS 및 * BSD OS 제품군에서
errno.ERANGE
. (이는 OS 수준의 버그로 보이며 그렇지 않으면 POSIX 표준의 "선택적 해석"이라고합니다.)
- 다른 모든 OS에서
errno.ENAMETOOLONG
.
결정적으로 이것은 기존 디렉토리에있는 경로 이름 만 유효 함을 의미합니다 . os.stat()
및 os.lstat()
기능은 일반적인 인상 FileNotFoundError
전달 경로 이름이 존재하지 않는 디렉토리에 거주하는 경우 그 패스가 무효인지 여부에 관계없이, 예외를. 디렉토리 존재는 경로 이름 무효보다 우선합니다.
이것은 존재 하지 않는 디렉토리에있는 경로 이름이 유효 하지 않음을 의미합니까 ? 예 – 기존 디렉토리에 상주하도록 해당 경로 이름을 수정하지 않는 한. 그러나 그것이 안전하게 가능합니까? 경로 이름을 수정하면 원래 경로 이름의 유효성을 검사 할 수 없습니까?
이 질문에 답하려면 ext4
파일 시스템의 구문 상 올바른 경로 이름에 널 바이트를 포함하는 경로 구성 요소 (A) 또는 길이가 255 바이트를 초과하는 (B) 가 없다는 점 을 상기 해보십시오 . 따라서 ext4
경로 이름은 해당 경로 이름의 모든 경로 구성 요소가 유효한 경우에만 유효합니다. 이것은 대부분의 실제 관심 파일 시스템 에 해당됩니다.
그 현학적 인 통찰력이 실제로 우리에게 도움이됩니까? 예. 한 번에 전체 경로 이름을 확인하는 더 큰 문제를 해당 경로 이름의 모든 경로 구성 요소 만 확인하는 작은 문제로 줄입니다. 임의의 경로 이름은 다음 알고리즘에 따라 크로스 플랫폼 방식으로 유효합니다 (경로 이름이 기존 디렉토리에 있는지 여부에 관계없이).
- 해당 경로 이름을 경로 구성 요소로 분할합니다 (예 : 경로 이름
/troldskog/faren/vild
을 목록으로 ['', 'troldskog', 'faren', 'vild']
).
- 이러한 각 구성 요소에 대해 :
- 해당 구성 요소와 함께 존재하는 디렉토리의 경로 이름을 새 임시 경로 이름 (예 :)에 결합
/troldskog
합니다.
- 해당 경로 이름을
os.stat()
또는에 전달하십시오 os.lstat()
. 해당 경로 이름과 해당 구성 요소가 유효하지 않은 경우이 호출은 일반 FileNotFoundError
예외가 아닌 무효 유형을 노출하는 예외를 발생시킵니다 . 왜? 해당 경로 이름이 기존 디렉토리에 있기 때문입니다. (순환 논리는 순환입니다.)
존재가 보장되는 디렉토리가 있습니까? 예, 그러나 일반적으로 단 하나 : 루트 파일 시스템의 최상위 디렉토리 (위에 정의 된대로).
에 (따라서 존재 보장되지과) 다른 디렉토리에있는 경로명 전달 os.stat()
또는 os.lstat()
디렉토리가 이전에 존재하는 테스트 된 경우에도 초대의 경쟁 조건을. 왜? 테스트가 수행 된 후 경로 이름이 또는에 전달 되기 전에 외부 프로세스가 해당 디렉토리를 동시에 제거하는 것을 방지 할 수 없기 때문 입니다. 마음을 사로 잡는 광기의 개들을 풀어주세요!os.stat()
os.lstat()
위의 접근 방식에는 보안 이라는 실질적인 부수적 이점도 있습니다. (아닌가 하는 좋은?) 구체적으로 :
이러한 경로 이름을 단순히 DoS (서비스 거부) 공격 및 기타 블랙 햇 허니 건에 전달 os.stat()
하거나 이에 os.lstat()
취약하여 신뢰할 수없는 소스에서 임의의 경로 이름을 검증하는 전면 응용 프로그램 입니다. 악의적 인 사용자는 오래되었거나 느린 것으로 알려진 파일 시스템 (예 : NFS Samba 공유)에있는 경로 이름을 반복적으로 확인하려고 시도 할 수 있습니다. 이 경우 들어오는 경로 이름을 맹목적으로 스테이 팅하면 결국 연결 시간 초과로 실패하거나 실업을 견딜 수있는 미약 한 능력보다 더 많은 시간과 자원을 소비 할 수 있습니다.
위의 접근 방식은 루트 파일 시스템의 루트 디렉토리에 대해 경로 이름의 경로 구성 요소를 확인함으로써이를 방지합니다. (심지어 경우 의가 있다고 는 오래된, 느린, 또는 액세스, 당신은 경로 검증보다 큰 문제를 가지고있다.)
잃어버린? 큰. 의 시작하자. (Python 3이 가정합니다. "What Is Fragile Hope for 300, leycec ?"참조)
import errno, os
ERROR_INVALID_NAME = 123
'''
Windows-specific error code indicating an invalid pathname.
See Also
----------
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
Official listing of all such codes.
'''
def is_pathname_valid(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS;
`False` otherwise.
'''
try:
if not isinstance(pathname, str) or not pathname:
return False
_, pathname = os.path.splitdrive(pathname)
root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
if sys.platform == 'win32' else os.path.sep
assert os.path.isdir(root_dirname)
root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep
for pathname_part in pathname.split(os.path.sep):
try:
os.lstat(root_dirname + pathname_part)
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == ERROR_INVALID_NAME:
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
except TypeError as exc:
return False
else:
return True
끝난. 그 코드에 눈을 찡 그리지 마십시오. ( 물린다. )
질문 # 2 : 경로 이름의 존재 또는 창의성이 유효하지 않을 수 있습니다.
위의 솔루션을 고려할 때 유효하지 않은 경로 이름의 존재 또는 생성 가능성을 테스트하는 것은 대부분 사소한 일입니다. 여기서 작은 열쇠 는 전달 된 경로 를 테스트 하기 전에 이전에 정의 된 함수를 호출하는 것입니다 .
def is_path_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create the passed
pathname; `False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
return os.access(dirname, os.W_OK)
def is_path_exists_or_creatable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS _and_
either currently exists or is hypothetically creatable; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_creatable(pathname))
except OSError:
return False
완료 및 완료. 정답이 아닙니다.
질문 # 3 : Windows에서 유효하지 않은 경로 이름 존재 또는 쓰기 가능성
주의 사항이 있습니다. 물론 있습니다.
공식 os.access()
문서에 따르면 다음과 같습니다.
참고 : I / O 작업 os.access()
은 성공할 것이라고 표시 되더라도 실패 할 수 있습니다. 특히 일반적인 POSIX 권한 비트 모델을 벗어난 권한 의미를 가질 수있는 네트워크 파일 시스템의 작업의 경우 더욱 그렇습니다.
놀랍게도 Windows는 여기에서 일반적인 용의자입니다. NTFS 파일 시스템에서 ACL (액세스 제어 목록)을 광범위하게 사용했기 때문에 단순한 POSIX 권한 비트 모델은 기본 Windows 현실에 제대로 매핑되지 않습니다. 이것은 (논란의 여지가 있지만) Python의 잘못은 아니지만 Windows 호환 응용 프로그램에 대해서는 우려 할 수 있습니다.
이것이 당신이라면 더 강력한 대안이 필요합니다. 전달 된 경로가 존재 하지 않는 경우 대신 해당 경로의 상위 디렉토리에서 즉시 삭제되도록 보장되는 임시 파일을 생성하려고 시도합니다. 이는보다 이식성이 높은 (비용이 많이 드는 경우) 생성 가능성 테스트입니다.
import os, tempfile
def is_path_sibling_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create **siblings**
(i.e., arbitrary files in the parent directory) of the passed pathname;
`False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
try:
with tempfile.TemporaryFile(dir=dirname): pass
return True
except EnvironmentError:
return False
def is_path_exists_or_creatable_portable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname on the current OS _and_
either currently exists or is hypothetically creatable in a cross-platform
manner optimized for POSIX-unfriendly filesystems; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_sibling_creatable(pathname))
except OSError:
return False
그러나 이것 만으로는 충분하지 않을 수 있습니다.
UAC (사용자 액세스 제어) 덕분에 항상 최소화 할 수있는 Windows Vista와 그 이후의 모든 반복 은 시스템 디렉터리와 관련된 권한 에 대해 노골적으로 거짓말 을합니다. 관리자가 아닌 사용자가 표준 C:\Windows
또는 C:\Windows\system32
디렉토리에 파일을 만들려고 시도하면 UAC는 사용자가 실제로 그렇게 할 수 있도록 허용하고 실제로 생성 된 모든 파일을 해당 사용자 프로필의 "가상 저장소"로 격리합니다. (기만하는 사용자가 장기적으로 해로운 결과를 초래할 것이라고 누가 상상할 수 있었을까요?)
미친 짓이야. 이것은 Windows입니다.
증명
감히? 위의 테스트를 테스트 할 시간입니다.
NULL은 UNIX 기반 파일 시스템의 경로 이름에서 금지 된 유일한 문자이므로이를 활용하여 차갑고 엄격한 진실을 보여 줍시다. 솔직히 저를 괴롭 히고 분노 케하는 무시할 수없는 Windows 헛소리를 무시합니다.
>>> print('"foo.bar" valid? ' + str(is_pathname_valid('foo.bar')))
"foo.bar" valid? True
>>> print('Null byte valid? ' + str(is_pathname_valid('\x00')))
Null byte valid? False
>>> print('Long path valid? ' + str(is_pathname_valid('a' * 256)))
Long path valid? False
>>> print('"/dev" exists or creatable? ' + str(is_path_exists_or_creatable('/dev')))
"/dev" exists or creatable? True
>>> print('"/dev/foo.bar" exists or creatable? ' + str(is_path_exists_or_creatable('/dev/foo.bar')))
"/dev/foo.bar" exists or creatable? False
>>> print('Null byte exists or creatable? ' + str(is_path_exists_or_creatable('\x00')))
Null byte exists or creatable? False
정신 이상. 고통을 넘어. 파이썬 이식성 문제를 발견 할 것입니다.