ตรวจสอบว่าเส้นทางถูกต้องใน Python โดยไม่ต้องสร้างไฟล์ที่เป้าหมายของเส้นทางหรือไม่


98

ฉันมีเส้นทาง (รวมถึงไดเรกทอรีและชื่อไฟล์)
ฉันต้องการทดสอบว่าชื่อไฟล์นั้นถูกต้องหรือไม่เช่นระบบไฟล์จะอนุญาตให้ฉันสร้างไฟล์ที่มีชื่อดังกล่าวหรือไม่
ชื่อไฟล์มีอักขระ Unicodeอยู่

ปลอดภัยที่จะถือว่าส่วนไดเร็กทอรีของเส้นทางนั้นถูกต้องและเข้าถึงได้ ( ฉันพยายามทำให้คำถามมีความเกี่ยวข้องมากขึ้นและดูเหมือนว่าฉันจะไปไกลเกินไป )

ฉันไม่ต้องการที่จะหลบหนีอะไรนอกจากจะต้องทำ

ฉันจะโพสต์ตัวละครตัวอย่างที่ฉันกำลังจัดการอยู่ แต่ดูเหมือนว่าจะถูกลบออกโดยอัตโนมัติโดยระบบแลกเปลี่ยนสแต็ก อย่างไรก็ตามฉันต้องการเก็บเอนทิตี Unicode มาตรฐานไว้เช่นเดียวกับöและหลีกเลี่ยงสิ่งที่ไม่ถูกต้องในชื่อไฟล์


นี่คือการจับ อาจมี (หรืออาจไม่มี) เป็นไฟล์ที่เป้าหมายของพา ธ ฉันจำเป็นต้องเก็บไฟล์นั้นไว้หากไม่มีอยู่และอย่าสร้างไฟล์หากไม่มี

โดยทั่วไปฉันต้องการตรวจสอบว่าฉันสามารถเขียนไปยังเส้นทางโดยไม่ต้องเปิดเส้นทางสำหรับการเขียนได้จริงหรือไม่ (และการสร้างไฟล์อัตโนมัติ / การโคลนไฟล์ที่มักเกิดขึ้น)

ดังต่อไปนี้:

try:
    open(filename, 'w')
except OSError:
    # handle error here

จากที่นี่

ไม่สามารถยอมรับได้เนื่องจากจะเขียนทับไฟล์ที่มีอยู่ซึ่งฉันไม่ต้องการแตะต้อง (หากมี) หรือสร้างไฟล์ดังกล่าวหากไม่ใช่

ฉันรู้ว่าฉันทำได้:

if not os.access(filePath, os.W_OK):
    try:
        open(filePath, 'w').close()
        os.unlink(filePath)
    except OSError:
        # handle error here

แต่ที่จะสร้างไฟล์ที่ซึ่งผมก็จะมีการfilePathos.unlink

ในท้ายที่สุดดูเหมือนว่าจะใช้เวลา 6 หรือ 7 บรรทัดเพื่อทำบางสิ่งที่ควรจะเรียบง่ายos.isvalidpath(filePath)หรือคล้ายกัน


นอกจากนี้ฉันต้องการให้สิ่งนี้ทำงานบน (อย่างน้อย) Windows และ MacOS ดังนั้นฉันจึงต้องการหลีกเลี่ยงสิ่งเฉพาะแพลตฟอร์ม


หากคุณต้องการทดสอบว่ามีเส้นทางนั้นอยู่และคุณสามารถเขียนลงในนั้นได้จากนั้นเพียงสร้างและลบไฟล์อื่น ๆ ตั้งชื่อให้ไม่ซ้ำกัน (หรือไม่ซ้ำกันเท่าที่จะทำได้) เพื่อหลีกเลี่ยงปัญหาผู้ใช้หลายคน / หลายเธรด มิฉะนั้นคุณกำลังมองหาการตรวจสอบการอนุญาตซึ่งจะนำคุณไปสู่ความสับสนเฉพาะของระบบปฏิบัติการ
Tony Hopkinson

3
@ โทนี่ฮอปกินสัน - โดยพื้นฐานแล้วฉันต้องการตรวจสอบว่าฉันสามารถเขียนเส้นทางโดยไม่ต้องเขียนอะไรได้จริงหรือไม่
ชื่อปลอม

หากคุณไม่มีอะไรจะเขียนลงในไฟล์ทำไมคุณต้องรู้ว่าคุณสามารถทำได้หรือไม่?
Karl Knechtel

@Karl Knechtel - ถ้าฉันเขียนถึงมันและมีไฟล์อยู่แล้วมันจะทำให้ไฟล์ที่มีอยู่เสียหาย
ชื่อปลอม

2
@FakeName - คุณมักจะมีสภาพการแข่งขันที่ละเอียดอ่อนที่นี่ ระหว่างการตรวจสอบว่าไม่มีไฟล์อยู่ แต่สามารถสร้างได้จากนั้นสร้างไฟล์กระบวนการอื่นบางอย่างสามารถสร้างได้และคุณจะโคลนไฟล์ต่อไป แน่นอนขึ้นอยู่กับการใช้งานของคุณว่านี่เป็นปัญหาที่เกิดขึ้นจริงหรือไม่ ...
ถึง

คำตอบ:


155

tl; dr

เรียกใช้is_path_exists_or_creatable()ฟังก์ชันที่กำหนดไว้ด้านล่าง

Python 3 อย่างเคร่งครัดนั่นเป็นเพียงวิธีที่เราหมุน

เรื่องของสองคำถาม

คำถามที่ว่า "ฉันจะทดสอบความถูกต้องของชื่อพา ธ ได้อย่างไรและสำหรับชื่อพา ธ ที่ถูกต้องการมีอยู่หรือความสามารถในการเขียนของเส้นทางเหล่านั้น" เป็นคำถามสองข้อที่แยกจากกันอย่างชัดเจน ทั้งสองอย่างน่าสนใจและไม่ได้รับคำตอบที่น่าพอใจอย่างแท้จริงที่นี่ ... หรือทุกที่ที่ฉันสามารถ grep ได้

คำตอบของvikkiอาจจะใกล้เคียงที่สุด แต่มีข้อเสียที่น่าทึ่งคือ:

  • โดยไม่จำเป็นต้องเปิด ( ... แล้วไม่สามารถปิดได้อย่างน่าเชื่อถือ ) ที่จับไฟล์
  • ไม่จำเป็นต้องเขียน ( ... แล้วล้มเหลวในการปิดหรือลบที่เชื่อถือได้ ) ไฟล์ 0 ไบต์
  • การละเว้นข้อผิดพลาดเฉพาะระบบปฏิบัติการที่แยกความแตกต่างระหว่างชื่อพา ธ ที่ไม่ถูกต้องที่ไม่สามารถละเว้นและปัญหาระบบไฟล์ที่ละเว้นได้ ไม่น่าแปลกใจที่นี่เป็นสิ่งสำคัญสำหรับ Windows ( ดูด้านล่าง )
  • ละเว้นเงื่อนไขการแข่งขันที่เกิดจากกระบวนการภายนอกพร้อมกัน (อีกครั้ง) การย้ายไดเร็กทอรีหลักของชื่อพา ธ ที่จะทดสอบ ( ดูด้านล่าง )
  • การละเว้นการหมดเวลาการเชื่อมต่อซึ่งเป็นผลมาจากชื่อพา ธ นี้ที่อยู่ในระบบไฟล์เก่าช้าหรือไม่สามารถเข้าถึงได้ชั่วคราว สิ่งนี้อาจเปิดเผยบริการที่เปิดเผยต่อสาธารณะถึงการโจมตี DoS -driven ที่อาจเกิดขึ้น ( ดูด้านล่าง )

เราจะแก้ไขทั้งหมดนั้น

คำถาม # 0: ความถูกต้องของชื่อพา ธ คืออะไรอีกครั้ง?

ก่อนที่จะเหวี่ยงชุดเนื้อบอบบางของเราเข้าไปในโพรงแห่งความเจ็บปวดที่เต็มไปด้วยงูหลามเราน่าจะนิยามความหมายของ "ความถูกต้องของชื่อพา ธ " สิ่งที่กำหนดความถูกต้องคืออะไร?

โดย "ความถูกต้องของชื่อพา ธ " เราหมายถึงความถูกต้องทางไวยากรณ์ของชื่อพา ธ ที่เกี่ยวข้องกับระบบไฟล์รูทของระบบปัจจุบัน - ไม่ว่าพา ธ นั้นหรือไดเร็กทอรีพาเรนต์นั้นจะมีอยู่จริง ชื่อพา ธ ถูกต้องตามหลักไวยากรณ์ภายใต้ข้อกำหนดนี้หากเป็นไปตามข้อกำหนดทางวากยสัมพันธ์ทั้งหมดของระบบไฟล์รูท

โดย "root filesystem" เราหมายถึง:

  • บนระบบที่เข้ากันได้กับ POSIX ระบบไฟล์ที่ติดตั้งกับไดเร็กทอรีราก ( /)
  • บน Windows ระบบไฟล์ที่ติดตั้งกับ%HOMEDRIVE%อักษรชื่อไดรฟ์ที่ต่อท้ายด้วยโคลอนที่มีการติดตั้ง Windows ปัจจุบัน (โดยทั่วไป แต่ไม่จำเป็นC:)

ในทางกลับกันความหมายของ "ความถูกต้องทางวากยสัมพันธ์" ขึ้นอยู่กับประเภทของระบบไฟล์รูท สำหรับระบบไฟล์ext4(และส่วนใหญ่ แต่ไม่ใช่ทั้งหมดที่เข้ากันได้กับ POSIX) ชื่อพา ธ จะถูกต้องตามหลักไวยากรณ์ถ้าชื่อพา ธ นั้น

  • ไม่มีไบต์ว่าง (เช่น\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)
    • ภายใต้ระบบปฏิบัติการอื่น ๆ ทั้งหมด:
    • สำหรับชื่อพา ธ ที่มี null ไบต์ (เช่น'\x00') อินสแตนซ์ของTypeError.
    • สำหรับชื่อพา ธ ที่มีส่วนประกอบพา ธ ที่ยาวกว่า 255 ไบต์อินสแตนซ์OSErrorที่มีerrcodeแอตทริบิวต์คือ:
      • ภายใต้ SunOS และครอบครัว * BSD errno.ERANGEระบบปฏิบัติการ, (สิ่งนี้ดูเหมือนจะเป็นข้อบกพร่องระดับ OS หรือเรียกอีกอย่างว่า "การตีความแบบเลือก" ของมาตรฐาน POSIX)
      • ภายใต้ OS อื่น ๆ ทั้งหมด, errno.ENAMETOOLONG.

โดยนัยสำคัญนี้หมายความว่าเฉพาะชื่อพา ธ ที่อยู่ในไดเร็กทอรีที่มีอยู่เท่านั้นที่สามารถตรวจสอบได้ os.stat()และos.lstat()ฟังก์ชั่นเพิ่มทั่วไปFileNotFoundErrorยกเว้นเมื่อผ่าน pathnames พำนักอยู่ในไดเรกทอรีไม่ใช่ที่มีอยู่โดยไม่คำนึงว่า pathnames เหล่านี้จะไม่ถูกต้องหรือไม่ การมีอยู่ของไดเร็กทอรีมีความสำคัญเหนือความไม่ถูกต้องของชื่อพา ธ

นี่หมายความว่าชื่อพา ธ ที่อยู่ในไดเร็กทอรีที่ไม่มีอยู่นั้นไม่สามารถใช้ได้หรือไม่? ใช่ - เว้นแต่เราจะปรับเปลี่ยนชื่อพา ธ เหล่านั้นให้อยู่ในไดเร็กทอรีที่มีอยู่ เป็นไปได้อย่างปลอดภัยหรือไม่? การแก้ไขชื่อพา ธ ไม่ควรทำให้เราไม่สามารถตรวจสอบชื่อพา ธ เดิมได้ใช่หรือไม่

ในการตอบคำถามนี้โปรดจำจากด้านบนว่าชื่อพา ธ ที่ถูกต้องตามหลักไวยากรณ์บนext4ระบบไฟล์ไม่มีส่วนประกอบพา ธ(A)ที่มี null ไบต์หรือ(B) ที่มีความยาวเกิน 255 ไบต์ ดังนั้นext4ชื่อพา ธ จึงใช้ได้ก็ต่อเมื่อคอมโพเนนต์พา ธ ทั้งหมดในชื่อพา ธ นั้นถูกต้อง นี่เป็นเรื่องจริงสำหรับระบบไฟล์ที่น่าสนใจในโลกแห่งความเป็นจริงส่วนใหญ่

ความเข้าใจเชิงลึกนั้นช่วยเราได้จริงหรือ ใช่. ช่วยลดปัญหาใหญ่ในการตรวจสอบความถูกต้องของชื่อพา ธ แบบเต็มในคราวเดียวไปจนถึงปัญหาเล็ก ๆ ในการตรวจสอบความถูกต้องเฉพาะส่วนประกอบของเส้นทางทั้งหมดในชื่อพา ธ นั้น ชื่อพา ธ ที่กำหนดเองสามารถตรวจสอบได้ (ไม่ว่าชื่อพา ธ นั้นจะอยู่ในไดเร็กทอรีที่มีอยู่หรือไม่ก็ตาม) ในลักษณะข้ามแพลตฟอร์มโดยทำตามอัลกอริทึมต่อไปนี้:

  1. แยกชื่อพา ธ นั้นออกเป็นส่วนประกอบพา ธ (เช่นชื่อพา ธ/troldskog/faren/vildในรายการ['', 'troldskog', 'faren', 'vild'])
  2. สำหรับแต่ละองค์ประกอบดังกล่าว:
    1. รวมชื่อพา ธ ของไดเร็กทอรีที่รับประกันว่ามีอยู่กับคอมโพเนนต์นั้นในชื่อพา ธ ชั่วคราวใหม่ (เช่น/troldskog)
    2. ผ่านชื่อพา ธ ที่หรือos.stat() os.lstat()หากชื่อพา ธ นั้นและด้วยเหตุนี้ส่วนประกอบนั้นจึงไม่ถูกต้องการเรียกนี้จะรับประกันได้ว่าจะยกข้อยกเว้นที่เปิดเผยประเภทของความไม่ถูกต้องแทนที่จะเป็นFileNotFoundErrorข้อยกเว้นทั่วไป ทำไม? เนื่องจากชื่อพา ธ นั้นอยู่ในไดเร็กทอรีที่มีอยู่ (ลอจิกแบบวงกลมคือวงกลม)

มีไดเร็กทอรีที่รับประกันว่ามีอยู่จริงหรือไม่? ใช่ แต่โดยทั่วไปจะมีเพียงไดเร็กทอรีบนสุดของระบบไฟล์รูท (ตามที่กำหนดไว้ด้านบน)

การส่งชื่อพา ธ ที่อยู่ในไดเร็กทอรีอื่น ๆ (และไม่รับประกันว่าจะมีอยู่) ไปยังos.stat()หรือos.lstat()เชิญเงื่อนไขการแข่งขันแม้ว่าไดเร็กทอรีนั้นจะถูกทดสอบก่อนหน้านี้ ทำไม? เพราะกระบวนการภายนอกไม่สามารถป้องกันจากการควบลบไดเรกทอรีที่หลังจากการทดสอบที่ได้รับการดำเนินการ แต่ก่อนที่จะพา ธ ที่ส่งผ่านไปหรือos.stat() os.lstat()ปลดปล่อยสุนัขบ้าคลั่ง!

แนวทางข้างต้นมีประโยชน์มากมายเช่นกัน: ความปลอดภัย (ไม่ว่าดี?) โดยเฉพาะ:

แอปพลิเคชันแบบหันหน้าไปทางด้านหน้าตรวจสอบความถูกต้องของชื่อพา ธ โดยพลการจากแหล่งที่มาที่ไม่น่าเชื่อถือโดยเพียงแค่ส่งชื่อพา ธ ดังกล่าวไปยังos.stat()หรือos.lstat()มีความอ่อนไหวต่อการโจมตี Denial of Service (DoS) และการโจมตีด้วยหมวกดำอื่น ๆ ผู้ใช้ที่เป็นอันตรายอาจพยายามตรวจสอบชื่อพา ธ ซ้ำ ๆ ที่อยู่ในระบบไฟล์ที่ทราบว่าเก่าหรือช้า (เช่น NFS Samba share) ในกรณีนี้การระบุชื่อพา ธ ขาเข้าแบบสุ่มสี่สุ่มห้ามีแนวโน้มที่จะล้มเหลวในที่สุดด้วยการหมดเวลาการเชื่อมต่อหรือใช้เวลาและทรัพยากรมากกว่าความสามารถที่อ่อนแอของคุณในการต้านทานการว่างงาน

วิธีการข้างต้นขัดขวางสิ่งนี้โดยการตรวจสอบความถูกต้องของส่วนประกอบพา ธ ของชื่อพา ธ กับไดเร็กทอรีรากของระบบไฟล์รูทเท่านั้น (หากแม้ว่าสิ่งนั้นจะค้างช้าหรือไม่สามารถเข้าถึงได้แสดงว่าคุณมีปัญหาใหญ่กว่าการตรวจสอบชื่อพา ธ )

สูญหาย? เยี่ยมมาก เอาล่ะ. (สมมติว่า Python 3 ดู "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?

เสร็จแล้ว อย่าเหล่รหัสนั้น ( มันกัด )

คำถาม # 2: ชื่อพา ธ ที่มีอยู่หรือความคิดสร้างสรรค์อาจไม่ถูกต้องเอ๊ะ?

การทดสอบการมีอยู่หรือความสามารถในการสร้างสรรค์ของชื่อพา ธ ที่อาจไม่ถูกต้องนั้นให้วิธีแก้ปัญหาข้างต้นซึ่งส่วนใหญ่เป็นเรื่องเล็กน้อย คีย์เล็ก ๆ ที่นี่คือการเรียกใช้ฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ก่อนที่จะทดสอบเส้นทางที่ผ่าน:

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

เสร็จสิ้นและทำ ยกเว้นไม่มาก

คำถาม # 3: ชื่อพา ธ ที่มีอยู่หรือความสามารถในการเขียนบน Windows อาจไม่ถูกต้อง

มีข้อแม้ มีแน่นอน

ตามที่os.access()เอกสารอย่างเป็นทางการยอมรับ:

หมายเหตุ:การดำเนินการ I / O อาจล้มเหลวแม้ว่าจะos.access()ระบุว่าจะประสบความสำเร็จโดยเฉพาะอย่างยิ่งสำหรับการดำเนินการบนระบบไฟล์เครือข่ายซึ่งอาจมีความหมายของสิทธิ์นอกเหนือจากโมเดลบิตสิทธิ์ POSIX ตามปกติ

ไม่แปลกใจเลยที่ Windows เป็นผู้ต้องสงสัยตามปกติที่นี่ ด้วยการใช้ Access Control Lists (ACL) อย่างกว้างขวางบนระบบไฟล์ NTFS โมเดลบิตอนุญาต POSIX ที่เรียบง่ายจะจับคู่กับความเป็นจริงของ Windows ที่ไม่ดี แม้ว่าสิ่งนี้ (เนื้อหา) จะไม่ใช่ความผิดพลาดของ Python แต่ก็อาจเป็นเรื่องที่น่ากังวลสำหรับแอปพลิเคชันที่เข้ากันได้กับ Windows

หากนี่คือคุณต้องการทางเลือกอื่นที่มีประสิทธิภาพมากขึ้น หากเส้นทางผ่านไม่ได้อยู่เราแทนที่จะพยายามที่จะสร้างแฟ้มชั่วคราวรับประกันว่าจะถูกลบออกทันทีในไดเรกทอรีหลักของเส้นทางที่ - การทดสอบแบบพกพาอื่น ๆ (ถ้าแพง) ของ creatability:

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

อย่างไรก็ตามโปรดทราบว่าสิ่งนี้อาจไม่เพียงพอ

ขอบคุณ User Access Control (UAC), Windows Vista ที่ไม่เคยมีมาก่อนและการทำซ้ำที่ตามมาทั้งหมดนั้นโกหกอย่างโจ่งแจ้งเกี่ยวกับสิทธิ์ที่เกี่ยวข้องกับไดเรกทอรีระบบ เมื่อผู้ใช้ที่ไม่ใช่ผู้ดูแลพยายามที่จะสร้างไฟล์ในทั้งที่ยอมรับC:\WindowsหรือC:\Windows\system32ไดเรกทอรี UAC เผินอนุญาตให้ผู้ที่จะทำในขณะที่จริงการแยกไฟล์ที่สร้างขึ้นทั้งหมดลงใน "เสมือนร้าน" ในโปรไฟล์ของผู้ใช้ว่า (ใครจะคิดได้บ้างว่าการหลอกลวงผู้ใช้จะส่งผลเสียในระยะยาว)

มันบ้าไปแล้ว. นี่คือ Windows

พิสูจน์สิ

กล้าไหม ถึงเวลาทดลองขับการทดสอบข้างต้น

เนื่องจาก NULL เป็นอักขระตัวเดียวที่ห้ามใช้ในชื่อพา ธ บนระบบไฟล์ที่เน้น UNIX เรามาใช้ประโยชน์จากสิ่งนั้นเพื่อแสดงให้เห็นถึงความจริงที่เย็นชาและยากโดยไม่สนใจ Windows shenanigans ที่ไม่เพิกเฉยซึ่งทำให้เบื่อและโกรธฉันในระดับที่เท่าเทียมกัน:

>>> 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

เกินความมีสติ นอกเหนือจากความเจ็บปวด คุณจะพบข้อกังวลเกี่ยวกับการพกพา Python


3
ใช่ฉันเอง! การพยายามรวบรวม regex ที่ตรวจสอบความถูกต้องของชื่อพา ธ แบบพกพาข้ามร่วมกันถือเป็นการออกกำลังกายที่ไร้ประโยชน์และรับประกันว่าจะล้มเหลวสำหรับกรณี Edge ทั่วไป พิจารณาความยาวของชื่อพา ธ ใน Windows ตัวอย่างเช่น "เส้นทางสูงสุด 32,767 อักขระเป็นค่าโดยประมาณเนื่องจากคำนำหน้า" \\? \ "อาจถูกขยายเป็นสตริงที่ยาวขึ้นตามระบบในขณะทำงานและการขยายนี้จะใช้กับความยาวทั้งหมด .” ด้วยเหตุนี้จึงเป็นไปไม่ได้ในทางเทคนิคที่จะสร้าง regex ที่ตรงกับชื่อพา ธ ที่ถูกต้องเท่านั้น มันสมเหตุสมผลกว่ามากที่จะเลื่อนไปใช้ Python แทน
Cecil Curry

2
อา. ฉัน (อย่างไม่เต็มใจ) เห็น คุณกำลังทำอะไรที่แปลกกว่าการแฮ็ก regex ใช่รับรองว่าจะล้มเหลวหนักกว่าเดิม นั่นก็ล้มเหลวโดยสิ้นเชิงในการตอบคำถามที่เป็นปัญหาซึ่งไม่ใช่ "ฉันจะดึงสตริงย่อยที่ไม่ถูกต้องออกจากชื่อฐานเฉพาะของ Windows ได้อย่างไร" (... ซึ่งโดยการละเว้นของคุณเองคุณไม่สามารถแก้ไขได้ - อีกครั้งเนื่องจากกรณี edge) แต่ "ฉันจะทดสอบความถูกต้องของชื่อพา ธ แบบข้ามพอร์ตได้อย่างไรและสำหรับชื่อพา ธ ที่ถูกต้องการมีอยู่หรือความสามารถในการเขียนของเส้นทางเหล่านั้น"
Cecil Curry

1
ข้อ จำกัด เฉพาะระบบไฟล์เป็นข้อกังวลที่ถูกต้อง - แต่จะตัดทั้งสองวิธี สำหรับแอปพลิเคชั่นด้านหน้าที่ใช้ชื่อพา ธ โดยพลการจากแหล่งที่มาที่ไม่น่าเชื่อถือการอ่านแบบสุ่มสี่สุ่มห้าเป็นเรื่องที่ดีที่สุด ในกรณีนี้การบังคับให้ใช้ระบบไฟล์รูทไม่เพียง แต่สมเหตุสมผล แต่ต้องรอบคอบด้วย อย่างไรก็ตามสำหรับแอปพลิเคชันอื่นฐานผู้ใช้อาจมีความน่าเชื่อถือเพียงพอที่จะให้สิทธิ์การเข้าถึงระบบไฟล์ที่ไม่ถูกยับยั้ง ฉันจะบอกว่ามันขึ้นอยู่กับบริบทพอสมควร ขอบคุณสำหรับการสังเกตสิ่งนี้อย่างชัดเจนไม่มีใคร ! ฉันจะเพิ่มข้อแม้ด้านบน
Cecil Curry

2
สำหรับการเรียกชื่อฉันเป็นแฟนอวดความรู้ของ prefixing is_ชื่อทดสอบโดย นี่คือข้อบกพร่องของตัวละครของฉัน อย่างไรก็ตามโปรดสังเกตว่าคุณไม่สามารถทำให้ทุกคนพอใจและบางครั้งคุณก็ไม่สามารถทำให้ใครพอใจได้ ;)
Cecil Curry

1
บน Fedora 24, python 3.5.3 ชื่อพา ธ ที่มีอักขระ null แบบฝังจะพ่น: ValueError: Embedded null byte …จำเป็นต้องเพิ่ม: `` ยกเว้น ValueError เป็น exc: return False `` ก่อนหรือหลังกับดัก TypeError
mMerlin

47
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

โปรดทราบว่าpath.existsอาจล้มเหลวได้ด้วยเหตุผลหลายประการthe file is not thereคุณอาจต้องทำการทดสอบปลีกย่อยเช่นการทดสอบว่ามีไดเร็กทอรีที่มีอยู่หรือไม่เป็นต้น


หลังจากการสนทนาของฉันกับ OP ปรากฎว่าปัญหาหลักน่าจะเป็นชื่อไฟล์อาจมีอักขระที่ระบบไฟล์ไม่อนุญาต แน่นอนว่าพวกเขาจำเป็นต้องลบออก แต่ OP ต้องการรักษาความสามารถในการอ่านข้อมูลของมนุษย์ให้มากที่สุดเท่าที่ระบบไฟล์จะอนุญาต

น่าเศร้าที่ฉันไม่รู้วิธีแก้ปัญหาที่ดีสำหรับเรื่องนี้ อย่างไรก็ตามคำตอบของ Cecil Curryจะพิจารณาอย่างละเอียดในการตรวจหาปัญหา


ไม่ฉันต้องคืนค่าจริงหากไฟล์ที่เส้นทางมีอยู่หรือสามารถสร้างได้ ฉันต้องการส่งคืนเท็จหากพา ธ ไม่ถูกต้อง (เนื่องจากมีอักขระที่ไม่ถูกต้องบน windows)
ชื่อปลอม

or can be createdดีฉันไม่ได้อ่านจากคำถามของคุณ การอ่านสิทธิ์จะขึ้นอยู่กับ platfrom ในระดับหนึ่ง
ไม่มีใครย้ายออกจาก SE

1
@ ชื่อปลอม: ใช่มันจะลบบางส่วนของการพึ่งพาแพลตฟอร์ม แต่ก็ยังมีบางแพลตฟอร์มที่เสนอสิ่งที่คนอื่นทำไม่ได้และไม่มีวิธีง่ายๆในการรวมสิ่งนั้นสำหรับพวกเขาทั้งหมด ฉันอัปเดตคำตอบของฉันดูที่นั่น
ไม่มีใครย้ายออกจาก SE

1
ฉันไม่รู้ว่าทำไมคำตอบนี้ถึงได้รับการโหวต ไม่ได้มาติดกับการตอบคำถามหลักจากระยะไกล - ข้อใดกล่าวโดยสรุปคือ: "ตรวจสอบชื่อพา ธ ได้ไหม" การตรวจสอบสิทธิ์เส้นทางเป็นคำถามเสริม (และส่วนใหญ่ไม่สนใจ) ที่นี่ ในขณะที่การเรียกเพื่อos.path.exists(filePath)เพิ่มข้อยกเว้นในทางเทคนิคเกี่ยวกับชื่อพา ธ ที่ไม่ถูกต้องข้อยกเว้นเหล่านั้นจะต้องได้รับการตรวจจับอย่างชัดเจนและแตกต่างจากข้อยกเว้นอื่น ๆ ที่ไม่เกี่ยวข้อง นอกจากนี้ยังมีผลตอบแทนที่โทรเดียวกันFalseบนเส้นทางที่มีอยู่เพื่อที่ผู้ใช้ปัจจุบันไม่ได้สิทธิ์ได้อ่าน ในระยะสั้นความไม่ดี
Cecil Curry

1
@CecilCurry: หากต้องการตอบคำถามของคุณ: ดูประวัติการแก้ไขของคำถาม เช่นเดียวกับคำถามส่วนใหญ่ในตอนแรกยังไม่ชัดเจนเท่าที่ควรและแม้กระทั่งตอนนี้คำพูดของชื่อเรื่องเพียงอย่างเดียวอาจเข้าใจได้มากกว่าที่คุณพูด
ไม่มีใครย้ายออกจาก SE

9

ด้วย Python 3 วิธีการเกี่ยวกับ:

try:
    with open(filename, 'x') as tempfile: # OSError if file exists or is invalid
        pass
except OSError:
    # handle error here

ด้วยตัวเลือก 'x' ทำให้เราไม่ต้องกังวลกับสภาพการแข่งขัน ดูเอกสารที่นี่

ตอนนี้สิ่งนี้จะสร้างไฟล์ชั่วคราวที่สั้นมากหากไม่มีอยู่แล้ว - เว้นแต่ชื่อจะไม่ถูกต้อง หากคุณสามารถอยู่กับสิ่งนั้นได้มันจะทำให้สิ่งต่างๆง่ายขึ้นมาก


2
ในตอนนี้โครงการที่ต้องการสิ่งนี้ได้ก้าวไปไกลเกินกว่าจุดที่คำตอบนั้นเกี่ยวข้องจนฉันไม่สามารถยอมรับคำตอบ
ชื่อปลอม

แดกดันคำตอบที่ใช้ได้จริงไม่ดีพอ ไม่ว่าฉันจะคิดว่าคุณสามารถดูได้ว่ามีไฟล์อยู่หรือไม่ ถ้าเป็นเช่นนั้นให้พยายามคัดลอกไฟล์ไปที่อื่นแล้วลองเขียนทับ
The Matt

5
open(filename,'r')   #2nd argument is r and not w

จะเปิดไฟล์หรือแสดงข้อผิดพลาดหากไม่มีอยู่ หากมีข้อผิดพลาดคุณสามารถลองเขียนเส้นทางหากทำไม่ได้คุณจะได้รับข้อผิดพลาดที่สอง

try:
    open(filename,'r')
    return True
except IOError:
    try:
        open(filename, 'w')
        return True
    except IOError:
        return False

ดูที่นี่เกี่ยวกับสิทธิ์บน windows


1
เพื่อหลีกเลี่ยงความจำเป็นในการยกเลิกการเชื่อมโยง () ไฟล์ทดสอบอย่างชัดเจนคุณสามารถใช้tempfile.TemporaryFile()ซึ่งจะทำลาย tempfile โดยอัตโนมัติเมื่ออยู่นอกขอบเขต
D_Bye

@FakeName รหัสแตกต่างกันฉันสามารถใช้ os.access ในส่วนที่สองได้ แต่ถ้าคุณไปตามลิงค์ที่ฉันให้คุณจะเห็นว่ามันไม่ใช่ความคิดที่ดีสิ่งนี้ทำให้คุณมีตัวเลือกในการพยายามเปิด เส้นทางสำหรับการเขียน
vikki

ฉันกำลังสร้างเส้นทางของฉันด้วยos.path.joinดังนั้นฉันจึงไม่มีปัญหาในการหลบหนี นอกจากนี้ฉันไม่มีปัญหาเกี่ยวกับการอนุญาตไดเรกทอรีจริงๆ ฉันมีปัญหาชื่อไดเร็กทอรี (และชื่อไฟล์)
ชื่อปลอม

@FakeName ในกรณีนั้นคุณต้องลองและเปิดเท่านั้น (คุณไม่จำเป็นต้องเขียน) python จะให้ข้อผิดพลาดหากfilenameมีอักขระที่ไม่ถูกต้อง ฉันแก้ไขคำตอบแล้ว
vikki

1
@HelgaIliashenko การเปิดเพื่อเขียนจะเขียนทับไฟล์ที่มีอยู่ (ทำให้ว่างเปล่า) แม้ว่าคุณจะปิดทันทีโดยไม่ต้องเขียนก็ตาม นั่นเป็นเหตุผลที่ฉันเปิดให้อ่านก่อนเพราะวิธีนั้นหากคุณไม่ได้รับข้อผิดพลาดคุณจะรู้ว่ามีไฟล์อยู่แล้ว
vikki

-7

ลองทำos.path.existsเช่นนี้จะตรวจสอบเส้นทางและย้อนกลับTrueหากมีอยู่และFalseหากไม่มี


1
ไม่ฉันต้องคืนค่าจริงหากไฟล์ที่เส้นทางมีอยู่หรือสามารถสร้างได้ ฉันต้องการส่งคืนเท็จหากพา ธ ไม่ถูกต้อง (เนื่องจากมีอักขระที่ไม่ถูกต้องบน windows)
ชื่อปลอม

อักขระที่ไม่ถูกต้องประเภทใด
Nilesh

Dunno - นั่นคือแพลตฟอร์มเฉพาะ
ชื่อปลอม

2
ระบบไฟล์เฉพาะจริงๆ
Piotr Kalinowski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.