Saya memiliki jalur (termasuk direktori dan nama file).
Saya perlu menguji apakah nama file itu valid, misalnya jika sistem file mengizinkan saya membuat file dengan nama seperti itu.
Nama file memiliki beberapa karakter unicode di dalamnya.
Aman untuk mengasumsikan segmen direktori dari jalur tersebut valid dan dapat diakses ( saya mencoba membuat pertanyaan lebih dapat diterapkan secara umum, dan tampaknya saya terlalu jauh ).
Saya sangat tidak ingin melarikan diri kecuali jika saya harus .
Saya akan memposting beberapa contoh karakter yang saya hadapi, tetapi tampaknya mereka dihapus secara otomatis oleh sistem pertukaran tumpukan. Bagaimanapun, saya ingin mempertahankan entitas unicode standar seperti ö
, dan hanya menghindari hal-hal yang tidak valid dalam nama file.
Ini tangkapannya. Mungkin (atau mungkin tidak) sudah ada file di target jalur. Saya perlu menyimpan file itu jika memang ada, dan tidak membuat file jika tidak ada.
Pada dasarnya saya ingin memeriksa apakah saya dapat menulis ke jalur tanpa benar-benar membuka jalur untuk menulis (dan pembuatan file otomatis / pemogokan file yang biasanya memerlukan).
Dengan demikian:
try:
open(filename, 'w')
except OSError:
# handle error here
Tidak dapat diterima, karena akan menimpa file yang ada, yang tidak ingin saya sentuh (jika ada), atau membuat file tersebut jika tidak ada.
Saya tahu saya bisa melakukan:
if not os.access(filePath, os.W_OK):
try:
open(filePath, 'w').close()
os.unlink(filePath)
except OSError:
# handle error here
Tapi itu akan membuat file di filePath
, yang kemudian harus saya lakukan os.unlink
.
Pada akhirnya, sepertinya menghabiskan 6 atau 7 baris untuk melakukan sesuatu yang sesederhana os.isvalidpath(filePath)
atau serupa.
Selain itu, saya membutuhkan ini untuk berjalan di (setidaknya) Windows dan MacOS, jadi saya ingin menghindari hal-hal khusus platform.
``
sumber
Jawaban:
tl; dr
Panggil
is_path_exists_or_creatable()
fungsi yang ditentukan di bawah ini.Ketat Python 3. Begitulah cara kami menggulung.
Kisah Dua Pertanyaan
Pertanyaan tentang "Bagaimana cara menguji validitas nama jalur dan, untuk nama jalur yang valid, keberadaan atau kemampuan penulisan jalur tersebut?" jelas merupakan dua pertanyaan terpisah. Keduanya menarik, dan tidak ada yang menerima jawaban yang benar-benar memuaskan di sini ... atau, di mana pun yang bisa saya grep.
vikki 's jawabannya mungkin hews yang paling dekat, namun memiliki kelemahan yang luar biasa:
Kami akan memperbaiki semua itu.
Pertanyaan # 0: Apa itu Pathname Validity Lagi?
Sebelum melemparkan pakaian daging kita yang rapuh ke dalam moshpits yang penuh dengan ular piton, kita mungkin harus mendefinisikan apa yang kita maksud dengan "validitas nama jalur". Apa sebenarnya yang mendefinisikan validitas?
Yang kami maksud dengan "validitas nama jalur" adalah kebenaran sintaksis dari nama jalur yang terkait dengan sistem file root dari sistem saat ini - terlepas dari apakah jalur tersebut atau direktori induknya secara fisik ada. Sebuah nama jalur secara sintaksis benar di bawah definisi ini jika itu memenuhi semua persyaratan sintaksis dari sistem berkas root.
Yang kami maksud dengan "sistem file root" adalah:
/
).%HOMEDRIVE%
, huruf kandar dengan akhiran titik dua yang berisi penginstalan Windows saat ini (biasanya tetapi tidak harusC:
).Arti "kebenaran sintaksis", pada gilirannya, bergantung pada jenis sistem file root. Untuk
ext4
(dan sebagian besar tetapi tidak semua sistem file yang kompatibel dengan POSIX), nama jalur secara sintaksis benar jika dan hanya jika nama jalur itu:\x00
dengan Python). Ini adalah persyaratan yang sulit untuk semua sistem file yang kompatibel dengan POSIX.'a'*256
dengan Python). Sebuah komponen path adalah substring terpanjang dari pathname yang tidak mengandung/
karakter (misalnya,bergtatt
,ind
,i
, danfjeldkamrene
di pathname/bergtatt/ind/i/fjeldkamrene
).Ketepatan sintaksis. Sistem file root. Itu dia.
Pertanyaan # 1: Bagaimana Sekarang Kita Melakukan Validitas Pathname?
Memvalidasi nama jalur dengan Python ternyata tidak intuitif. Saya sangat setuju dengan Nama Palsu di sini:
os.path
paket resmi harus memberikan solusi out-of-the-box untuk ini. Untuk alasan yang tidak diketahui (dan mungkin tidak menarik), ternyata tidak. Untungnya, membuka gulungan solusi ad-hoc Anda sendiri tidak terlalu memilukan ...Oke, sebenarnya begitu. Itu berbulu; itu jorok; itu mungkin terkekeh saat menggelegar dan cekikikan saat bersinar. Tapi apa yang akan kamu lakukan? Nuthin '.
Kami akan segera turun ke jurang radioaktif kode tingkat rendah. Tapi pertama-tama, mari kita bicara tentang toko tingkat tinggi. Standar
os.stat()
danos.lstat()
fungsi memunculkan pengecualian berikut saat meneruskan nama jalur yang tidak valid:FileNotFoundError
.WindowsError
yangwinerror
atributnya adalah123
(yaitu,ERROR_INVALID_NAME
).'\x00'
), instance dariTypeError
.OSError
yangerrcode
atributnya adalah:errno.ERANGE
,. (Ini tampaknya merupakan bug tingkat OS, atau disebut sebagai "interpretasi selektif" dari standar POSIX.)errno.ENAMETOOLONG
,.Yang terpenting, ini menyiratkan bahwa hanya nama jalur yang berada di direktori yang ada yang dapat divalidasi. Fungsi
os.stat()
danos.lstat()
memunculkanFileNotFoundError
pengecualian umum ketika nama jalur yang diteruskan yang berada di direktori yang tidak ada, terlepas dari apakah nama jalur tersebut tidak valid atau tidak. Keberadaan direktori lebih diutamakan daripada ketidakabsahan nama jalur.Apakah ini berarti bahwa nama path yang berada di direktori yang tidak ada tidak dapat divalidasi? Ya - kecuali kita memodifikasi nama path tersebut agar berada di direktori yang ada. Namun, apakah itu mungkin secara aman? Bukankah memodifikasi nama jalur mencegah kami memvalidasi nama jalur asli?
Untuk menjawab pertanyaan ini, ingat dari atas bahwa nama jalur yang benar secara sintaksis pada sistem
ext4
file tidak berisi komponen jalur (A) yang berisi byte nol atau (B) dengan panjang lebih dari 255 byte. Karenanya,ext4
nama jalur valid jika dan hanya jika semua komponen jalur dalam nama jalur itu valid. Hal ini berlaku untuk sebagian besar sistem file dunia nyata yang menarik.Apakah wawasan bertele-tele itu benar-benar membantu kita? Iya. Ini mengurangi masalah yang lebih besar dalam memvalidasi nama jalur lengkap dalam satu gerakan ke masalah yang lebih kecil hanya memvalidasi semua komponen jalur dalam nama jalur itu. Nama jalur arbitrer apa pun dapat divalidasi (terlepas dari apakah nama jalur tersebut berada di direktori yang ada atau tidak) secara lintas platform dengan mengikuti algoritme berikut:
/troldskog/faren/vild
jalur ke dalam daftar['', 'troldskog', 'faren', 'vild']
)./troldskog
).os.stat()
atauos.lstat()
. Jika nama jalur itu dan karenanya komponen itu tidak valid, panggilan ini dijamin akan memunculkan pengecualian yang mengekspos jenis ketidakabsahan daripadaFileNotFoundError
pengecualian umum . Mengapa? Karena nama jalur itu berada di direktori yang sudah ada. (Logika melingkar adalah melingkar.)Apakah ada direktori yang dijamin ada? Ya, tetapi biasanya hanya satu: direktori paling atas dari sistem berkas root (seperti yang didefinisikan di atas).
Meneruskan nama jalur yang berada di direktori lain (dan karenanya tidak dijamin ada) ke
os.stat()
atauos.lstat()
mengundang kondisi balapan, meskipun direktori tersebut sebelumnya telah diuji keberadaannya. Mengapa? Karena proses eksternal tidak dapat dicegah untuk secara bersamaan menghapus direktori tersebut setelah pengujian dilakukan, tetapi sebelum nama jalur tersebut diteruskan keos.stat()
atauos.lstat()
. Bebaskan anjing kegilaan yang menghancurkan pikiran!Ada juga manfaat sampingan yang substansial dari pendekatan di atas: keamanan. (Bukankah itu bagus?) Secara khusus:
Pendekatan di atas menghilangkan ini dengan hanya memvalidasi komponen path dari nama path terhadap direktori root dari filesystem root. (Bahkan jika itu basi, lambat, atau tidak dapat diakses, Anda memiliki masalah yang lebih besar daripada validasi nama jalur.)
Kalah? Bagus. Mari kita mulai. (Python 3 diasumsikan. Lihat "What Is Fragile Hope for 300, leycec ?")
import errno, os # Sadly, Python fails to provide the following magic number for us. 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. ''' # If this pathname is either not a string or is but is empty, this pathname # is invalid. try: if not isinstance(pathname, str) or not pathname: return False # Strip this pathname's Windows-specific drive specifier (e.g., `C:\`) # if any. Since Windows prohibits path components from containing `:` # characters, failing to strip this `:`-suffixed prefix would # erroneously invalidate all valid absolute Windows pathnames. _, pathname = os.path.splitdrive(pathname) # Directory guaranteed to exist. If the current OS is Windows, this is # the drive to which Windows was installed (e.g., the "%HOMEDRIVE%" # environment variable); else, the typical root directory. root_dirname = os.environ.get('HOMEDRIVE', 'C:') \ if sys.platform == 'win32' else os.path.sep assert os.path.isdir(root_dirname) # ...Murphy and her ironclad Law # Append a path separator to this directory if needed. root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep # Test whether each path component split from this pathname is valid or # not, ignoring non-existent and non-readable path components. for pathname_part in pathname.split(os.path.sep): try: os.lstat(root_dirname + pathname_part) # If an OS-specific exception is raised, its error code # indicates whether this pathname is valid or not. Unless this # is the case, this exception implies an ignorable kernel or # filesystem complaint (e.g., path not found or inaccessible). # # Only the following exceptions indicate invalid pathnames: # # * Instances of the Windows-specific "WindowsError" class # defining the "winerror" attribute whose value is # "ERROR_INVALID_NAME". Under Windows, "winerror" is more # fine-grained and hence useful than the generic "errno" # attribute. When a too-long pathname is passed, for example, # "errno" is "ENOENT" (i.e., no such file or directory) rather # than "ENAMETOOLONG" (i.e., file name too long). # * Instances of the cross-platform "OSError" class defining the # generic "errno" attribute whose value is either: # * Under most POSIX-compatible OSes, "ENAMETOOLONG". # * Under some edge-case OSes (e.g., SunOS, *BSD), "ERANGE". 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 # If a "TypeError" exception was raised, it almost certainly has the # error message "embedded NUL character" indicating an invalid pathname. except TypeError as exc: return False # If no exception was raised, all path components and hence this # pathname itself are valid. (Praise be to the curmudgeonly python.) else: return True # If any other exception was raised, this is an unrelated fatal issue # (e.g., a bug). Permit this exception to unwind the call stack. # # Did we mention this should be shipped with Python already?
Selesai. Jangan menyipitkan mata pada kode itu. ( Itu menggigit. )
Pertanyaan # 2: Keberadaan atau Keberadaan Nama Jalur yang Mungkin Tidak Valid, Eh?
Menguji keberadaan atau pembuatan nama jalur yang mungkin tidak valid, mengingat solusi di atas, kebanyakan sepele. Kunci kecil di sini adalah memanggil fungsi yang ditentukan sebelumnya sebelum menguji jalur yang dilewati:
def is_path_creatable(pathname: str) -> bool: ''' `True` if the current user has sufficient permissions to create the passed pathname; `False` otherwise. ''' # Parent directory of the passed path. If empty, we substitute the current # working directory (CWD) instead. 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: # To prevent "os" module calls from raising undesirable exceptions on # invalid pathnames, is_pathname_valid() is explicitly called first. return is_pathname_valid(pathname) and ( os.path.exists(pathname) or is_path_creatable(pathname)) # Report failure on non-fatal filesystem complaints (e.g., connection # timeouts, permissions issues) implying this path to be inaccessible. All # other exceptions are unrelated fatal issues and should not be caught here. except OSError: return False
Selesai dan selesai. Kecuali tidak sepenuhnya.
Pertanyaan # 3: Keberadaan Pathname atau Penulisan yang Mungkin Tidak Valid di Windows
Ada peringatan. Tentu saja ada.
Seperti yang diakui oleh
os.access()
dokumentasi resmi :Tidak mengherankan, Windows adalah tersangka biasa di sini. Berkat penggunaan Access Control List (ACL) yang ekstensif pada sistem file NTFS, model izin-bit POSIX yang sederhana memetakan dengan buruk ke realitas Windows yang mendasarinya. Meskipun ini (bisa dibilang) bukan kesalahan Python, itu mungkin menjadi perhatian untuk aplikasi yang kompatibel dengan Windows.
Jika ini Anda, alternatif yang lebih kuat dibutuhkan. Jika jalur yang diteruskan tidak ada, kami malah mencoba membuat file sementara yang dijamin akan segera dihapus di direktori induk dari jalur tersebut - tes kreasi yang lebih portabel (jika mahal):
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. ''' # Parent directory of the passed path. If empty, we substitute the current # working directory (CWD) instead. dirname = os.path.dirname(pathname) or os.getcwd() try: # For safety, explicitly close and hence delete this temporary file # immediately after creating it in the passed path's parent directory. with tempfile.TemporaryFile(dir=dirname): pass return True # While the exact type of exception raised by the above function depends on # the current version of the Python interpreter, all such types subclass the # following exception superclass. 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: # To prevent "os" module calls from raising undesirable exceptions on # invalid pathnames, is_pathname_valid() is explicitly called first. return is_pathname_valid(pathname) and ( os.path.exists(pathname) or is_path_sibling_creatable(pathname)) # Report failure on non-fatal filesystem complaints (e.g., connection # timeouts, permissions issues) implying this path to be inaccessible. All # other exceptions are unrelated fatal issues and should not be caught here. except OSError: return False
Perhatikan, bagaimanapun, bahwa ini pun mungkin tidak cukup.
Berkat User Access Control (UAC), Windows Vista yang selalu tidak dapat ditiru dan semua iterasi berikutnya secara terang - terangan berbohong tentang perizinan yang berkaitan dengan direktori sistem. Ketika pengguna non-Administrator mencoba membuat file baik di kanonis
C:\Windows
atauC:\Windows\system32
direktori, UAC secara dangkal mengizinkan pengguna untuk melakukannya sambil benar-benar mengisolasi semua file yang dibuat ke dalam "Toko Virtual" di profil pengguna tersebut. (Siapa yang menyangka bahwa menipu pengguna akan memiliki konsekuensi jangka panjang yang berbahaya?)Ini gila. Ini adalah Windows.
Buktikan itu
Berani kita? Saatnya untuk menguji coba tes di atas.
Karena NULL adalah satu-satunya karakter yang dilarang dalam nama path pada sistem file berorientasi UNIX, mari kita manfaatkan itu untuk menunjukkan kebenaran yang dingin dan sulit - mengabaikan kejahatan Windows yang tidak dapat diabaikan, yang terus terang membuat saya bosan dan marah dalam ukuran yang sama:
>>> 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
Di luar kewarasan. Melampaui rasa sakit. Anda akan menemukan masalah portabilitas Python.
sumber
is_
. Ini adalah kekurangan karakter saya. Meskipun demikian, perlu dicatat: Anda tidak bisa menyenangkan semua orang, dan terkadang Anda tidak bisa menyenangkan siapa pun. ;)if os.path.exists(filePath): #the file is there elif os.access(os.path.dirname(filePath), os.W_OK): #the file does not exists but write privileges are given else: #can not write there
Perhatikan bahwa
path.exists
bisa gagal karena lebih dari sekedar alasanthe file is not there
sehingga Anda mungkin harus melakukan tes yang lebih baik seperti pengujian jika direktori yang berisi ada dan seterusnya.Setelah saya berdiskusi dengan OP ternyata, yang menjadi masalah utama sepertinya, bahwa nama file mungkin mengandung karakter yang tidak diperbolehkan oleh filesystem. Tentu saja mereka perlu dihapus tetapi OP ingin mempertahankan sebanyak mungkin kemudahan yang dapat dibaca oleh sistem file.
Sayangnya saya tidak tahu solusi yang baik untuk ini. Namun , jawaban Cecil Curry melihat lebih dekat pada pendeteksian masalahnya.
sumber
or can be created
baik saya tidak membacanya dari pertanyaan Anda. Membaca izin akan bergantung pada platfrom sampai batas tertentu.os.path.exists(filePath)
teknis memunculkan pengecualian pada nama jalur yang tidak valid, pengecualian tersebut perlu ditangkap dan dibedakan secara eksplisit dari pengecualian lain yang tidak terkait. Selain itu, panggilan yang sama kembaliFalse
ke jalur yang ada di mana pengguna saat ini tidak memiliki izin baca. Singkatnya, kejahatan.Dengan Python 3, bagaimana dengan:
try: with open(filename, 'x') as tempfile: # OSError if file exists or is invalid pass except OSError: # handle error here
Dengan opsi 'x' kami juga tidak perlu khawatir dengan kondisi balapan. Lihat dokumentasi di sini .
Sekarang, ini AKAN membuat file sementara yang berumur sangat pendek jika belum ada - kecuali namanya tidak valid. Jika Anda bisa menerimanya, itu menyederhanakan banyak hal.
sumber
open(filename,'r') #2nd argument is r and not w
akan membuka file atau memberikan kesalahan jika tidak ada. Jika ada kesalahan, maka Anda dapat mencoba menulis ke jalur, jika Anda tidak bisa maka Anda mendapatkan kesalahan kedua
try: open(filename,'r') return True except IOError: try: open(filename, 'w') return True except IOError: return False
Lihat juga di sini tentang izin di windows
sumber
tempfile.TemporaryFile()
yang secara otomatis akan menghancurkan tempfile ketika keluar dari ruang lingkup.os.path.join
, jadi saya tidak memiliki masalah `\` melarikan diri. Selain itu, saya tidak benar-benar mengalami masalah izin direktori . Saya mengalami masalah nama direktori (dan nama file) .filename
berisi karakter yang tidak valid. Saya telah mengedit jawabannyacoba
os.path.exists
ini akan memeriksa jalur dan mengembalikanTrue
jika ada danFalse
jika tidak.sumber