[Python] os.Walk — 列出所有目錄與檔名

Sean Yeh
Python Everywhere -from Beginner to Advanced
12 min readAug 25, 2022

--

Sui Hong, Taiwan, photo by Sean Yeh

使用 Python 開發程式時,若碰到需要處理檔案的狀況,通常我們會把檔案目錄中包含的檔案名稱全部列出來,再依照需要使用for迴圈對個別檔案進行後續處理。

透過Python 取得目錄中包含的檔案名稱的各種方法中,如果您想要對某個資料夾中的所有檔案更改檔名,或者是對某個資料夾以及該資料夾中的所有子資料夾中的所有檔案更改檔名的話,碰到這種情境時,您需要走訪整個檔案目錄的樹狀結構(directory tree)的所有檔案。

Python 中的os套件提供了.walk()函式,剛好可以協助我們走訪資料夾裡面的所有層級的檔案。

os.walk()走訪目錄

由於walk()函式為os套件中的函式,使用之前需要先匯入os套件。

import os

如果要走訪某一個資料夾裡面的檔案,可以將資料夾的路徑傳入walk()函式中。以下面的程式碼來說,我們將資料夾的路徑project_dir傳入walk()函式中,並且將該函式指定給變數source。

source為一物件,<generator object walk at 0x7fbc219eb970>。

project_dir = '/Users/path/to/folder'source = os.walk(project_dir)

walk實際走訪

實際透過walk函式撰寫一個Python程式執行看看。並且為暸解整個walk函式的實際運作,在此特地簡化程式來了解 os.walk()的構造。

假設我們有下面一個樹狀結構的資料夾目錄。

我們把程式碼簡化成下面的一個for迴圈,來看一下回傳的結果。

import osfor res in os.walk('/Users/path/to/root'):
print(res)

其中,路徑的部分 /Users/path/to/root 我們假設根目錄在root的地方(實際上不是),在根目錄前面的位置則是您電腦的實際路徑。

執行上面的簡單程式碼後,我們可以得到類似下面的執行結果:

('/Users/path/to/root', ['subFolder02', 'subFolder03', 'subFolder01'])
('/Users/path/to/root/subFolder02', [], ['02file_01.txt', '02file_02.txt'])
('/Users/path/to/root/subFolder03', [], ['03file_01.txt'])
('/Users/path/to/root/subFolder01', [], ['01file_01.txt', '01file_02.txt', '01file_03.txt'])

從上面的結果可以發現walk物件傳回一個內含三個參數的tupple,這三個參數分別代表資料夾名稱,資料夾裡面的子目錄名稱以及子目錄中的檔案名稱。它們的資料型別分別是str字串、list串列與list串列。

就以第一行來說( ('/Users/path/to/root', ['subFolder02', 'subFolder03', 'subFolder01']) ),資料夾root下面含有三個資料夾,分別是 subFolder01、 subFolder02、subFolder03;若以第二行來說( ('/Users/path/to/root/subFolder02', [], ['02file_01.txt', '02file_02.txt']) ),資料夾subFolder02 下面含有兩個資料夾,分別是 02file_01.txt、02file_02.txt。

如果第二個回傳的值是空[],就表示沒有下一層了。例如此範例中的資料夾subFolder02 下面已經沒有子資料夾,所以回傳值為空白。

接下來,就可以使用for迴圈,巡迴這個物件。

for folder, subfolders, filenames in source:
print(f'目前資料夾路徑為:{folder}')

for subfolder in subfolders:
print(f'{folder}的子資料夾為:{subfolder}')

for filename in filenames:
print(f'{folder}/{subfolder}內含檔案為:{filename}')

執行上列的程式,就可以得到「目前資料夾路徑」、「目前資料夾以下的子資料夾」以及「子資料夾的內含檔案」。

我們可以依照需要任意取用這三個參數。例如下面程式碼可以印出資料夾裡面檔案的完整路徑。

for folder, subfolders, filenames in source:
print(f'目前資料夾路徑為:{folder}')
for filename in filenames:
print(f'{folder}/{filename}')

取出第一層的內容

我們也可以利用next()函式,取出目錄第一層的內容。

folder, subfolders, filenames = next(source)
print(f'目前資料夾路徑為:{folder}')
print(f'{folder}的子資料夾為:{subfolders}')
print(f'{folder}內含檔案為:{filenames}')

取出根目錄的內容

或者是透過下面方式取得根目錄中所有檔案的名稱:

all_files =[]
for _, _, filenames in source:
all_files.extend(filenames)

print(all_files)

先建立一個串列all_files,再利用for迴圈把檔案名稱,透過extend方法全部搜集到all_files串列中。

上面的程式碼也可以簡化成下面的寫法:

all_files = [filenames for _, _ ,filenames in source ]
print(all_files)

以下是這部分的完整程式碼:

import os
# 專案路徑
project_dir = '/Users/path/to/folder'
source = os.walk(project_dir)for folder, subfolders, filenames in source:
print(f'目前資料夾路徑為:{folder}')

for subfolder in subfolders:
print(f'{folder}的子資料夾為:{subfolder}')
for filename in filenames:
print(f'{folder}/{subfolder}內含檔案為:{filename}')

應用:關鍵字查詢程式

以上的示範可得知os.walk()方法可以走訪資料夾,並且取得資料夾以及其子資料夾的所有檔案的內容。我們可以利用這個性質,設計一個查詢關鍵字的程式。透過這個程式可以幫助我們查詢特定資料夾裡面的文字型檔案,裡面是否存在我們要查詢的關鍵文字?以及該關鍵字位於檔案的幾行?

匯入套件

在這個例子中我們需要用到以下兩種套件:

import fnmatch,os

其中一個是前面提過的os套件,將用於 os.walkos.path 等方法;另一個是fnmatch套件。這個套件可以用來比對文字。我們會在此用到 fnmatch.filter 方法來檢測目標字串是否符合需要的關鍵字。

詢問路徑

利用一個交談式對話input來誘導使用者輸入要查詢的路徑。並且指定一個預設的查詢路徑,避免使用者忘記輸入。程式碼如下:

path = input('請輸入您要查詢的資料夾路徑 >> ')
if path =='':
print('以預設資料夾Documents進行查詢')
path ='/Users/path/to/folder'

其中path變數被用來指定為路徑的內容。

詢問查詢關鍵字

再次透過input對話,來提示使用者輸入關鍵字,並且將關鍵次存入keyword變數中。

keyword = input('請輸入您要查詢的關鍵字 >> ')
print(f'您要查詢的關鍵字:『{keyword}』')

查詢的檔案格式

利用一個串列,儲存想要查詢的所有檔案之副檔名。並且指定給變數file_types。

file_types = ['txt','html']
file_exts = [f'*.{f_type}' for f_type in file_types]

另外,我們要查詢的是所有的txt與html檔案,也就是說副檔名前面是什麼並不重要。一般來說,在這樣的查詢時我們會使用星號( * )作為萬用字元,代表所有的類型。要在副檔名前面加上星號與逗點(*.),也就是 *.txt*.html。因此透過 f'*.{f_type}' for f_type in file_types 方式產生這個結果,並且存在變數 file_exts 中。

巡迴資料夾檔案

先將要查詢的路徑(path)帶入 os.walk 中,並且將 os.walk 物件指定給變數source。

source = os.walk(path)

接著開始透過for迴圈巡迴os.walk物件(已經指定給source變數)。

for folder, subfolders, filenames in source:
...略...

並且透過另一個for迴圈篩選出符合副檔名條件的檔案

for ext in (tuple(file_exts)):         for filename in fnmatch.filter(filenames, ext):  

...略...

判斷如果路徑存在,就開始進行關鍵字的尋找,並且在畫面中顯示出包括檔案檔名的完整路徑filepath:

if os.path.isfile(filepath):     
print('尋找:',filepath)

使用open函式讀取符合條件的檔案( with open(filepath, "r",) as fp: ),並且再透過一組for迴圈逐行的讀取(for line in fp.readlines(): ),並且透過if條件式,判斷該行是否存在符合的關鍵字( if keyword in line: ),如果找到了關鍵字就透過print將檔名與行數顯示在畫面中( print(f"找到檔案: {filename} 第{num}行: 關鍵字:{str(line)}") )。以下是這一段的程式碼:

with open(filepath, "r",) as fp:
num = 1
for line in fp.readlines():
if keyword in line:
print(f"找到檔案: {filename} 第{num}行: 關鍵字:{str(line)}")
print()
num = num + 1

程式碼中,行數乃透過 num 來決定,在讀取前的起始值設定為1,放在for迴圈外面,當開始讀取後( for line in fp.readlines(): ),每讀取一行增加1的值(num = num + 1 )。

完整程式碼

以下是關鍵字查詢程式的完整程式碼,大家可以執行看看:

import fnmatch, ospath = input('請輸入您要查詢的資料夾路徑 >> ')
if path =='':
print('以預設資料夾Documents進行查詢')
path ='/Users/path/to/folder'

keyword = input('請輸入您要查詢的關鍵字 >> ')
print(f'您要查詢的關鍵字:『{keyword}』')
source = os.walk(path)
# 查詢的檔案格式
file_types = ['*.txt','*.html']

for dirs, subdirs, files in source:
for ext in (tuple(file_types)):
for filename in fnmatch.filter(files, ext):
filepath = os.path.join(dirs, filename)
if os.path.isfile(filepath):
print('尋找:',filepath)

with open(filepath, "r",) as fp:
num = 1
for line in fp.readlines():
if keyword in line:
print(f"找到檔案: {filename} 第{num}行: 關鍵字:{str(line)}")
print()
num = num + 1

結語

os.walk()的運作原理是透過遞迴的方式重複的呼叫自己來處理子資料夾。它可以巡迴的走訪資料夾下所有子資料夾,並且可以得到「目前資料夾路徑」、「目前資料夾以下的子資料夾」清單以及「子資料夾的內含檔案」清單,是一個很有用的函式。如果知道了它的性質之後,可以幫助我們在其他的專案中更方便的處理資料夾中的檔案。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。