ほんじゃらねっと

ダイエット中プログラマのブログ

Pythonでgrep風にディレクトリ内のファイルを検索する方法(Python3対応&機能追加版)

f:id:piro_suke:20160616233945j:plain

だいぶ前に書いた下記の記事

blog.honjala.net

の内容がPython3で動かないようなので、

Python3対応しつつ、もう少し使えるように書きなおしてみる。

まずはPython3で動くように修正する。

import os
import re

search_dir = "<検索対象フォルダ>"
search_pattern = "<検索パターン>"

file_name_list = os.listdir(search_dir)
for file_name in file_name_list:
    f = open(os.path.join(search_dir, file_name), encoding="utf-8")
    line = f.readline()
    line_number = 1 
    while line:
        m = re.search(search_pattern, line)
        if m:
            print("%s,%d : %s" % (file_name, line_number, line.strip()))
        line = f.readline()
        line_number = line_number + 1 
    f.close()

続いて、ファイル処理をwith内で行うように変更する。

import os                                                                                             
import re                                                                                             
                                                                                                      
search_dir = "<検索対象フォルダ>"
search_pattern = "<検索パターン>"
                                                                                                                                                                                    
file_name_list = os.listdir(search_dir)                                                               
for file_name in file_name_list:                                                                      
    with open(os.path.join(search_dir, file_name), encoding="utf-8") as f:                            
        line_number = 1                                                                               
        for line in f:                                                                                
            m = re.search(search_pattern, line)                                                       
            if m:                                                                                     
                print("%s,%d : %s" % (file_name, line_number, line.strip()))                          
            line_number = line_number + 1    

ちょっとシンプルになった。

ディレクトリを無視する処理を追加する。

この処理がなかったのはひどい。

...
for file_name in file_name_list:
    file_path = os.path.join(search_dir, file_name)
    if os.path.isdir(file_path):
        continue

    with open(file_path, encoding="utf-8") as f:
        line_number = 1 
        for line in f:
            m = re.search(search_pattern, line)
            if m:
                print("%s,%d : %s" % (file_name, line_number, line.strip()))
            line_number = line_number + 1 

特定の拡張子のファイルのみ検索対象とする。

...
ext_patterns = (
    ".txt",
    ".py",
    ".csv",
    ".rst",
    ".md",
)

file_name_list = os.listdir(search_dir)
for file_name in file_name_list:
    file_path = os.path.join(search_dir, file_name)
    if os.path.isdir(file_path):
        continue

    _, ext = os.path.splitext(file_name)
    if not ext in ext_patterns:
        continue
...

文字コード自動判別機能を追加する。

chardetモジュールのインストールが必要。

下記を参照した:

blog.amedama.jp

pip install chardet
...
from chardet.universaldetector import UniversalDetector

...

def detect_encoding(file_path):
    detector = UniversalDetector()
    try:
        with open(file_path, mode="rb") as f:
            while True:
                data = f.readline()
                if data == b'':
                    break

                detector.feed(data)
                if detector.done:
                    break
    finally:
        detector.close()

    detected_encodings = detector.result

    return detected_encodings["encoding"]

...

for file_name in file_name_list:
    ...

    encoding = detect_encoding(file_path)
    with open(file_path, encoding=encoding) as f:

    ...

サブディレクトリも検索できるように

os.walkを使うかどうか指定できるようにする。

あと、検索処理と表示処理を分離する。

今回はこれで完成とする。

# -*- coding: utf-8 -*-

import os
import re

from chardet.universaldetector import UniversalDetector

walk_subdirs = True

search_dir = "<検索対象フォルダ>"
search_pattern = "<検索パターン>"

ext_patterns = (
    ".txt",
    ".py",
    ".csv",
    ".rst",
    ".md",
)

def detect_encoding(file_path):
    detector = UniversalDetector()
    try:
        with open(file_path, mode="rb") as f:
            while True:
                data = f.readline()
                if data == b'':
                    break

                detector.feed(data)
                if detector.done:
                    break
    finally:
        detector.close()

    detected_encodings = detector.result

    return detected_encodings["encoding"]

def search_files(dir_path, file_name_list):
    matched_list = []
    for file_name in file_name_list:
        _, ext = os.path.splitext(file_name)
        if not ext in ext_patterns:
            continue

        file_path = os.path.join(dir_path, file_name)
        encoding = detect_encoding(file_path)
        with open(file_path, encoding=encoding) as f:
            line_number = 1
            for line in f:
                m = re.search(search_pattern, line)
                if m:
                    file_info = {
                        "path": file_path,
                        "line_number": line_number,
                        "line": line.strip()
                    }
                    matched_list.append(file_info)
                line_number = line_number + 1
    return matched_list

def print_matched_list(matched_list):
    for file_info in matched_list:
        print("%s,%d : %s" % (file_info["path"], file_info["line_number"], file_info["line"]))

if walk_subdirs:
    matched_list = []
    for root, dirs, files in os.walk(search_dir):
        sub_matched_list = search_files(root, files)
        matched_list.extend(sub_matched_list)
else:
    file_name_list = os.listdir(search_dir)
    matched_list = search_files(search_dir, file_name_list)

print_matched_list(matched_list)

おわり

Excelファイルの中身とかも検索できるようにできたら、

実用的なスクリプトになりそうだ。

入門 Python 3

入門 Python 3