เปลี่ยนสตริงเป็นชื่อไฟล์ที่ถูกต้องหรือไม่


298

ฉันมีสตริงที่ฉันต้องการใช้เป็นชื่อไฟล์ดังนั้นฉันต้องการลบอักขระทั้งหมดที่ไม่อนุญาตให้ใช้ในชื่อไฟล์โดยใช้ Python

ฉันควรจะเข้มงวดกว่าอย่างอื่นจึงขอบอกว่าผมต้องการที่จะรักษาตัวอักษรตัวเลขและชุดเล็ก ๆ ของตัวละครอื่น ๆ "_-.() "เช่น ทางออกที่ดีที่สุดคืออะไร

ชื่อไฟล์จะต้องใช้ได้กับระบบปฏิบัติการหลายระบบ (Windows, Linux และ Mac OS) - เป็นไฟล์ MP3 ในห้องสมุดของฉันที่มีชื่อเพลงเป็นชื่อไฟล์และแชร์และสำรองข้อมูลระหว่างเครื่อง 3 เครื่อง


17
สิ่งนี้ไม่ควรถูกสร้างไว้ในโมดูล os.path ใช่ไหม
endolith

2
บางทีแม้ว่ากรณีการใช้งานของเธอจะต้องใช้เส้นทางเดียวที่ปลอดภัยในทุกแพลตฟอร์มไม่ใช่แค่เส้นทางปัจจุบันซึ่งเป็นสิ่งที่ os.path ไม่ได้ออกแบบมาเพื่อจัดการ
javawizard

2
หากต้องการขยายความคิดเห็นด้านบน: การออกแบบปัจจุบันของos.pathจริงโหลดไลบรารีที่แตกต่างกันขึ้นอยู่กับระบบปฏิบัติการ (ดูหมายเหตุที่สองในเอกสารประกอบ ) ดังนั้นหากมีการใช้งานฟังก์ชั่นการอ้างอิงในos.pathนั้นสามารถอ้างถึงสตริงสำหรับความปลอดภัย POSIX เมื่อทำงานบนระบบ POSIX หรือเพื่อความปลอดภัยของ Windows เมื่อทำงานบน windows ชื่อไฟล์ที่ได้จะไม่ถูกต้องทั้งใน windows และ POSIX ซึ่งเป็นคำถามที่ถาม
dshepherd

คำตอบ:


164

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

ข้อความของ Django กำหนดฟังก์ชั่นslugify()ซึ่งอาจเป็นมาตรฐานทองคำสำหรับสิ่งนี้ เป็นหลักรหัสของพวกเขามีดังต่อไปนี้

def slugify(value):
    """
    Normalizes string, converts to lowercase, removes non-alpha characters,
    and converts spaces to hyphens.
    """
    import unicodedata
    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    value = unicode(re.sub('[-\s]+', '-', value))
    # ...
    return value

มีมากกว่านั้น แต่ฉันก็ทิ้งไปเพราะมันไม่ได้แก้ปัญหาความเฉื่อย แต่หนีไป


11
บรรทัดสุดท้ายควรเป็น: value = unicode (re.sub ('[- \ s] +', '-', ค่า))
Joseph Turian

1
ขอบคุณ - ฉันอาจจะหายไปบางสิ่งบางอย่าง แต่ฉันได้รับ: "normalize () การโต้แย้ง 2 จะต้องเป็นยูนิโค้ดไม่ใช่ STR"
Alex Cook

"ปรับมาตรฐาน () อาร์กิวเมนต์ 2" valueหมายถึง หากค่านั้นต้องเป็น Unicode คุณต้องแน่ใจว่าเป็น Unicode จริง หรือ. คุณอาจต้องการออกจากการทำให้เป็นมาตรฐาน unicode ถ้าค่าจริงของคุณเป็นสตริง ASCII
S.Lott

8
ในกรณีที่ทุกคนไม่ได้สังเกตเห็นด้านบวกของวิธีการนี้คือมันไม่เพียง แต่ลบตัวอักษรที่ไม่ใช่ตัวอักษร แต่พยายามค้นหาตัวเลือกที่ดีก่อน (ผ่านการทำให้เป็นมาตรฐานของ NFKD) ดังนั้นéกลายเป็น e ปกติ 1 ฯลฯ ขอบคุณ
Michael Scott Cuthbert

48
slugifyฟังก์ชั่นได้ถูกย้ายไปDjango / utils / text.pyและแฟ้มที่นอกจากนี้ยังมีget_valid_filenameฟังก์ชั่น
Denilson Sá Maia

104

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

>>> import string
>>> valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
>>> valid_chars
'-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
>>> filename = "This Is a (valid) - filename%$&$ .txt"
>>> ''.join(c for c in filename if c in valid_chars)
'This Is a (valid) - filename .txt'

7
valid_chars = frozenset(valid_chars)จะไม่เจ็บ เร็วกว่า 1.5 เท่าหากใช้กับ allchars
jfs

2
คำเตือน: นี่แมปสองสายที่แตกต่างกันไปยังสายเดียวกัน >>> นำเข้าสตริง >>> valid_chars = "- . ()% s% s"% (string.ascii_letters, string.digits) >>> valid_chars '- . () abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '>>> filename = "a.com/hello/world" >>>' '.join (c สำหรับ c ใน c ชื่อไฟล์ใน valid_chars)' a.comhelloworld โลก ' ">>> '' .join (c สำหรับ c ในชื่อไฟล์ if c ใน valid_chars) 'a.comhelloworld' >>>
เบิร์ตคิงส์

3
ไม่พูดถึงว่าการตั้งชื่อไฟล์"CON"บน Windows จะได้รับคุณเป็นปัญหา ...
นาธานออสมัน

2
การจัดเรียงใหม่เล็กน้อยทำให้การระบุอักขระแทนตรงไปตรงมา ก่อนอื่นฟังก์ชันการทำงานดั้งเดิม: '' .join (c ถ้า c ใน valid_chars else '' สำหรับ c ในชื่อไฟล์) หรือด้วยอักขระหรือสตริงที่ถูกแทนที่สำหรับอักขระที่ไม่ถูกต้องทุกตัว: '' .join (c ถ้า c ใน valid_chars อื่น 'สำหรับ c ในชื่อไฟล์)
PeterVermont

101

คุณสามารถใช้ list comprehension ร่วมกับเมธอด string

>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'

3
โปรดทราบว่าคุณสามารถละเว้นวงเล็บเหลี่ยมได้ ในกรณีนี้นิพจน์ตัวสร้างจะถูกส่งผ่านเพื่อเข้าร่วมซึ่งจะบันทึกขั้นตอนการสร้างรายการที่ไม่ได้ใช้
Oben Sonne

31
+1 ชอบสิ่งนี้ ฉันได้ทำการแก้ไขเล็กน้อย: "" .join ([x if x.isalnum () อื่น "_" สำหรับ x ใน s)) - จะให้ผลลัพธ์ที่รายการที่ไม่ถูกต้องเป็น _ เช่นที่ว่างเปล่า อาจจะเป็น thelps คนอื่น
Eddie Parker

12
วิธีนี้ดีมาก! ฉันได้ทำการปรับเปลี่ยนเล็กน้อย:filename = "".join(i for i in s if i not in "\/:*?<>|")
Alex Krycek

1
น่าเสียดายที่มันไม่อนุญาตช่องว่างและจุด แต่ฉันชอบความคิด
tiktak

9
@tiktak: ถึง (ยัง) อนุญาตให้มีช่องว่างจุดและขีดล่างที่คุณสามารถไปได้"".join( x for x in s if (x.isalnum() or x in "._- "))
hardmooth

95

เหตุผลในการใช้สตริงเป็นชื่อไฟล์คืออะไร ถ้าความสามารถในการอ่านของมนุษย์ไม่ใช่ปัจจัยฉันจะไปกับโมดูล base64 ซึ่งสามารถสร้างสตริงระบบไฟล์ที่ปลอดภัย จะไม่สามารถอ่านได้ แต่คุณจะไม่ต้องจัดการกับการชนและสามารถย้อนกลับได้

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

อัปเดต : เปลี่ยนตามความคิดเห็นของ Matthew


1
เห็นได้ชัดว่านี่เป็นคำตอบที่ดีที่สุดหากเป็นเช่นนั้น
user32141

60
คำเตือน! การเข้ารหัส base64 โดยค่าเริ่มต้นประกอบด้วยอักขระ "/" เป็นเอาต์พุตที่ถูกต้องซึ่งไม่ถูกต้องในชื่อไฟล์ในระบบจำนวนมาก แทนที่จะใช้ base64.urlsafe_b64encode (your_string)
แมทธิว

15
ความสามารถในการอ่านของมนุษย์ในความเป็นจริงแล้วมักจะเป็นปัจจัยแม้ว่าจะใช้เพื่อการดีบั๊กเท่านั้น
static_rtti

5
ใน Python 3 your_stringจะต้องเป็นอาร์เรย์ไบต์หรือผลลัพธ์ที่ได้encode('ascii')เพื่อให้สามารถใช้งานได้
Noumenon

4
def url2filename(url): url = url.encode('UTF-8') return base64.urlsafe_b64encode(url).decode('UTF-8') def filename2url(f): return base64.urlsafe_b64decode(f).decode('UTF-8')
JeffProd

40

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

  • สตริงเป็นอักขระที่ไม่ถูกต้องทั้งหมด (ปล่อยให้คุณมีสตริงว่าง)

  • คุณจะได้สตริงที่มีความหมายพิเศษเช่น "." หรือ ".."

  • บน windows ชื่ออุปกรณ์บางอย่างถูกสงวนไว้ ตัวอย่างเช่นคุณไม่สามารถสร้างไฟล์ชื่อ "nul", "nul.txt" (หรือ nul.anything อันที่จริง) ชื่อที่สงวนไว้คือ:

    CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT8, LPT8, และ LPT9

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


24

มีโครงการดีๆใน Github ที่เรียกว่าpython-slugify :

ติดตั้ง:

pip install python-slugify

จากนั้นใช้:

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'

2
ฉันชอบห้องสมุดนี้ แต่ก็ไม่ดีเท่าที่ฉันคิด การทดสอบครั้งแรกตกลง แต่มันก็แปลงเป็นจุด ดังนั้นtest.txtได้รับtest-txtซึ่งมากเกินไป
therealmarv

23

เช่นเดียวกับที่S.LottตอบคุณสามารถดูDjango Frameworkสำหรับวิธีที่พวกเขาแปลงสตริงเป็นชื่อไฟล์ที่ถูกต้อง

เวอร์ชันล่าสุดและที่อัพเดตพบได้ใน utils / text.py และกำหนด "get_valid_filename" ซึ่งมีดังต่อไปนี้:

def get_valid_filename(s):
    s = str(s).strip().replace(' ', '_')
    return re.sub(r'(?u)[^-\w.]', '', s)

(ดูhttps://github.com/django/django/blob/master/django/utils/text.py )


4
สำหรับคนขี้เกียจที่ django แล้ว:django.utils.text import get_valid_filename
theannouncer

2
ในกรณีที่คุณไม่คุ้นเคยกับ regex ให้re.sub(r'(?u)[^-\w.]', '', s)ลบอักขระทั้งหมดที่ไม่ใช่ตัวอักษรไม่ใช่ตัวเลข (0-9) ไม่ใช่ขีดล่าง ('_') ไม่ใช่ขีดกลาง ('-') และไม่ใช่จุด ('.' ) "ตัวอักษร" ที่นี่มีตัวอักษร unicode ทั้งหมดเช่น漢語
cowlinator

3
คุณอาจต้องการตรวจสอบความยาว: ชื่อไฟล์ จำกัด 255 ตัวอักษร (หรือคุณรู้, 32; ขึ้นอยู่กับ FS)
Matthias Winkelmann

19

นี่คือทางออกที่ฉันใช้ในท้ายที่สุด:

import unicodedata

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)

def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(c for c in cleanedFilename if c in validFilenameChars)

การโทร unicodedata.normalize จะแทนที่อักขระที่เน้นเสียงด้วยการเทียบเท่าที่ไม่มีการเน้นเสียงซึ่งดีกว่าการแยกออก หลังจากนั้นตัวละครที่ไม่ได้รับอนุญาตทั้งหมดจะถูกลบออก

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


คุณควรจะสามารถที่จะใช้ uuid.uuid4 () เพื่อคำนำหน้าไม่ซ้ำกันของคุณ
SLF

6
กรณีของอูฐ .. อ่า
dehedgehog

สามารถแก้ไข / อัปเดตเพื่อทำงานกับ Python 3.6 ได้หรือไม่
Wavesailor

13

โปรดทราบว่าจริงๆแล้วไม่มีข้อ จำกัด เกี่ยวกับชื่อไฟล์ในระบบ Unix ที่นอกเหนือจากนี้

  • อาจไม่มี \ 0
  • มันอาจจะไม่มี /

ทุกอย่างเป็นเกมที่ยุติธรรม

$ touch "
> หลายเส้น
> ฮ่าฮ่า
> ^ [[31m แดง ^] [[0m
> ความชั่วร้าย "
$ ls -la 
-rw-r - r-- 0 Nov 17 23:39 แม้ multiline? haha ​​?? [31m แดงหรือไม่ [0m? ชั่วร้าย
$ ls -lab
-rw-r - r-- 0 พ.ย. 17 23:39 \ neven \ multiline \ nhaha \ n \ 033 [31m \ red \ \ 033 [0m \ nevil
$ perl -e 'สำหรับ $ i ของฉัน (glob (q {./* แม้ *})) {พิมพ์ $ i; } '
./
แม้หลายสาย
ฮ่าฮ่า
 สีแดง 
ชั่วร้าย

ใช่ฉันเพิ่งเก็บรหัสสี ANSI ไว้ในชื่อไฟล์และให้พวกเขามีผล

เพื่อความบันเทิงให้ใส่ตัวอักษร BEL ในชื่อไดเรกทอรีและดูความสนุกที่จะตามมาเมื่อคุณใส่ซีดีลงไป)


OP ระบุว่า "ชื่อไฟล์จะต้องถูกต้องในระบบปฏิบัติการหลายระบบ"
ประสานงาน

1
@cowlinator นั้นมีการเพิ่มการชี้แจง 10 ชั่วโมงหลังจากคำตอบของฉันถูกโพสต์ :) ตรวจสอบบันทึกการแก้ไขของ OP
Kent Fredric

12

ในหนึ่งบรรทัด:

valid_file_name = re.sub('[^\w_.)( -]', '', any_string)

คุณสามารถใส่อักขระ '_' เพื่อให้อ่านได้ง่ายขึ้น (ในกรณีที่ใช้แทนสแลชเป็นต้น)


7

คุณสามารถใช้วิธี re.sub () เพื่อแทนที่อะไรก็ได้ที่ไม่ใช่ "filelike" แต่ผลก็คือตัวละครทุกตัวสามารถใช้ได้จริง ดังนั้นจึงไม่มีฟังก์ชั่นที่สร้างไว้ล่วงหน้า (ฉันเชื่อว่า) เพื่อให้ทำงานได้

import re

str = "File!name?.txt"
f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))

จะส่งผลให้ filehandle เป็น /tmp/filename.txt


5
คุณต้องมีเส้นประเพื่อไปก่อนในการจับคู่กลุ่มจึงไม่ปรากฏเป็นช่วง re.sub ('[^ - a-zA-Z0-9 _. ()] +', '', str)
10:09 น

7
>>> import string
>>> safechars = bytearray(('_-.()' + string.digits + string.ascii_letters).encode())
>>> allchars = bytearray(range(0x100))
>>> deletechars = bytearray(set(allchars) - set(safechars))
>>> filename = u'#ab\xa0c.$%.txt'
>>> safe_filename = filename.encode('ascii', 'ignore').translate(None, deletechars).decode()
>>> safe_filename
'abc..txt'

ไม่จัดการกับสตริงว่างชื่อไฟล์พิเศษ ('nul', 'con' ฯลฯ )


+1 สำหรับตารางการแปลเป็นวิธีที่มีประสิทธิภาพมากที่สุด สำหรับชื่อไฟล์พิเศษ / เปล่าการตรวจสอบสภาพเบื้องต้นอย่างง่ายจะเพียงพอและสำหรับช่วงเวลาภายนอกที่เป็นการแก้ไขที่ง่ายเช่นกัน
Christian Witts

1
ในขณะที่การแปลมีประสิทธิภาพมากกว่า regexp เล็กน้อยเวลานั้นน่าจะแคระถ้าคุณลองเปิดไฟล์ซึ่งไม่ต้องสงสัยเลยว่าคุณตั้งใจจะทำ ดังนั้นฉันชอบมากกว่าการแก้ปัญหา regexp อ่านได้มากขึ้นกว่าที่ระเบียบดังกล่าวข้างต้น
nosatalian

ฉันยังกังวลเกี่ยวกับบัญชีดำ ได้รับมันเป็นบัญชีดำที่อยู่ในรายการที่อนุญาต แต่ยัง ดูเหมือนปลอดภัยน้อยกว่า คุณจะรู้ได้อย่างไรว่า "allchars" สมบูรณ์จริง ๆ ?
isaaclw

@isaaclw: '.translate ()' ยอมรับสตริง 256- ถ่านเป็นตารางการแปล (การแปลแบบไบต์ต่อไบต์) '.maketrans ()' สร้างสตริงดังกล่าว ครอบคลุมค่าทั้งหมด; มันเป็นวิธีการที่อนุญาตอย่างบริสุทธิ์
jfs

แล้วชื่อไฟล์ '.' (จุดเดียว) ที่จะไม่ทำงานบน Unixes เป็นไดเรกทอรีปัจจุบันใช้ชื่อนั้น
ฟินน์Årup Nielsen

6

แม้ว่าคุณจะต้องระมัดระวัง มันไม่ได้กล่าวไว้อย่างชัดเจนในคำนำของคุณหากคุณกำลังดูเฉพาะภาษาลาติน คำบางคำอาจไม่มีความหมายหรือมีความหมายอื่นหากคุณทำให้มีความปลอดภัยด้วยอักขระ ascii เท่านั้น

ลองจินตนาการว่าคุณมี "forêtpoésie" (กวีนิพนธ์ป่าไม้) การฆ่าเชื้อโรคของคุณอาจทำให้ "ป้อม-posie" (แรง + บางอย่างไร้ความหมาย)

เลวร้ายยิ่งถ้าคุณต้องจัดการกับตัวอักษรจีน

"下北沢" ระบบของคุณอาจลงเอยด้วยการทำ "---" ซึ่งเป็นอีกครั้งที่จะล้มเหลวหลังจากที่ในขณะที่และไม่เป็นประโยชน์มาก ดังนั้นหากคุณจัดการกับไฟล์เท่านั้นฉันจะแนะนำให้เรียกพวกเขาโซ่ทั่วไปที่คุณควบคุมหรือเพื่อให้ตัวละครตามที่เป็นอยู่ สำหรับ URIs ก็เหมือนกัน


6

ทำไมไม่ห่อ "osopen" ด้วยการลอง / ยกเว้นและปล่อยให้ระบบปฏิบัติการพื้นฐานเรียงลำดับว่าไฟล์นั้นถูกต้องหรือไม่

ดูเหมือนว่าจะทำงานได้น้อยกว่ามากและใช้ได้ในทุกระบบปฏิบัติการที่คุณใช้


5
ชื่อนี้ใช้ได้จริงหรือไม่? ฉันหมายความว่าถ้าระบบปฏิบัติการไม่มีความสุขคุณก็ต้องทำอะไรใช่ไหม?
jeromej

1
ในบางกรณีระบบปฏิบัติการ / ภาษาอาจลบชื่อไฟล์ของคุณในรูปแบบอื่น แต่เมื่อคุณทำรายชื่อไดเรกทอรีคุณจะได้รับชื่อที่แตกต่างออกไป และสิ่งนี้สามารถนำไปสู่ ​​"เมื่อฉันเขียนไฟล์ที่นั่น แต่เมื่อฉันมองหาไฟล์มันเรียกว่าอย่างอื่น" ปัญหา (ฉันกำลังพูดถึงพฤติกรรมที่ฉันเคยได้ยินเกี่ยวกับ VAX ... )
Kent Fredric

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

5

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

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

หากคุณต้องการและคุณต้องอนุญาตช่องว่างและ '.' สำหรับนามสกุลไฟล์เป็นส่วนหนึ่งของชื่อลองอะไรเช่น:

import re
badchars= re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$')
badnames= re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)')

def makeName(s):
    name= badchars.sub('_', s)
    if badnames.match(name):
        name= '_'+name
    return name

แม้จะไม่สามารถรับประกันได้อย่างถูกต้องโดยเฉพาะในระบบปฏิบัติการที่ไม่คาดคิดเช่น RISC OS เกลียดช่องว่างและใช้ '.' เป็นตัวคั่นไดเรกทอรี


4

ฉันชอบวิธีการแบบ pug-slugify ที่นี่ แต่มันก็เป็นการลอกจุดออกไปซึ่งไม่ต้องการ ดังนั้นฉันจึงปรับมันสำหรับการอัปโหลดชื่อไฟล์ที่สะอาดไปยัง s3 ด้วยวิธีนี้:

pip install python-slugify

รหัสตัวอย่าง:

s = 'Very / Unsafe / file\nname hähä \n\r .txt'
clean_basename = slugify(os.path.splitext(s)[0])
clean_extension = slugify(os.path.splitext(s)[1][1:])
if clean_extension:
    clean_filename = '{}.{}'.format(clean_basename, clean_extension)
elif clean_basename:
    clean_filename = clean_basename
else:
    clean_filename = 'none' # only unclean characters

เอาท์พุท:

>>> clean_filename
'very-unsafe-file-name-haha.txt'

นี่เป็นสิ่งที่ไม่ปลอดภัยจึงทำงานกับชื่อไฟล์ที่ไม่มีนามสกุลและใช้ได้กับชื่อไฟล์อักขระที่ไม่ปลอดภัยเท่านั้น (ผลลัพธ์อยู่noneที่นี่)


1
ฉันชอบสิ่งนี้อย่าสร้างวงล้อใหม่อย่านำเข้ากรอบงาน Django ทั้งหมดหากคุณไม่ต้องการไม่วางโค้ดโดยตรงถ้าคุณจะไม่ดูแลมันในอนาคตและพยายามสร้างสตริง เพื่อจับคู่ตัวอักษรที่คล้ายกันกับตัวอักษรที่ปลอดภัยดังนั้นการอ่านสตริงใหม่จึงง่ายขึ้น
vicenteherrera

1
หากต้องการใช้เครื่องหมายขีดล่างแทนเครื่องหมายขีด: name = slugify (s, separator = '_')
vicenteherrera

3

คำตอบถูกแก้ไขสำหรับ python 3.6

import string
import unicodedata

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(chr(c) for c in cleanedFilename if chr(c) in validFilenameChars)

คุณช่วยอธิบายรายละเอียดคำตอบของคุณได้ไหม?
เซเรนิตี้

มันเป็นคำตอบเดียวกันกับที่ Sophie Gage ยอมรับ แต่มันได้รับการดัดแปลงให้ทำงานกับ python 3.6
Jean-Robin Tremblay

2

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

def normalizefilename(fn):
    validchars = "-_.() "
    out = ""
    for c in fn:
      if str.isalpha(c) or str.isdigit(c) or (c in validchars):
        out += c
      else:
        out += "_"
    return out    

หากคุณต้องการคุณสามารถเพิ่มตัวอักษรที่ถูกต้องของคุณลงใน validcharsตัวแปรที่จุดเริ่มต้นเช่นตัวอักษรประจำชาติของคุณที่ไม่มีอยู่ในตัวอักษรภาษาอังกฤษ นี่คือสิ่งที่คุณอาจต้องการหรือไม่ต้องการ: ระบบไฟล์บางระบบที่ไม่ทำงานบน UTF-8 อาจยังมีปัญหากับตัวอักษรที่ไม่ใช่ ASCII

ฟังก์ชั่นนี้เป็นการทดสอบความถูกต้องของชื่อไฟล์เดียวดังนั้นมันจะแทนที่ตัวคั่นพา ธ ด้วย _ ซึ่งพิจารณาว่าเป็นตัวอักษรที่ไม่ถูกต้อง ถ้าคุณต้องการที่จะเพิ่มมันเป็นเรื่องเล็กน้อยที่จะปรับเปลี่ยนifเพื่อรวมตัวคั่นเส้นทางของระบบปฏิบัติการ


1

โซลูชันเหล่านี้ส่วนใหญ่ไม่ทำงาน

'/ hello / world' -> 'helloworld'

'/ helloworld' / -> 'helloworld'

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

ฉันดองเผด็จการเช่น:

{'helloworld': 
    (
    {'/hello/world': 'helloworld', '/helloworld/': 'helloworld1'},
    2)
    }

2 หมายถึงหมายเลขที่ควรต่อท้ายชื่อไฟล์ถัดไป

ฉันค้นหาชื่อไฟล์ทุกครั้งจาก dict หากไม่มีฉันสร้างใหม่ใหม่ต่อท้ายจำนวนสูงสุดหากจำเป็น


โปรดทราบหากใช้ helloworld1 คุณต้องตรวจสอบ helloworld1 ไม่ได้ใช้งานเป็นต้น ..
เบิร์ตคิง

1

ไม่ว่า OP ขออะไร แต่นี่คือสิ่งที่ฉันใช้เพราะฉันต้องการการแปลงที่ไม่ซ้ำใครและย้อนกลับได้:

# p3 code
def safePath (url):
    return ''.join(map(lambda ch: chr(ch) if ch in safePath.chars else '%%%02x' % ch, url.encode('utf-8')))
safePath.chars = set(map(lambda x: ord(x), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-_ .'))

ผลลัพธ์คือ "ค่อนข้าง" สามารถอ่านได้อย่างน้อยจากมุมมองดูแลระบบ


เสื้อคลุมสำหรับสิ่งนี้โดยไม่มีช่องว่างในชื่อไฟล์:def safe_filename(filename): return safePath(filename.strip().replace(' ','_'))
SpeedCoder5

1

หากคุณไม่คิดที่จะติดตั้งแพ็คเกจสิ่งนี้ควรมีประโยชน์: https://pypi.org/project/pathvalidate/

จากhttps://pypi.org/project/pathvalidate/#sanitize-a-filename :

from pathvalidate import sanitize_filename

fname = "fi:l*e/p\"a?t>h|.t<xt"
print(f"{fname} -> {sanitize_filename(fname)}\n")
fname = "\0_a*b:c<d>e%f/(g)h+i_0.txt"
print(f"{fname} -> {sanitize_filename(fname)}\n")

เอาท์พุต

fi:l*e/p"a?t>h|.t<xt -> filepath.txt
_a*b:c<d>e%f/(g)h+i_0.txt -> _abcde%f(g)h+i_0.txt

0

ฉันแน่ใจว่านี่ไม่ใช่คำตอบที่ดีเนื่องจากจะแก้ไขสตริงที่วนลูปมากกว่า แต่ดูเหมือนจะใช้ได้ดี:

import string
for chr in your_string:
 if chr == ' ':
   your_string = your_string.replace(' ', '_')
 elif chr not in string.ascii_letters or chr not in string.digits:
    your_string = your_string.replace(chr, '')

ฉันได้พบสิ่งนี้"".join( x for x in s if (x.isalnum() or x in "._- "))ในความคิดเห็นโพสต์นี้
SergioAraujo

0

UPDATE

ลิงก์ทั้งหมดแตกเกินกว่าจะซ่อมในคำตอบอายุ 6 ปี

นอกจากนี้ฉันจะไม่ทำเช่นนี้อีกต่อไปเพียงbase64เข้ารหัสหรือวางตัวอักษรที่ไม่ปลอดภัย Python 3 ตัวอย่าง:

import re
t = re.compile("[a-zA-Z0-9.,_-]")
unsafe = "abc∂éåß®∆˚˙©¬ñ√ƒµ©∆∫ø"
safe = [ch for ch in unsafe if t.match(ch)]
# => 'abc'

ด้วยbase64คุณสามารถเข้ารหัสและการถอดรหัสเพื่อให้คุณสามารถเรียกดูชื่อไฟล์เดิมอีกครั้ง

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

from random import choice
from string import ascii_lowercase, ascii_uppercase, digits
allowed_chr = ascii_lowercase + ascii_uppercase + digits

safe = ''.join([choice(allowed_chr) for _ in range(16)])
# => 'CYQ4JDKE9JfcRzAZ'

LINKROTTEN คำตอบเดิม :

bobcatโครงการประกอบด้วยโมดูลหลามที่ไม่เพียงแค่นี้

มันไม่สมบูรณ์อย่างสมบูรณ์ดูโพสต์นี้และคำตอบนี้นี้

ดังนั้นตามที่ระบุไว้: การbase64เข้ารหัสอาจเป็นความคิดที่ดีกว่าถ้าการอ่านไม่สำคัญ


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