由於目前工作之餘,正使用 Python 寫 Side-Project,在過程中,針對檔案與目錄相關的寫法總是反覆查了又查,於是想說抽空寫個相關的 Cheat Sheet 好讓自己快速查閱,但又覺得可能網友們也會遇到同樣的問題,倒不如整理完整一點再分享出來,若是有錯誤也歡迎各位指教,該篇是針對 Python 3 檢查檔案及目錄是否存在 作筆記。
前言
站長平常幾乎沒再寫程式的,除了偶爾爬爬蟲,但該次的主題是從寫 Python 一直以來都有遇到的問題,常常在處理檔案的時候被路徑搞瘋,「工作目錄」與「執行路」基本上是分開的,工作目錄會依照使用者所在目錄而有不同,而執行目錄則是取決於檔案的位置,該篇文章也針對 Python 3 檢查檔案及目錄是否存在 準備了一個簡單的 Lab,是說很久沒寫 Python 相關的文章了,除了沒下文的 跌 入 數 據 分 析 的 坑 系列以外還有一篇談 C l o u d F i r e s t o r e 新增查刪,實作該篇文章大致上可以學習到以下技能。
- 檢查檔案是否存在
- 檢查目錄是否存在
- 檔案存在,自動更名並建立(隨機、序列)
- 檔案不存在,自動建立
- 目錄不存在,自動建立
- 多層目錄建立
補充
自 Python 3.5 以上的版本新增 pathlib 函式庫,可以輕鬆解決檔案重複、存不存在的問題,該文章以實驗精神土炮的方式實作,有興趣參考之。
測試環境建置 (Linux with Python3)
環境使用 Python3.6 與 Windows 的 WLS 下的 Ubuntu,建置 Lab 環境,只要在 Linux 環境底下,複製以下內容並貼上並戳一下 Enter 即可。
cd ~/
mkdir demo && cd demo
mkdir dir1 && mkdir dir2
touch dir1/file1.txt
echo "import os\nbase_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)))\nPrint(f'User_Path: {os.getcwd()}')\nprint(f'File_path: {base_dir}')" > dir2/demo.py
python3
取得當前目錄
使用者所在目錄
import os
os.getcwd() # /home/{username}/dome
程式所在目錄
在環境建置的時候有透過指令在 dir2
底下建置一個由 Python3 撰寫的腳本,其程式碼如下,如果有複製貼上環境建置的 bash 腳本,檔案應該是已經建好的狀態。
# /home/mksyi/demo/dir2/demo.py
import os
base_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)))
print(f'User_Path: {os.getcwd()}')
print(f'File_Path: {base_dir}')
由於使用檔案腳本,若是在 Python Shell 底下,必須先使用 quit()
退出 Python Shell,並且在 OS Shell 下執行才行,以下是執行過程與結果。
➜ pwd [1:32:10]
/home/mksyi/demo
➜ python3 dir2/demo.py [1:32:12]
User_path: /home/mksyi/demo
File_path: /home/mksyi/demo/dir2
目錄切換
import os
os.chdir("dir1")
os.getcwd() # /home/{username}/demo/dir1
檔案判斷
import os
os.path.isfile("file2.txt") # False
不存在自動創建檔案
import os
filename = "file2.txt"
os.path.isfile(filename) # False
if not os.path.isfile(filename):
f = open(filename, "a")
f.write("Hello World\n")
f.close()
os.path.isfile(filename) # True
對於 open
的第二個代表的是模式,可以參考下方補充資訊。
若是要創建空檔案,只要把 write()
拿掉即可,如下方範例。
import os
filename = "file3.txt"
os.path.isfile(filename) # False
if not os.path.isfile(filename):
f = open(filename, "a")
f.close()
os.path.isfile(filename) # True
若檔案存在,自動更名並創建。
import os
import random, string
def get_newname(o_filename):
"""
filename file3.txt => file3
extension file3.txt => .txt
new_filename file3-[a-z]{5}.txt
"""
size = 5
filename = os.path.splitext(o_filename)[0]
extension = os.path.splitext(o_filename)[1]
random_string = ''.join(random.choice(string.ascii_letters) for x in range(5))
new_filename = f'{filename}-{random_string}{extension}'
return new_filename
filename = "file3.txt"
if os.path.isfile(filename):
filename = get_newname(filename)
f = open(filename, "ab")
f.close()
該隨機方法參考 [4],當然隨機是一個做法,檔案命名的方式可以依照需求自訂,若以寫 Log 的形式,可以參照每天日期創建新增檔案,又或者可以採用流水號的方式,當 file1 存在嘗試建立 file2 file3 … fileN,缺點是當檔案數量龐大時,檢查的機制會消耗掉些許效能。
目錄判斷
import os
os.path.isdir("dir3") # False
目錄不存在自動創建
import os
>>> os.getcwd() # /home/{username}/demo
dirname = "dir3"
if not os.path.isdir(dirname)
os.mkdir(dirname)
創建多層目錄
創建目錄常遇到的問題 「目錄已經存在」 、「找不到檔案或目錄」。
import os
>>> os.getcwd() # /home/{username}/demo
>>> os.mkdir('dir3')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileExistsError: [Errno 17] File exists: 'dir3'
>>> os.mkdir('dir3/dirA/dirA1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'dir3/dirA/dirA1'
可以使用 os.makedirs()
來一次創建多層目錄。
import os
os.makedirs('dir3/dirA/dirA1')
os.chdir("dir3/dirA/dirA1")
os.getcwd() # /home/{username}/demo/dir3/dirA/dirA1
但仍然要注意,不論是使用 mkdir()
還是 makedirs()
方法都會產生 FileNotFoundError
錯誤訊息,最好使用之前多一個判斷檢查是否已經存在。
import os
os.getcwd() # /home/{username}/demo
dirname = "dir4/dirA/dirA1"
os.path.isdir(dirname) # False
if not os.path.isdir(dirname):
os.makedirs(dirname)
os.path.isdir(dirname) # True
常見例子
很多時候是依照檔案的位置建立檔案或目錄,舉個簡單的例子,有一隻爬蟲程式放在 /home/{username}/demo/crawler/
底下,並且該爬蟲程式會將爬下的資料來放在 demo/crawler/Download
資料夾下,若是使用者在 /home/{username}/
路徑下執行該爬蟲程式,則目錄會被創建在 /home/{username}/Download
,這並不是我們要的,所以要解決該問題,會用到上面的例子提到的 「取得程式所在目錄」 功能,並且針對重複的檔案建立流水號。
# /home/{username}/demo/crawler/crawler.py
import os
def get_dirpath(dir_path):
dir_path = os.path.join(
os.path.dirname(
os.path.realpath(__file__)), dir_path
)
if not os.path.isdir(dir_path):
os.makedirs(dir_path)
return dir_path
def get_newname(o_filename, sn= 1):
"""
filename /home/{username}/demo/crawler/download/mks_data.txt
=> /home/{username}/demo/crawler/download/mks_data
extension /home/{username}/demo/crawler/download/mks_data.txt
=> .txt
"""
if os.path.isfile(o_filename):
filename = os.path.splitext(o_filename)[0]
extension = os.path.splitext(o_filename)[1]
new_filename = f'{filename}-{str(sn)}{extension}'
if os.path.isfile(new_filename):
new_filename = get_newname(o_filename, sn + 1)
else:
new_filename = o_filename
return new_filename
if __name__ == '__main__':
filename = "mks_data.txt"
dir_path = 'download'
dir_path = get_dirpath(dir_path)
filename = f'{dir_path}/{get_newname(filename)}'
for n in range(1,5 + 1):
new_filename = get_newname(filename)
f = open(new_filename, "w")
f.write(str(n))
f.close()
補充資訊
檔案讀寫模式
| r r+ w w+ a a+
------------------|--------------------------
read | + + + +
write | + + + + +
write after seek | + + +
create | + + + +
truncate | + +
position at start | + + + +
position at end | + +
該圖表參考 [2]
若是模式後方有 b
代表使用 Binary 模式操作檔案。
Reference
h t t p s : / / w w w . o p e n c l i . c o m / p e r l / p y t h o n – 4 – w a y – w r i t e – t o – f i l e
[2] h t t p s : / / s t a c k o v e r f l o w . c o m / q u e s t i o n s / 1 4 6 6 0 0 0 / p y t h o n – o p e n – b u i l t – i n – f u n c t i o n – d i f f e r e n c e – b e t w e e n – m o d e s – a – a – w – w – a n d – r
h t t p s : / / w w w . j i s h u w e n . c o m /d/2LwL/zh-tw
[4] https://shazi.info/python3-用-random-和-string-建立隨機字元-學習筆記/