Python 自動建立資料夾、搜尋、複製搬移與安全刪除檔案的流程示意圖

Python 檔案操作教學:os、shutil、pathlib 複製搬移與刪除

用 Python 整理資料時,常會遇到建立資料夾、組合路徑、搜尋檔案、複製、搬移與刪除等需求。原本教學使用 osshutil,這些標準庫至今仍然適用;現在也可以搭配 pathlib,用物件化路徑讓程式更容易閱讀並兼顧 Windows、macOS 與 Linux。

快速選擇:pathlib 適合一般路徑與檔案操作;shutil 負責複製整棵目錄、搬移與遞迴刪除;os 則提供底層作業系統介面與 os.walk() 等成熟工具。

需求 建議工具 常用方法
建立、檢查、組合路徑 pathlib Path.mkdir()exists()/
遍歷目錄 ospathlib os.walk()Path.rglob()
複製檔案或整個目錄 shutil copyfile()copy2()copytree()
搬移檔案或目錄 shutil shutil.move()
刪除單一檔案或整棵目錄 pathlibshutil Path.unlink()shutil.rmtree()

建立資料夾:os.mkdir、os.makedirs 與 Path.mkdir

原文分別介紹單層和多層目錄,這兩種寫法仍然正確:

import os

os.mkdir("./test")
os.makedirs("./test/save", exist_ok=True)

os.mkdir() 只建立指定目錄;父目錄不存在時會拋出 FileNotFoundError,目錄已存在時則會拋出 FileExistsErroros.makedirs() 會建立缺少的中間目錄,搭配 exist_ok=True 可在目標資料夾已存在時繼續執行。

使用 pathlib 可以把相同需求寫得更集中:

from pathlib import Path

output_dir = Path("test") / "save"
output_dir.mkdir(parents=True, exist_ok=True)

parents=True 對應建立中間目錄,exist_ok=True 則允許既有目錄。若同名路徑實際上是檔案,仍會發生錯誤,不會把檔案當成資料夾。

檢查路徑與組合檔名

原本的 os.path.isdir()os.path.join() 仍可使用:

import os

if not os.path.isdir("./test"):
    print("資料夾不存在")

full_path = os.path.join("./test", "save", "test.jpg")
print(full_path)

不要用手動字串相加處理斜線。不同作業系統的路徑分隔方式不完全相同;os.path.join()Path 會依平台處理:

from pathlib import Path

root = Path("test")
image_path = root / "save" / "test.jpg"

print(image_path.exists())
print(image_path.is_file())
print(root.is_dir())

搜尋多層目錄內的檔案

os.walk() 會對每層目錄回傳 dirpath、子目錄名稱與檔案名稱。原文範例補上正確縮排後如下:

import os

for root, dirs, files in os.walk("./test"):
    for filename in files:
        full_path = os.path.join(root, filename)
        print(full_path)

若只要找特定副檔名,Path.rglob() 更精簡:

from pathlib import Path

for image_path in sorted(Path("test").rglob("*.jpg")):
    print(image_path)

Python 官方文件說明 rglob() 的結果沒有保證排序,因此需要固定順序時應自行使用 sorted()

複製檔案:copyfile、copy 與 copy2 差在哪?

原文介紹了 shutil.copyfile()shutil.copy()。兩者都能複製檔案,但目的地與中繼資料處理不同:

函式 目的地 保留內容 既有檔案
copyfile(src, dst) 必須指定完整目標檔名 檔案內容,不複製中繼資料 直接取代可寫入的目標檔案
copy(src, dst) 可指定檔名或目錄 內容與權限模式 同名目標檔案會被取代
copy2(src, dst) 可指定檔名或目錄 內容,並盡可能保留中繼資料 同名目標檔案會被取代
from pathlib import Path
import shutil

source = Path("photos") / "test.jpg"
destination = Path("backup") / "test.jpg"
destination.parent.mkdir(parents=True, exist_ok=True)

shutil.copyfile(source, destination)
shutil.copy2(source, Path("archive"))

若目的地可能已有同名檔案,務必先決定是否允許覆寫,不要把「複製」誤認為一定會保留兩份不同名稱的檔案。

複製整個資料夾:shutil.copytree

原文使用 shutil.copytree("A", "B") 複製整棵目錄,這仍是正確基本寫法:

import shutil

shutil.copytree("A", "B")

預設 dirs_exist_ok=False,因此目的地 B 已存在時會出現 FileExistsError。若確定要合併進既有目錄,可明確設定:

shutil.copytree("A", "B", dirs_exist_ok=True)

覆寫提醒:dirs_exist_ok=True 允許既有目錄繼續複製,但來源中同名檔案會取代目的地檔案。執行前仍應確認目的地、備份需求與同名檔案處理方式。

搬移與重新命名:shutil.move、Path.rename、Path.replace

原文的 shutil.move("A", "B") 可以搬移檔案或目錄。若來源和目的地位於相同檔案系統,內部會優先使用重新命名;跨檔案系統時,可能改為複製到目的地後刪除來源。

from pathlib import Path
import shutil

source = Path("inbox") / "report.csv"
archive = Path("archive")
archive.mkdir(exist_ok=True)

new_path = shutil.move(source, archive / source.name)
print(new_path)

只在同一檔案系統重新命名時,可使用 Path.rename()。Windows 與 Unix 對既有目標檔案的行為不同;若明確要跨平台取代既有檔案,使用 Path.replace() 比較清楚:

from pathlib import Path

Path("draft.txt").rename("final.txt")
Path("new-config.json").replace("config.json")

刪除單一檔案與空資料夾

原文使用 os.remove() 刪除單一檔案;等價的現代寫法是 Path.unlink()

import os
from pathlib import Path

os.remove("file.jpg")
Path("temporary.txt").unlink(missing_ok=True)

os.remove() 遇到資料夾會拋出錯誤;Path.unlink() 也只用於檔案或符號連結。刪除空資料夾可用 Path.rmdir(),資料夾內還有內容時不會刪除:

Path("empty-folder").rmdir()

安全使用 shutil.rmtree 刪除整棵目錄

shutil.rmtree() 會遞迴刪除指定資料夾及其所有內容,而且不會把檔案送進作業系統資源回收筒。不要直接把未驗證的使用者輸入、空字串或計算結果交給它。

下面先把允許範圍與目標轉成絕對解析路徑,再確認目標確實位於工作目錄內,而且不是工作目錄本身:

from pathlib import Path
import shutil

base = Path("workspace").resolve()
target = (base / "cache").resolve()

if target == base or not target.is_relative_to(base):
    raise ValueError("拒絕刪除工作範圍外或根目錄本身")

if target.exists():
    shutil.rmtree(target)

Path.resolve() 會轉成絕對路徑、解析符號連結並消除 ..;先解析再使用 is_relative_to(),才能讓路徑邊界檢查更可靠。重要資料還應先備份,或先輸出即將刪除的清單供人工確認。

完整範例:整理 JPG 圖片到輸出資料夾

這個範例延續原文「搜尋檔案並進行整理」的用途,使用 rglob() 找出圖片,再用 copy2() 複製。來源檔案不會被刪除:

from pathlib import Path
import shutil

source_dir = Path("photos")
output_dir = Path("organized")
output_dir.mkdir(parents=True, exist_ok=True)

for source in sorted(source_dir.rglob("*.jpg")):
    destination = output_dir / source.name

    if destination.exists():
        print(f"略過同名檔案:{destination}")
        continue

    shutil.copy2(source, destination)
    print(f"已複製:{source} -> {destination}")

如果資料來自遠端主機,可延伸閱讀 Python SFTP 自動化與多主機檔案傳輸教學;需要處理下載後的分析資料,可參考 Python 抓取股票資料與 DataFrame 教學

常見問題

Windows 路徑一定要使用反斜線嗎?

不需要手動組合。優先使用 Path("folder") / "file.txt"os.path.join()。如果程式中必須直接寫 Windows 反斜線路徑,可使用原始字串,例如 r"C:\data\file.txt",避免 \n\t 被解讀成跳脫字元。

copy、copy2 應該選哪一個?

只需要內容與權限模式時用 copy();需要盡可能保留修改時間等中繼資料時用 copy2()。不同作業系統支援的中繼資料不完全一致,因此 copy2() 不是所有屬性都能百分之百複製。

shutil.move 一定只是重新命名嗎?

不一定。同一檔案系統通常使用重新命名;跨檔案系統時可能採用複製後刪除。大量檔案、權限或磁碟空間敏感的流程,需要把這項差異納入錯誤處理。

如何避免誤刪檔案?

先限制允許操作的根目錄、將路徑解析成絕對路徑、拒絕根目錄本身、預覽即將刪除的清單,並為重要資料保留備份。不要用 ignore_errors=True 隱藏所有刪除錯誤,否則可能誤以為操作已完整完成。

結論

osshutil 仍是 Python 檔案操作的重要標準庫,原本的建立目錄、搜尋、複製、搬移與刪除流程都能保留。新程式可搭配 pathlib 讓路徑更易讀;使用會覆寫或遞迴刪除的函式時,則要先確認目的地、處理同名檔案並驗證操作範圍。

官方文件:Python os:檔案與目錄Python shutil:高階檔案操作Python pathlib:物件化路徑

Similar Posts

發佈留言

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