前言

前面介紹的函式都是針對單一檔案或單一目錄。但實務上常常需要「把某個資料夾底下所有檔案都處理一遍」,這時就需要能夠走訪整棵目錄樹的工具。

本節介紹兩個常用的作法:

模組用途適合情境
os.walk遞迴走訪目錄及所有子目錄要處理整棵目錄樹
glob萬用字元取得符合條件的檔案串列只要挑出特定類型的檔案

os.walk 函式

os.walk可以搜尋指定目錄以及其子目錄,它會傳回一個包含 3 個元素的元組,分別是:

元素內容
dirname目前所在的目錄名稱(字串)
subdir下一層的目錄串列(list)
files目前目錄中所有的檔案串列(list)

由於它具有類似遞迴方式的處理能力,可以遍歷所有的子目錄,功能非常強大。

範例目錄結構

為了方便說明,假設有下列的目錄結構:

\oswalk
   ├─\Dir ──────── \SubDir ──── SubDir1.txt
   │               ├─ Dir1.txt
   │               └─ Dir2.txt
   ├─ oswalk.py
   └─ oswalk1.txt

[結果]

檔案路徑: C:\example\ch09\oswalk
目錄串列: ['Dir']
檔案串列: ['oswalk.py', 'oswalk1.txt']
 
檔案路徑: C:\example\ch09\oswalk\Dir
目錄串列: ['SubDir']
檔案串列: ['Dir1.txt', 'Dir2.txt']
 
檔案路徑: C:\example\ch09\oswalk\Dir\SubDir
目錄串列: []
檔案串列: ['SubDir1.txt']

說明

  1. os.walk()每繞一圈就回傳一層目錄的資訊,因此用for迴圈接收。
  2. 一次解包成三個變數dirname, subdir, files,對應前面表格的三個元素。
  3. 首先取得的是最上層<oswalk>,該路徑包含一個<Dir>目錄以及兩個檔案。
  4. 接著進入子目錄<Dir>,其下包含<SubDir>目錄和兩個 txt 檔。
  5. 最後進入<SubDir>,該目錄串列為[]表示已無子目錄

走訪順序是「由上而下、逐層深入」

os.walk預設是先處理上層目錄,再往下鑽。每一圈只回傳「當層」的資訊,子目錄的內容要等下一圈才會出現。
觀察上面的結果:Dir1.txt並沒有出現在第一圈,而是等走到\Dir那一層才出現。

取得每個檔案的完整路徑

files裡面只有檔名,沒有路徑。要組合成完整路徑,必須搭配 6. os.path 路徑處理join()

import os
for dirname, subdir, files in os.walk("."):
    for f in files:
        fullpath = os.path.join(dirname, f)     # ⭐ 組合完整路徑
        print(fullpath, os.path.getsize(fullpath), "bytes")

常見錯誤:直接用檔名去開檔

for dirname, subdir, files in os.walk(path):
    for f in files:
        open(f)          # ❌ 找不到檔案!f 只是檔名,沒有路徑
        open(os.path.join(dirname, f))   # ✅

只有當程式剛好在該目錄執行時前者才會成功,一旦走進子目錄就會失敗。

glob 模組

glob可以取得符合指定條件的檔案串列,語法比os.walk簡單得多。

import glob
glob.glob("路徑名稱")

路徑名稱可以明確指定檔案名稱,也可使用「*」萬用字元。

[簡例] 取得<glob.py>檔、檔名前兩個字元是os開頭的所有 py 檔案,以及所有副檔名為 txt 的檔案。(檔名:glob.py

[結果]

glob.py
osdirname.py
osexists.py
osgetcwd.py
osmkdir.py
ospath.py
osremove.py
osrmdir.py
ossystem.py
file1.txt
file2.txt
fileUTF8.txt
password.txt
test01.txt
test02.txt

萬用字元

萬用字元意義範例
*任意長度(含 0 個)的字元*.txt 所有 txt 檔
?剛好一個字元file?.txt file1.txt、file2.txt
[…]中括號內任一字元file[12].txt 只有 file1、file2

遞迴搜尋子目錄: recursive=True

書上沒有提到,但glob其實也能像os.walk一樣往下鑽,只要用**並加上recursive=True

import glob
glob.glob("**/*.txt", recursive=True)     # 所有子目錄中的 txt 檔

這是glob最實用的功能之一,只要一行就能取代整段os.walk

兩者的選用

os.walkglob
寫法需要for迴圈解包三個變數一行取得串列
回傳目錄與檔名分開直接是路徑串列
篩選條件要自己寫if判斷用萬用字元直接篩
取得目錄結構✅ 知道每個檔案在哪一層❌ 只有路徑字串
適合需要知道目錄層次、要對目錄本身做事只想挑出特定檔案

簡單說:只是要找檔案 → 用glob;需要處理目錄結構 → 用os.walk


相關主題與延伸閱讀