Python 檔案操作教學:os、shutil、pathlib 複製搬移與刪除
用 Python 整理資料時,常會遇到建立資料夾、組合路徑、搜尋檔案、複製、搬移與刪除等需求。原本教學使用 os 與 shutil,這些標準庫至今仍然適用;現在也可以搭配 pathlib,用物件化路徑讓程式更容易閱讀並兼顧 Windows、macOS 與 Linux。
快速選擇:pathlib 適合一般路徑與檔案操作;shutil 負責複製整棵目錄、搬移與遞迴刪除;os 則提供底層作業系統介面與 os.walk() 等成熟工具。
| 需求 | 建議工具 | 常用方法 |
|---|---|---|
| 建立、檢查、組合路徑 | pathlib |
Path.mkdir()、exists()、/ |
| 遍歷目錄 | os 或 pathlib |
os.walk()、Path.rglob() |
| 複製檔案或整個目錄 | shutil |
copyfile()、copy2()、copytree() |
| 搬移檔案或目錄 | shutil |
shutil.move() |
| 刪除單一檔案或整棵目錄 | pathlib/shutil |
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,目錄已存在時則會拋出 FileExistsError。os.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 隱藏所有刪除錯誤,否則可能誤以為操作已完整完成。
結論
os 與 shutil 仍是 Python 檔案操作的重要標準庫,原本的建立目錄、搜尋、複製、搬移與刪除流程都能保留。新程式可搭配 pathlib 讓路徑更易讀;使用會覆寫或遞迴刪除的函式時,則要先確認目的地、處理同名檔案並驗證操作範圍。
官方文件:Python os:檔案與目錄、Python shutil:高階檔案操作、Python pathlib:物件化路徑。















