2022년 10월 10일 월요일

python 재귀를 이용한 폴더 검색 recursive, searching directory/folder

간혹 폴더에서 특정 파일이 있는지 혹은 모든 폴더를 뒤져서 특정한 형태의 text가 있는지 찾는 프로그램이 필요한 경우가 있습니다.

쉽게 설명하자면 grep이나 findfile 같은 것을 만들 수 있는 이론적인 배경이 됩니다.

특히 폴더 구조에서는 재귀 호출을 사용하게 되며 여기에서는 폴더를 순회하면서 특별한(여러가지) 처리가 가능한 범용적인 예제입니다.


다음 예제로 학습 할 수 있는 것은 recursive, callback 함수 등이 됩니다.


들어가기 앞서 몇가지 기본 함수 들입니다.

폴더 만들기

폴더가 존재하지 않는다면 os.makedirs()를 이용해서 폴더를 만듭니다. 이때 만들고자 하는 폴더의 경로상 상위가 없더라도 모두 만들어 집니다. 즉 "a/b/c" 라는 폴더를 만들때 "a" "a/b" 폴더가 없더라도 "a", "a/b", "a/b/c" 모두 만들어지게 됩니다.


def makefolder(folder_name):
if folder_name != '' and not os.path.exists(folder_name):
os.makedirs(folder_name)


파일 쓰기

간단하게 text를 파일로 저장하는 기능입니다.


def write_file(filename, str_data):
with open(filename, 'w') as fp:
fp.write(str_data)
fp.close()


폴더 지우기

예제로 폴더를 만들었다면 한꺼번에 폴더를 지우는 것이 필요합니다.

폴더내에 다른 파일이 존재하더라도 쉽게 지울 수 있는 shutil.rmtree가 주로 사용됩니다.

shutil.rmtree("temp", ignore_errors=True)


callback 함수

callback이란 call을 나중에(back)시점에 해준다는 의미입니다. 다른 언어에서는 메소드 포인터를 넘겨서 해당 메소드를 나중에 호출해주는 방법으로, 인자를 변수를 넘겨주는 것이 아니라 메소드 이름으로 넘겨주는데 보통 메소드 이름을 만들때 cb 라고 붙여줍니다. (완전 개인적인 약속이며 습관일 뿐입니다. 다른 분들은 이렇게 안해도 됩니다.)

여기에서는 호출 시점에 아래와 같은 형태로 넘겨주었습니다.


recursive_searching_dir(0, ".", print_filename_cb)
recursive_searching_dir(0, ".", searching_filename_cb)


재귀 함수

내용은 간단합니다. 재귀 함수 내에서 다른 자기 자신의 함수를 호출하면 됩니다. 


def 재귀 함수():

    재귀 함수()


여기서 고려해야 할 부분은 탈출 조건입니다. 그렇지 않으면 무한정 호출되어 stack overflow가 발생하게 됩니다.


def 재귀 함수(검색 하고자 하는 폴더경로):

    폴더이름들=폴더명획득(검색 하고자 하는 폴더경로)

    for 검색 하고자 하는 폴더경로 in 폴더이름들

        재귀 함수(검색 하고자 하는 폴더경로)


재귀 함수 내에서 다른 폴더를 검색하고자 할때 자기 자신 함수를 호출하면 됩니다. 이때 존재하는 폴더가 있을 경우만 호출되고 없다면 탈출 조건이 될 겁니다.

이것을 구현한 함수입니다.


def recursive_searching_dir(depth, search_path, cb_fun):
abs_path = os.path.abspath(search_path)
full_file_list = []
base_file_list = []
full_folder_list = []
base_folder_list = []
all_list = os.listdir(abs_path)
for file_name in all_list:
full_path = os.path.join(abs_path, file_name)
if os.path.isdir(full_path):
full_folder_list.append(full_path)
base_folder_list.append(file_name)
elif os.path.isfile(full_path):
full_file_list.append(full_path)
base_file_list.append(file_name)
cb_fun(depth, abs_path, base_file_list, full_file_list, base_folder_list, full_folder_list)

for folder in full_folder_list:
recursive_searching_dir(depth + 1, folder, cb_fun)

여기에서 depth는 stack관리를 위해 디버깅용 입니다. cb_fun는 앞서 설명한 callback 함수를 실제 호출 해주는 곳입니다.

base_file_list

full_file_list

base_folder_list

full_folder_list

4개의 list가 있는데 이것은 file인지 folder인지 구별해서 list가 관리가 되고 full 인지 base인지는 전체 경로를 포함할지 이름만 존재하는지에 따른 list입니다. (간혹 full name을 관리하는 경우가 편한 경우도 있습니다.)


전체소스

지금까지 구현한 전체 소스입니다.


import os
import shutil


def recursive_searching_dir(depth,
search_path,
cb_fun):
abs_path = os.path.abspath(search_path)
full_file_list = []
base_file_list = []
full_folder_list = []
base_folder_list = []
all_list = os.listdir(abs_path)
for file_name in all_list:
full_path = os.path.join(abs_path, file_name)
if os.path.isdir(full_path):
full_folder_list.append(full_path)
base_folder_list.append(file_name)
elif os.path.isfile(full_path):
full_file_list.append(full_path)
base_file_list.append(file_name)
cb_fun(depth, abs_path, base_file_list,
full_file_list, base_folder_list,
full_folder_list)

for folder in full_folder_list:
recursive_searching_dir(depth + 1, folder, cb_fun)


def print_filename_cb(depth,
abs_path,
base_file_list,
full_file_list,
base_folder_list,
full_folder_list):
print(f"depth #{depth} //{abs_path}")
print(f"base_file_list:{base_file_list}")
print(f"full_file_list:{full_file_list}")
print(f"base_folder_list:{base_folder_list}")
print(f"full_folder_list:{full_folder_list}")
print("\n")


def searching_filename_cb(depth,
abs_path,
base_file_list,
full_file_list,
base_folder_list,
full_folder_list):
print(f"depth #{depth} //{abs_path}")
print(f"base_file_list:{base_file_list}")
print(f"full_file_list:{full_file_list}")
for fn in base_file_list:
if fn.startswith("a"):
print("!!! find !!!:" + fn)

print("\n")


def makefolder(folder_name):
if folder_name != '' and \
not os.path.exists(folder_name):
os.makedirs(folder_name)


def write_file(filename, str_data):
with open(filename, 'w') as fp:
fp.write(str_data)
fp.close()


if __name__ == "__main__":
shutil.rmtree("temp", ignore_errors=True)
makefolder("temp/a/b/c/d")
makefolder("temp/b/e/f")
makefolder("temp/g/h")
write_file("temp/a/abc.txt", "Hello ABC")
write_file("temp/b/e/f/acc.txt", "Hello ACC")

recursive_searching_dir(0, ".", print_filename_cb)
recursive_searching_dir(0, ".", searching_filename_cb)

shutil.rmtree("temp", ignore_errors=True)

실행 화면

depth #0 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir
base_file_list:['recursive_searching_dir.py']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\recursive_searching_dir.py']
base_folder_list:['temp']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp']


depth #1 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp
base_file_list:[]
full_file_list:[]
base_folder_list:['a', 'b', 'g']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a', 'C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\b', 'C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\g']


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a
base_file_list:['abc.txt']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a\\abc.txt']
base_folder_list:['b']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a\\b']


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b
base_file_list:[]
full_file_list:[]
base_folder_list:['c']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a\\b\\c']


depth #4 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b\c
base_file_list:[]
full_file_list:[]
base_folder_list:['d']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a\\b\\c\\d']


depth #5 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b\c\d
base_file_list:[]
full_file_list:[]
base_folder_list:[]
full_folder_list:[]


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b
base_file_list:[]
full_file_list:[]
base_folder_list:['e']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\b\\e']


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b\e
base_file_list:[]
full_file_list:[]
base_folder_list:['f']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\b\\e\\f']


depth #4 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b\e\f
base_file_list:['acc.txt']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\b\\e\\f\\acc.txt']
base_folder_list:[]
full_folder_list:[]


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\g
base_file_list:[]
full_file_list:[]
base_folder_list:['h']
full_folder_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\g\\h']


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\g\h
base_file_list:[]
full_file_list:[]
base_folder_list:[]
full_folder_list:[]


depth #0 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir
base_file_list:['recursive_searching_dir.py']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\recursive_searching_dir.py']


depth #1 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp
base_file_list:[]
full_file_list:[]


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a
base_file_list:['abc.txt']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\a\\abc.txt']
!!! find !!!:abc.txt


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b
base_file_list:[]
full_file_list:[]


depth #4 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b\c
base_file_list:[]
full_file_list:[]


depth #5 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\a\b\c\d
base_file_list:[]
full_file_list:[]


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b
base_file_list:[]
full_file_list:[]


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b\e
base_file_list:[]
full_file_list:[]


depth #4 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\b\e\f
base_file_list:['acc.txt']
full_file_list:['C:\\Users\\jun\\Documents\\GitHub\\sourcecode\\python\\example\\_49_recursive_searching_dir\\temp\\b\\e\\f\\acc.txt']
!!! find !!!:acc.txt


depth #2 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\g
base_file_list:[]
full_file_list:[]


depth #3 //C:\Users\jun\Documents\GitHub\sourcecode\python\example\_49_recursive_searching_dir\temp\g\h
base_file_list:[]
full_file_list:[]

실제 코드는 아래 링크 참고하세요

sourcecode/recursive_searching_dir.py at main · donarts/sourcecode · GitHub


댓글 없음:

댓글 쓰기