[Python 3] 檢查檔案及目錄是否存在 ,自動更名,避免複寫。

Photo by Tj Holowaychuk on Unsplash

由於目前工作之餘,正使用 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 新增查刪,實作該篇文章大致上可以學習到以下技能。

  1. 檢查檔案是否存在
  2. 檢查目錄是否存在
  3. 檔案存在,自動更名並建立(隨機、序列)
  4. 檔案不存在,自動建立
  5. 目錄不存在,自動建立
  6. 多層目錄建立

補充

自 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()
[Python3] 檢查檔案及目錄是否存在 ,自動更名,避免複寫。
Python3 檢查檔案及目錄是否存在,自動更名,避免複寫。

補充資訊

檔案讀寫模式

                  | 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-建立隨機字元-學習筆記/

MksYi

透過網路分享知識的學習者。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料