note.nkmk.me

Python, pathlibでファイル一覧を取得(glob, iterdir)

Date: 2018-10-01 / tags: Python, ファイル操作

Pythonのpathlibモジュールを使ってディレクトリ(フォルダ)内のファイルやサブディレクトリの一覧を取得する方法について説明する。再帰的に処理したり、ワイルドカード文字*や正規表現による条件を指定して一覧を抽出したりできる。

Pathオブジェクトの一覧が取得できるのでそれぞれに対して各種メソッドを使った操作を行うことも簡単。

pathlibはPython3.4から追加されたモジュール。ファイルやディレクトリのパスをオブジェクトとして操作できる。標準ライブラリに含まれているので追加のインストールは不要(importは必要)。

ここでは以下の内容について説明する。

  • ディレクトリ内のファイル・サブディレクトリ一覧を取得: iterdir()
  • 条件を指定して再帰的に一覧取得: glob()
    • globモジュールとpathlibモジュールでの**の意味の違い
    • 正規表現で条件指定
  • Pathオブジェクト一覧に対する処理の例
    • すべて絶対パスに変換
    • すべて文字列に変換
    • ファイルまたはディレクトリのみ抽出
    • ファイル名(basename)のみを抽出
    • 条件で抽出したファイルを削除

pathlibの基礎的な使い方については以下の記事を参照。

以下のようなファイル・ディレクトリ構成を例とする。

temp
├── 1.txt
├── 12.text
├── 123.txt
├── [x].txt
├── aaa.text
└── dir
    ├── 987.text
    ├── bbb.txt
    ├── sub_dir1
    │   ├── 98.txt
    │   └── ccc.text
    └── sub_dir2
        └── ddd.text
スポンサーリンク

ディレクトリ内のファイル・サブディレクトリ一覧を取得: iterdir()

ディレクトリ内のファイル・サブディレクトリ一覧を取得するにはPathオブジェクトのiterdir()メソッドを使う。

pathlib.Path()Pathオブジェクトを生成する。

import pathlib
import glob
import re
import pprint

p_temp = pathlib.Path('temp')

print(p_temp)
# temp

print(type(p_temp))
# <class 'pathlib.PosixPath'>

Pathの基本については以下の記事を参照。

iterdir()メソッドはos.listdir()に似ているが、名前の通りリストではなくイテレータを返す。for文で使うのであればイテレータのままで問題ないが、リストにしたい場合はlist()を使う。

各要素はPathオブジェクト。

ここでは結果を見やすくするためにpprintを使っている。

print(type(p_temp.iterdir()))
# <class 'generator'>

pprint.pprint(list(p_temp.iterdir()))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

ファイルを指すPathオブジェクトからiterdir()を呼ぶとエラー。

# print(list(pathlib.Path('temp/1.txt').iterdir()))
# NotADirectoryError: [Errno 20] Not a directory: 'temp/1.txt'

iterdir()単体ではサブディレクトリ内のファイルやディレクトリを再帰的に取得することはできない。再帰的に一覧を取得したい場合は次に説明するglob()を使う。

条件を指定して再帰的に一覧取得: glob()

条件を指定して再帰的にファイル・サブディレクトリ一覧を取得するにはPathオブジェクトのglob()メソッドを使う。

名前の通り、処理としてはglobモジュールと同じ。globモジュールの使い方などの詳細は以下の記事を参照。

globモジュールのglob()はリストを返すが、Pathオブジェクトのglob()はイテレータを返す。for文で使うのであればイテレータのままで問題ないが、リストにしたい場合はlist()を使う。

以下はサブディレクトリ内を含むすべてのディレクトリの中から拡張子がtxtのファイルの一覧を取得する例。

各要素はPathオブジェクト。

p_temp = pathlib.Path('temp')

print(type(p_temp.glob('**/*.txt')))
# <class 'generator'>

pprint.pprint(list(p_temp.glob('**/*.txt')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

glob()で使える特殊文字は以下の通り。

  • *: 長さ0文字以上の任意の文字列
  • ?: 任意の一文字
  • []: 特定の一文字

各特殊文字を使った例を示す。使い方はglobモジュールと同じなので、詳しい説明は上述の関連記事を参照されたい。

pprint.pprint(list(p_temp.glob('*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint(list(p_temp.glob('dir/*/*.text')))
# [PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

pprint.pprint(list(p_temp.glob('???.*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint(list(p_temp.glob('[a-z][a-z][a-z].*')))
# [PosixPath('temp/aaa.text')]

globモジュールとpathlibモジュールでの**の意味の違い

globモジュールとpathlibモジュールのglob()**の意味が若干異なる。

  • globモジュールでの**
    • 引数recursive=Trueの場合、あらゆるファイルや 0 個以上のディレクトリおよびサブディレクトリにマッチして再帰的な処理を行う
  • pathlibモジュールのglob()での**
    • 引数recursiveは存在せず、**を使うと常に再帰的な処理を行う
    • すべてのディレクトリおよびサブディレクトリを再帰的に走査する

具体的な例を示す。

globモジュールでrecursive=Trueとして**を使うと、すべてのサブディレクトリを再帰的に操作し、ファイル・ディレクトリの一覧を返す。

pprint.pprint(glob.glob('temp/**', recursive=True))
# ['temp/',
#  'temp/[x].txt',
#  'temp/aaa.text',
#  'temp/dir',
#  'temp/dir/sub_dir1',
#  'temp/dir/sub_dir1/98.txt',
#  'temp/dir/sub_dir1/ccc.text',
#  'temp/dir/987.text',
#  'temp/dir/bbb.txt',
#  'temp/dir/sub_dir2',
#  'temp/dir/sub_dir2/ddd.text',
#  'temp/1.txt',
#  'temp/12.text',
#  'temp/123.txt']

pathlibモジュールのglob()での**の場合、**はディレクトリにのみマッチする。ファイルにもマッチさせたい場合は**/*とする。

pprint.pprint(list(p_temp.glob('**')))
# [PosixPath('temp'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/dir/sub_dir1'),
#  PosixPath('temp/dir/sub_dir2')]

pprint.pprint(list(p_temp.glob('**/*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1'),
#  PosixPath('temp/dir/987.text'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir2'),
#  PosixPath('temp/dir/sub_dir1/98.txt'),
#  PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

正規表現で条件指定

glob()で使えるのは限られた特殊文字のみだが、正規表現による条件指定と組み合わせることも可能。

リスト内包表記の条件分岐でre.serch()による判定を行う。Pathオブジェクトをstr()で文字列に変換する必要があるので注意。

例は以下の通り。

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search('\d+\.txt', str(p))])
# [PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search('/\D{3}\.(txt|text)', str(p))])
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

\dは数字、+は1回以上の繰り返し。\Dは数字以外の文字、{n}はn回の繰り返し。また、(a|b)はa, bのいずれかとなる。globだけでは実現できない複雑な条件が指定できる

Pathオブジェクト一覧に対する処理の例

iterdir(), glob()いずれもPathオブジェクトの一覧が取得できるので、リスト内包表記を使ってそれらのオブジェクトに任意の処理を実行することが可能。

いくつか具体例を示す。

すべて絶対パスに変換

絶対パスに変換するのはresolve()

pprint.pprint([p.resolve() for p in p_temp.iterdir()])
# [PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/[x].txt'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/aaa.text'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/dir'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/1.txt'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/12.text'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/123.txt')]

すべて文字列に変換

Pathオブジェクトはstr()関数で文字列に変換できる。

pprint.pprint([str(p) for p in p_temp.iterdir()])
# ['temp/[x].txt',
#  'temp/aaa.text',
#  'temp/dir',
#  'temp/1.txt',
#  'temp/12.text',
#  'temp/123.txt']

ファイルまたはディレクトリのみ抽出

is_file(), is_dir()でファイルまたはディレクトリかを判定できる。リスト内包表記の条件分岐でそれらのメソッドを使うとファイルまたはディレクトリのパスのみを抽出可能。

pprint.pprint([p for p in p_temp.iterdir() if p.is_file()])
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint([p for p in p_temp.iterdir() if p.is_dir()])
# [PosixPath('temp/dir')]

ファイル名(basename)のみを抽出

ファイル名(basename)はname属性で取得できる。ディレクトリのname属性はディレクトリ名そのものになる。

この例ではis_file()と組み合わせてディレクトリは除外してファイル名のみ抽出している。

pprint.pprint([p.name for p in p_temp.iterdir() if p.is_file()])
# ['[x].txt', 'aaa.text', '1.txt', '12.text', '123.txt']

条件で抽出したファイルを削除

glob()の条件で抽出したファイルをunlink()で削除する。

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search('\d+\.txt', str(p))])
# [PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

for p in p_temp.glob('**/*'):
    if re.search('\d+\.txt', str(p)) and p.is_file():
        p.unlink()

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search('\d+\.txt', str(p))])
# []

以下のようにリスト内包表記で一行で書くことも可能。

[p.unlink() for p in p_temp.glob('**/*') if re.search('\d+\.txt', str(p)) and p.is_file()]

ディレクトリを削除したい場合はis_dir()で判定した上でshutli.rmtree()を使う。Pathオブジェクトのメソッドrmdir()は空のディレクトリのみが対象なので注意。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事