ไพธอนมีฟังก์ชันมากมายที่เกี่ยวกับการจัดการไฟล์ ส่วนใหญ่จะอยู่ในมอดูลชื่อ
os
ซึ่งเป็นมอดูลที่มีการใช้งานที่หลากหลาย และมีบางส่วนที่อยู่ในมอดูล
shutil
ทั้ง ๒ มอดูลนี้ต่างก็มีอยู่แล้วในไพธอนโดยไม่ต้องติดตั้งเพิ่ม
เนื่องจากคำสั่งภายใน ๒ มอดูลนี้มีความเกี่ยวข้องกันมาก จึงขอสรุปเกี่ยวกับพวกฟังก์ชันต่างๆที่ใช้จัดการไฟล์จาก ๒
มอดูลนี้ลงในบทความนี้ทีเดียวเลย
อย่างไรก็ตาม มอดูล os นั้นแบ่งเป็นหลายส่วน คำสั่งถูกใช้งานได้ในหลายด้าน เช่นมีมอดูลย่อย os.path
ซึ่งใช้ในการจัดการเรื่องพาธและข้อมูลไฟล์ ซึ่งได้เขียนถึงไปใน
https://phyblas.hinaboshi.com/20200304
shutil เองก็ยังมีอีกหลายฟังก์ชันซึ่งไม่ได้เกี่ยวกับการจัดการไฟล์ ในที่นี้ก็จะไม่ได้พูดถึง
คำสั่งส่วนใหญ่นั้นจริงๆแล้วมีความใกล้เคียงกับคำสั่งที่สั่งในคอมมานด์ไลน์ ซึ่งถ้าใช้มอดูล subprocess (ดูใน
https://phyblas.hinaboshi.com/20200306)
เพื่อสั่งคอมมานด์ไลน์ก็สามารถทำได้
แต่ถ้าคำสั่งไหนใช้ฟังก์ชันที่มีอยู่แล้วในไพธอนได้ก็ย่อมสะดวกกว่า
หากไม่ใช่งานที่ระดับสูงมากนักแค่ใช้ฟังก์ชันต่างๆที่มีอยู่ในมอดูล os และ shutil ก็จัดการได้โดยง่าย
ขอแบ่งอธิบายเป็นกลุ่มๆ ตามการใช้งาน ดูชื่อฟังก์ชันทั้งหมดได้ที่สารบัญข้างบน
การสร้างโฟลเดอร์ (os.mkdir, os.makedirs)
คำสั่งสำหรับสร้างโฟลเดอร์ขึ้นมาใหม่มีอยู่ ๒ ตัวคือ os.mkdir() และ os.makedirs() วิธีการใช้คล้ายกัน คือใส่ชื่อโฟลเดอร์ที่ต้องการสร้าง เช่น
import os
os.mkdir('aki') # สร้างโฟลเดอร์ชื่อ aki
os.makedirs('aya') # สร้างโฟลเดอร์ชื่อ aya
ในกรณีแบบนี้จะใช้อันไหนก็ไม่ต่างกัน แต่ข้อแตกต่างจะเกิดขึ้นในกรณีที่สร้างโฟลเดอร์ซ้อนภายในโฟลเดอร์ที่ไม่มีมาก่อน
ถ้าเป็น os.mkdir() จะเกิดข้อผิดพลาดขึ้น แต่ os.makedirs() จะสร้างได้
os.mkdir('ama/saki') # FileNotFoundError: [Errno 2] No such file or directory: 'ama/saki'
os.makedirs('ama/saki') # สร้างโฟลเดอร์ชื่อ ama แล้วจึงสร้างโฟลเดอร์ชื่อ saki ในนั้น
ถ้าหากโฟลเดอร์ที่สร้างมีตัวตนอยู่แล้ว จะเกิดข้อผิดพลาดขึ้น ไม่ว่าจะใช้คำสั่งไหน
os.mkdir('aki') # ได้ FileExistsError: [Errno 17] File exists: 'aki'
os.makedirs('aya') # ได้ FileExistsError: [Errno 17] File exists: 'aya'
แต่สำหรับ os.makedirs สามารถใส่ exist_ok=True เพื่อให้ไม่เกิดอะไรขึ้นเมื่อโฟลเดอร์มีอยู่แล้วได้
os.makedirs('aya',exist_ok=True) # ไม่เกิดอะไรขึ้น
การย้ายหรือเปลี่ยนชื่อไฟล์ (os.rename, os.renames, os.replace, shutil.move)
การย้ายไฟล์หรือเปลี่ยนชื่อไฟล์นั้นที่จริงแล้วก็เป็นเรื่องเดียวกัน มีหลายคำสั่งที่ใช้ในการเปลี่ยนชื่อหรือย้าย ได้แก่
os.rename(), os.renames(), os.replace() และ shutil.move()
ถ้าใช้กับไฟล์หรือโฟลเดอร์ที่มีอยู่แล้ว ไม่ว่าจะใช้อันไหนก็ให้ผลไม่ต่างกัน เป็นการเปลี่ยนชื่อไฟล์หรือโฟลเดอร์นั้น
import os,shutil
os.rename('aki','aka') # เปลี่ยนชื่อ aki เป็น aka
os.renames('ama','ami') # เปลี่ยนชื่อ ama เป็น ami
os.replace('aya','maya') # เปลี่ยนชื่อ aya เป็น maya
shutil.move('maya','saya') # เปลี่ยนชื่อ maya เป็น saya
แต่ในกรณีที่ปลายทางเป็นโฟลเดอร์ที่มีอยู่แล้วและเป็นโฟลเดอร์ที่ไม่ว่างเปล่า แบบนี้ os.rename(), os.renames(), os.replace() จะเกิดข้อผิดพลาดขึ้น ในขณะที่ shutil.move() จะเป็นการย้ายเข้าโฟลเดอร์นั้น
os.rename('aka','ami') # OSError: [Errno 66] Directory not empty: 'aka' -> 'ami'
os.renames('aka','ami') # OSError: [Errno 66] Directory not empty: 'aka' -> 'ami'
os.replace('aka','ami') # OSError: [Errno 66] Directory not empty: 'aka' -> 'ami'
shutil.move('aka','ami') # ย้าย aka ไปไว้ใน ami
เพียงแต่หากไฟล์ที่ต้องการย้ายนั้นเป็นโฟลเดอร์ และย้ายไปโฟลเดอร์ปลายทางซึ่งพอดีเป็นโฟลเดอร์เปล่า แบบนี้ os.rename(), os.renames(), os.replace() จะกลายเป็นการย้ายโฟลเดอร์ต้นทางไปทับโฟลเดอร์นั้นแทน
กรณีที่ปลายทางอยู่ในโฟลเดอร์ที่ไม่มีตัวตนอยู่ os.rename() กับ os.replace() จะเกิดข้อผิดพลาด ส่วน os.renames() กับ
shutil.move() จะสร้างโฟลเดอร์ใหม่ขึ้นมาตามเส้นทาง
os.rename('aka','hana/saka') # FileNotFoundError: [Errno 2] No such file or directory: 'aka' -> 'hana/saka'
os.replace('aka','hana/saka') # FileNotFoundError: [Errno 2] No such file or directory: 'aka' -> 'hana/saka'
os.renames('aka','hana/saka') # aka ย้ายไปอยู่ใน hana และเปลี่ยนชื่อเป็น saka
# shutil.move('aka','hana/saka') ก็ให้ผลเหมือน os.renames()
กรณีที่ย้ายไฟล์ออกจากโฟลเดอร์แล้วโฟลเดอร์นั้นไม่เหลือไฟล์อื่น หากใช้ os.renames() จะทำการลบโฟลเดอร์นั้นทิ้งโดยอัตโนมัติ
ส่วนตัวอื่นจะแค่ย้ายไปที่ใหม่
os.renames('hana/saka','saya/suki')
# saka ย้ายไปอยู่ใน saya และเปลี่ยนชื่อเป็น suki ส่วน โฟลเดอร์ hana ถูกลบทิ้ง
การคัดลอกไฟล์ (shutil.copy, shutil.copy2, shutil.copyfile, shutil.copytree,
shutil.copystat)
ฟังก์ชันสำหรับทำการคัดลอกอยู่ในมอดูล shutil ทั้งหมด
shutil.copy(), shutil.copy2(), shutil.copyfile() ใช้คัดลอกไฟล์ธรรมดาที่ไม่ใช่โฟลเดอร์
ถ้าใช้กับโฟลเดอร์จะเกิดข้อผิดพลาด
ข้อแตกต่างคือ shutil.copy() กับ shutil.copyfile() จะแค่คัดลอกเนื้อหาจากไฟล์เดิมไปสร้างไฟล์ให้มีเนื้อหาเหมือนเดิม
แต่พวกข้อมูลสถานะเช่นพวกเวลาสร้าง เวลาแก้ไข จะไม่ได้คัดลอกมาด้วย แต่จะเหมือนสร้างใหม่
ตัวอย่างเช่น มีไฟล์นึงชื่อ sasori.txt ลองคัดลอกไปเป็นไฟล์อีกชื่อ
shutil.copy('sasori.txt','sasuru.txt') # คัดลอกแค่เนื้อหาไฟล์
shutil.copy2('sasori.txt','sosoru.txt') # คัดลอกทั้งเนื้อหาไฟล์และสถานะ
shutil.copyfile('sasori.txt','susuri.txt') # คัดลอกแค่เนื้อหาไฟล์
ส่วน shutil.copytree() จะใช้คัดลอกโฟลเดอร์ พร้อมทั้งเนื้อหาที่อยู่ในโฟลเดอร์ทั้งหมด
ถ้าปลายทางอยู่ในโฟลเดอร์ที่ยังไม่มีตัวตนอยู่ก็จะถูกสร้างใหม่
shutil.copytree('ami','hama') # ตัดลอกโฟลเดอร์และเนื้อใน ami ไปยัง hama
shutil.copytree('ami','asa/nagi') # โฟลเดอร์ asa จะถูกสร้างขึ้นถ้าไม่มีอยู่ แล้วจึงคัดลอกข้อมูลจาก ami ไป nagi ใน asa
ปกติแล้วการคัดลอกจะให้ผลเหมือยน shutil.copy2() คือคัดลอกทั้งเนื้อหาไฟล์และข้อมูลประกอบไฟล์ แต่หากต้องการทำให้คัดลอกแบบ
shutil.copy ก็ทำได้โดยใส่ copy_function=shutil.copy
shutil.copytree('ami','nami',copy_function=shutil.copy)
ถ้าโฟลเดอร์ปลายทางมีอยู่แล้วก็จะเกิดข้อผิดพลาดขึ้น แต่ในไพธอน 3.8 สามารถใส่ dirs_exist_ok=True
เพื่อจะทำให้สามารถคัดลอกได้แม้จะมีโฟลเดอร์เดิมอยู่ โดยเนื้อหาจากต้นฉบับจะถูกคัดลอกเข้าไปยังโฟลเดอร์ปลายทางนั้น
ส่วนเนื้อหาในโฟลเดอร์ปลายทางที่มีอยู่เดิมก็ไม่ได้หายไปไหน
shutil.copytree('ami','nami',dirs_exist_ok=True)
ส่วน shutil.copystat() มีไว้คัดลอกสถานะของไฟล์หนึ่งไปยังอีกไฟล์ที่มีอยู่แล้ว โดยไม่ได้เปลี่ยนเนื้อหาข้างใน
shutil.copystat('sasori.txt','sasuru.txt')
สร้างลิงก์หรือชอร์ตคัต (os.link, os.symlink)
ฟังก์ชัน os.link() มีไว้สร้างฮาร์ดลิงก์ ส่วน os.symlink() ไว้ใช้สร้างซิมโบลิกลิงก์
os.link('sasori.txt','sasoru.txt') # สร้างฮาร์ดลิงก์ของ sasori.txt ชื่อ sasoru.txt
os.symlink('sasori.txt','sasara.txt') # สร้างซิมโบลิกลิงก์ของ sasori.txt ชื่อ sasara.txt
เกี่ยวกับความแตกต่างระหว่างฮาร์ดลิงก์กับซิมโบลิกลิงก์ได้เขียนถึงไว้ใน
https://phyblas.hinaboshi.com/20200303
การลบไฟล์หรือโฟลเดอร์ (os.remove, os.unlink, os.rmdir, os.removedirs,
shutil.rmtree)
ฟังก์ชันที่ใช้ในการลบไฟล์คือ os.remove() และ os.unlink() ใช้อันไหนก็เหมือนกัน
os.remove('sasara.txt') # ลบ sasara.txt
os.rmdir() กับ os.removedirs() มีไว้ใช้ลบโฟลเดอร์เปล่า แต่ถ้าใช้กับโฟลเดอร์ที่มีอะไรอยู่จะเกิดข้อผิดพลาด
os.rmdir('hama/saki') # ลบโฟลเดอร์ saka ในโฟลเดอร์ hama แต่ถ้าใน saka ไม่ได้ว่างเปล่าจะเกิดข้อผิดพลาด
หากต้องการลบโฟลเดอร์ที่มีอะไรอยู่พร้อมกับไฟล์ในนั้นทั้งหมดให้ใช้ shutil.rmtree
shutil.rmtree('asa') # ลบโฟลเดอร์ asa และไฟล์ที่อยู่ข้างในทั้งหมด
โฟลเดอร์ที่ทำงาน (os.chdir, os.getcwd)
os.getcwd() ใช้หาพาธของโฟลเดอร์ที่ทำงานอยู่ปัจจุบัน และหากต้องการย้ายโฟลเดอร์ก็ใช้ os.chdir()
ตัวอย่าง
os.chdir('/') # ย้ายโฟลเดอร์ทำงานไปที่ /
os.getcwd() # ได้ /
ดูสถานะไฟล์ (os.stat, os.lstat)
os.stat ใช้สำหรับดูข้อมูลสถานะของไฟล์
stat = os.stat('sasori.txt')
print(stat) # ได้ os.stat_result(st_mode=33188, st_ino=14819342, st_dev=16777223, st_nlink=3, st_uid=501, st_gid=20, st_size=5, st_atime=1584553332, st_mtime=1584549830, st_ctime=1584553331)
print(stat.st_mode) # ได้ 33188
st_atime, st_ctime และ st_mtime เป็นข้อมูลเวลา ถูกแสดงในรูปตัวเลขวินาที timestamp อาจใช้
datetime.datetime.fromtimestamp() เพื่อเปลี่ยนเป็นวันที่ที่อ่านได้ (รายละเอียดการใช้ดูได้ใน
https://phyblas.hinaboshi.com/20160621)
import datetime
print(datetime.datetime.fromtimestamp(stat.st_atime)) # ได้ 2020-03-19 01:42:12.758729
print(datetime.datetime.fromtimestamp(stat.st_ctime)) # ได้ 2020-03-19 01:42:11.705659
print(datetime.datetime.fromtimestamp(stat.st_mtime)) # ได้ 2020-03-19 00:43:50.621884
os.lstat() จะเหมือนกับ os.stat() แต่ต่างกันตรงที่ถ้าหากใช้กับไฟล์ที่เป็นซิมโบลิกลิงก์
จะตามไปหาข้อมูลไฟล์ต้นทางแทน
เช่นสมมุติว่า sasara.txt เป็นซิมโบลิกลิงก์ของไฟล์ sasori.txt เมื่อครู่นี้ ก็จะได้ว่าผลของ os.stat() กับ os.fstat()
ต่างกัน
stat = os.stat('sasara.txt')
lstat = os.lstat('sasara.txt')
print(stat==lstat) # ได้ T False
print(stat) # ได้ os.stat_result(st_mode=33279, st_ino=14819342, st_dev=16777223, st_nlink=3, st_uid=501, st_gid=20, st_size=5, st_atime=1584595520, st_mtime=1584549830, st_ctime=1584595595)
print(lstat) # ได้ os.stat_result(st_mode=41453, st_ino=14851181, st_dev=16777223, st_nlink=1, st_uid=501, st_gid=20, st_size=10, st_atime=1584594424, st_mtime=1584594424, st_ctime=1584594424)
print(os.stat('sasori.txt')==os.lstat('sasori.txt')) # ได้ True
จัดการสิทธิไฟล์ (os.chmod, os.chown, os.chflags, os.lchmod, os.lchown,
os.lchflags)
os.chmod(), os.chown(), os.chflags() มีไว้สำหรับเปลี่ยนพวกสิทธิหรือสถานะต่างๆใน unix เทียบเท่ากับการใช้คำสั่ง chmod,
chown, chflags ในเชลยูนิกซ์ ในที่นี้จะไม่อธิบายละเอียด แต่มีเขียนถึงไว้ใน
https://phyblas.hinaboshi.com/20190126
ตัวอย่างการใช้
os.chflags('sasori.txt',0)
os.chmod('sasori.txt',0o777)
os.chown('sasori.txt',501,20)
ส่วน os.lchmod(), os.lchown(), os.lchflags() จะคล้ายกัน
แค่ต่างตรงที่ว่าถ้าเจอซิมโบลิกลิงก์จะไปจัดการที่ตัวต้นทางแทน
ดูไฟล์ที่มีอยู่ในโฟลเดอร์ (os.listdir, os.scandir)
ฟังก์ชัน os.listdir() จะแสดงลิสต์ของชื่อไฟล์ทั้งหมดในโฟลเดอร์ที่ระบุ
แต่ถ้าหากไม่ระบุโฟลเดอร์ก็จะแสดงไฟล์ของโฟลเดอร์ที่ทำงานอยู่ปัจจุบัน
ตัวอย่าง
os.listdir('kami') # ได้ ['nari.txt', 'washinda']
นอกจากนี้ยังมี os.scandir() จะดูไฟล์ต่างๆในโฟลเดอร์ทั้งหมด คล้ายกับ os.listdir()
แต่จะให้ผลออกมาเป็นอิเทอเรเตอร์ซึ่งสามารถนำมาดูข้อมูลของไฟล์ได้หลายอย่าง
วิธีการใช้มักใช้คู่กับ for เพื่อวนดูไฟล์ทีละตัว และใช้เสร็จก็ให้ใช้ .close() เพื่อปิดด้วย หรือจะใช้กับ with เพื่อจะได้ไม่ต้องมาปิดตอนท้ายก็ได้
fol = os.scandir('kami')
for f in fol:
print('=='+f.name+'==') # แสดงชื่อไฟล์
print('path: %s'%f.path) # แสดงพาธไฟล์
print('inode: %s'%f.inode()) # แสดงไอโหนด
print('file?: %s'%f.is_file()) # เป็นไฟล์ธรรมดาหรือเปล่า?
print('dir?: %s'%f.is_dir()) # เป็นโฟลเดอร์หรือเปล่า?
print('symlink?: %s'%f.is_symlink()) # เป็นซิมโบลิกลิงก์หรือเปล่า?
print('stat: %s'%(f.stat(),)) # แสดงข้อมูลสถานะ
fol.close()
ได้
==nari.txt==
path: kami/nari.txt
inode: 14849154
file?: True
dir?: False
symlink?: False
stat: os.stat_result(st_mode=33188, st_ino=14849154, st_dev=16777223, st_nlink=1, st_uid=501, st_gid=20, st_size=5, st_atime=1584592839, st_mtime=1584549830, st_ctime=1584592838)
==washinda==
path: kami/washinda
inode: 14849149
file?: False
dir?: True
symlink?: False
stat: os.stat_result(st_mode=16877, st_ino=14849149, st_dev=16777223, st_nlink=2, st_uid=501, st_gid=20, st_size=64, st_atime=1584592813, st_mtime=1584592813, st_ctime=1584592821)
ไล่จัดการโฟลเดอร์ไปทีละชั้น (os.walk)
os.walk() เป็นฟังก์ชันที่สะดวกเมื่อต้องการจะไล่จัดการกับไฟล์ที่อยู่ในโฟลเดอร์ที่ซ้อนอยู่ในโฟลเดอร์ย่อยเป็นชั้นๆ
ฟังก์ชันนี้เมื่อใช้ไปจะคืนค่าเป็นเจเนอเรเตอร์ที่มีข้อมูลของโฟลเดอร์ โดยจะให้ ๓ อย่างมาพร้อมกัน คือ
- [พาธโฟลเดอร์นั้น, ลิสต์ของโฟลเดอร์ข้างใน, ลิสต์ของไฟล์ข้างใน]
จากนั้นหากในนั้นมีโฟลเดอร์อยู่ ในรอบต่อๆไปของ for ก็จะไปวนดูโฟลเดอร์ด้านในนั้น และให้ค่า ๓ อย่างเหมือนเดิม
เป็นอย่างนี้ซ้ำไปเรื่อยๆจนหมดทุกโฟลเดอร์ย่อยด้านใน
ตัวอย่างการใช้ เช่น สมมุติมีโฟลเดอร์ที่ข้างในมีโฟลเดอร์ย่อยและไฟล์แบบนี้
เมื่อลองใช้ os.walk() เพื่อให้ไล่ดูไฟล์และโฟลเดอร์ข้างในไปทีละชั้น
import os
i = 0
for path,lis_fol,lis_fai in os.walk('sa'):
i += 1
print('%d. ในโฟลเดอร์ '%i+path)
print('มีโฟลเดอร์ %d โฟลเดอร์'%len(lis_fol))
for fol in lis_fol:
print('- '+os.path.join(path,fol))
print('มีไฟล์ %d ไฟล์'%len(lis_fai))
for fai in lis_fai:
print('- '+os.path.join(path,fai))
ก็จะไล่ออกมาได้ผลแบบนี้
1. ในโฟลเดอร์ sa
มีโฟลเดอร์ 2 โฟลเดอร์
- sa/ga
- sa/ka
มีไฟล์ 0 ไฟล์
2. ในโฟลเดอร์ sa/ga
มีโฟลเดอร์ 1 โฟลเดอร์
- sa/ga/shi
มีไฟล์ 2 ไฟล์
- sa/ga/mi
- sa/ga/su
3. ในโฟลเดอร์ sa/ga/shi
มีโฟลเดอร์ 2 โฟลเดอร์
- sa/ga/shi/ma
- sa/ga/shi/te
มีไฟล์ 1 ไฟล์
- sa/ga/shi/ta
4. ในโฟลเดอร์ sa/ga/shi/ma
มีโฟลเดอร์ 1 โฟลเดอร์
- sa/ga/shi/ma/su
มีไฟล์ 0 ไฟล์
5. ในโฟลเดอร์ sa/ga/shi/ma/su
มีโฟลเดอร์ 0 โฟลเดอร์
มีไฟล์ 0 ไฟล์
6. ในโฟลเดอร์ sa/ga/shi/te
มีโฟลเดอร์ 0 โฟลเดอร์
มีไฟล์ 0 ไฟล์
7. ในโฟลเดอร์ sa/ka
มีโฟลเดอร์ 1 โฟลเดอร์
- sa/ka/mi
มีไฟล์ 1 ไฟล์
- sa/ka/na
8. ในโฟลเดอร์ sa/ka/mi
มีโฟลเดอร์ 0 โฟลเดอร์
มีไฟล์ 1 ไฟล์
- sa/ka/mi/chi
เนื่องจากมีทั้งหมด ๘ โฟลเดอร์ (รวมตัวโฟลเดอร์ที่เริ่มต้นด้วย) ดังนั้นจึงมีการวน ๘ ครั้ง
แต่ละครั้งจะบอกเนื้อหาว่าข้างในมีโฟลเดอร์อะไรอยู่บ้าง โดยไล่จากนอกสุดเข้ามา
อ้างอิง