
Python では、標準ライブラリの osモジュールや shutilモジュールを利用することで、OSが管理しているディレクトリの操作や情報を取得することができる。今回はこれらのモジュールを利用し、ファイル一覧を表示したり、パターンに一致するファイルを数えたり、ディレクトリごとバックアップするプログラムを作ってみる。
目次
サンプル・プログラム
ファイル一覧を表示する
プログラム "printDir1.py" をご覧いただきたい。変数 PATH で指定したディレクトリのファイルやディレクトリ一覧を表示するプログラムである。DOSコマンドの dirと同様の働きをする。
import os
import datetime
import pahooValidate as pv
# メイン・プログラム =======================================================
# 初期値
PATH = "./program" # ディレクトリ
try:
for fname in os.listdir(PATH):
# フルパス名
fullname = os.path.join(PATH, fname)
# タイムスタンプ
ti = str(datetime.datetime.fromtimestamp(os.path.getctime(fullname)))
# ファイルの場合
if not os.path.isdir(fullname):
size = float(os.path.getsize(fullname)) / 1024
print(f"{fname:<20} {size:8.1f}KB {ti[:16]}")
# サブディレクトリの場合
else:
print(f"{fname:<20} <div> {ti[:16]}")
# その他の例外処理
except Exception as errmsg:
pv.dispErrorMessageAndExit(f"エラーが発生しました -- {errmsg}")
まず、ファイルシステムを扱うには、OSの機能を呼び出す osモジュールを importする。ファイルやディレクトリのタイム医スタンプを表示したいので、日付や時間を扱う datetimeモジュールも importする。

表示させたいディレクトリは変数 PATH に代入する。各自の環境に合わせて設定してもらいたい。絶対パスで指定してもよい。

ファイル操作なので例外が発生する可能性がある。そこで、全体を try文で囲んで例外処理できるようにしておく。

os.listdir は、引数で指定したディレクトリにあるファイル名やサブディレクトリ名をリスト型で返す。ただし、自ディレクトリ "." と親ディレクトリ ".." はリストに含まない。

for文を使って、1つ1つのファイルやサブディレクトリの情報を取得していく。
情報を取得するのに、パス名を含めたフルパス名が必要である。OSによってディレクトリの区切り文字に差異があるので、os.path.join を使って、ディレクトリ名 PATH とファイル名 fname を結合し、変数 fullname に代入する。
os.path.getctime を使って、ファイルの作成日時(Linuxでは最後にメタデータが変更された日時)を取得する。戻り値は UNIX Epoch(1970年1月1日午前0時0分0秒からの経過秒数)なので、datetime.datetime.fromtimestamp を使って視認可能なテキストに変換してやる。ファイルのタイムスタンプ取得には、これ以外に、os.path.getatime、os.path.getmtime があり、何を取得するかはリンク先を参考にしてほしい。

エントリがサブディレクトリでなければ――os.path.isdir が偽であれば――os.path.getsize を使ってファイルサイズを取得する。戻り値の単位はバイトだ。
サブディレクトリの場合は、ファイルサイズの代わりに文字列 "<dir>" を表示する。

表示させたいディレクトリは変数 PATH に代入する。各自の環境に合わせて設定してもらいたい。絶対パスで指定してもよい。

ファイル操作なので例外が発生する可能性がある。そこで、全体を try文で囲んで例外処理できるようにしておく。

os.listdir は、引数で指定したディレクトリにあるファイル名やサブディレクトリ名をリスト型で返す。ただし、自ディレクトリ "." と親ディレクトリ ".." はリストに含まない。

for文を使って、1つ1つのファイルやサブディレクトリの情報を取得していく。
情報を取得するのに、パス名を含めたフルパス名が必要である。OSによってディレクトリの区切り文字に差異があるので、os.path.join を使って、ディレクトリ名 PATH とファイル名 fname を結合し、変数 fullname に代入する。
os.path.getctime を使って、ファイルの作成日時(Linuxでは最後にメタデータが変更された日時)を取得する。戻り値は UNIX Epoch(1970年1月1日午前0時0分0秒からの経過秒数)なので、datetime.datetime.fromtimestamp を使って視認可能なテキストに変換してやる。ファイルのタイムスタンプ取得には、これ以外に、os.path.getatime、os.path.getmtime があり、何を取得するかはリンク先を参考にしてほしい。

エントリがサブディレクトリでなければ――os.path.isdir が偽であれば――os.path.getsize を使ってファイルサイズを取得する。戻り値の単位はバイトだ。
サブディレクトリの場合は、ファイルサイズの代わりに文字列 "<dir>" を表示する。
サブディレクトリを含めて一覧表示する
プログラム "printDir2.py" は、"printDir1.py" の一覧表示部分をユーザー関数にして、それを再帰呼び出しすることで、サブディレクトリを含めて一覧表示するプログラムだ。DOSコマンドの "dir /S" と同様の働きをする。再帰呼び出しについては、「5.3 再帰呼び出し」で学んだ通り。
import os
import datetime
import pahooValidate as pv
def dispDir(path):
"""指定ディレクトリのファイル/サブディレクトリを一覧表示する.
Args:
path(str): ディレクトリ名
Returns:
None
Raises:
エラー表示してプログラム終了
"""
try:
print(path)
subdir = [] # サブディレクトリ格納用リスト
for fname in os.listdir(path):
# フルパス名
fullname = os.path.join(path, fname)
# タイムスタンプ
ti = str(datetime.datetime.fromtimestamp(os.path.getctime(fullname)))
# ファイルの場合
if not os.path.isdir(fullname):
size = float(os.path.getsize(fullname)) / 1024
print(f"{fname:<20} {size:8.1f}KB {ti[:16]}")
# サブディレクトリの場合
else:
print(f"{fname:<20} <div> {ti[:16]}")
# サブディレクトリ・リストに追加
subdir.append(fullname)
# サブディレクトリの内容を表示(再帰呼び出し)
for subpath in subdir:
print("")
dispDir(subpath)
# その他の例外処理
except Exception as errmsg:
pv.dispErrorMessageAndExit(f"エラーが発生しました -- {errmsg}")
# メイン・プログラム =======================================================
# 初期値
PATH = "./program" # ディレクトリ
dispDir(PATH)
"printDir1.py" のメイン・プログラムを、ユーザー関数 dispDir にした。
サブディレクトリは、いったんリスト subdir に格納しておき、そのディレクトリの表示が終わったら、1つ1つを dispDir の再帰呼び出しで処理している。
サブディレクトリが見つかった時点で再帰呼び出しをすると、ディレクトリのツリー構造として表示することができる。これは課題として、各自で解いてみてほしい。
サブディレクトリは、いったんリスト subdir に格納しておき、そのディレクトリの表示が終わったら、1つ1つを dispDir の再帰呼び出しで処理している。
サブディレクトリが見つかった時点で再帰呼び出しをすると、ディレクトリのツリー構造として表示することができる。これは課題として、各自で解いてみてほしい。
パターンに一致するファイルを数える
プログラム "countFiles.py" は、"printDir2.py" のユーザー関数 dispDir を、表示ではなくファイル数を数える処理に置き換えた。さらに、あらかじめ指定したパターン(ワイルドカード指定可能)に合致するファイルのみを数えたり、サブディレクトリを再帰的に検索する処理のON/OFFを切り替えるようにした。
import os
import fnmatch
import pahooValidate as pv
def countFiles(path, pattern="*.*", subDir=False):
"""指定ディレクトリでextにマッチするファイルを数える
Args:
path(str): ディレクトリ名
pattern(str): マッチするパターン;ワイルドカード指定可能(デフォルトは *.*)
sunDir(bool): サブディレクトリを再帰的に検索するかどうか(デフォルトFalse)
Returns:
int: マッチするファイルの数
Raises:
エラー表示してプログラム終了
"""
count = 0
try:
for fname in os.listdir(path):
# フルパス名
fullname = os.path.join(path, fname)
# ファイルの場合
if not os.path.isdir(fullname):
# 拡張子がマッチ
if fnmatch.fnmatch(fname, pattern):
count += 1
# サブディレクトリ かつ subDir=Trueの場合
else if subDir:
count += countFiles(fullname, pattern, subDir)
# その他の例外処理
except Exception as errmsg:
pv.dispErrorMessageAndExit(f"エラーが発生しました -- {errmsg}")
return count
# メイン・プログラム =======================================================
# 初期値
PATH = "./program" # ディレクトリ
PATTERN = "*.py" # 拡張子 .py
# count = countFiles(PATH, PATTERN, True)
count = countFiles(PATH)
print(f"{PATH} の下には {PATTERN} にマッチするファイルが {count}個あります.")
前述のように"printDir2.py" のユーザー関数 dispDir を改良し、ファイル数を返す countFiles 関数にした。引数として、ファイルのパターンを指定する pattern、サブディレクトリを再帰的に検索するスイッチ subDir を加えた。それぞれデフォルト値を指定しており、呼び出し時に引数を省略するとデフォルト値が代入される。

ファイルである場合には、UNIXのファイルパターンマッチと同等のことができる fnmatch.fnmatch を使ってマッチングさせる。第1引数にはファイル名を、第2引数にはパターンを指定する。マッチすると True が返るので、ファイル数を数える変数 count に1を加える。
fnmatch.fnmatch を利用するには、標準モジュール fnmatch をimportする必要がある。

サブディレクトリかつ、引数 subDir が True の場合には、再帰的にサブディレクトリを検索し、返ってくるファイル数を加算する。

ファイルである場合には、UNIXのファイルパターンマッチと同等のことができる fnmatch.fnmatch を使ってマッチングさせる。第1引数にはファイル名を、第2引数にはパターンを指定する。マッチすると True が返るので、ファイル数を数える変数 count に1を加える。
fnmatch.fnmatch を利用するには、標準モジュール fnmatch をimportする必要がある。

サブディレクトリかつ、引数 subDir が True の場合には、再帰的にサブディレクトリを検索し、返ってくるファイル数を加算する。
指定ディレクトリをバックアップする
プログラム "backupDir.py" をご覧いただきたい。変数 SOUR で指定したディレクトリのファイルやサブディレクトリを丸ごと、変数 DEST で指定したディレクトリにバックアップ(コピー)する。バックアップ先ディレクトリ名に年月日時分秒を自動的に付与する。
import os
import shutil
import datetime
import pahooValidate as pv
# メイン・プログラム =======================================================
# 初期値
SOUR = "./program" # バックアップ元ディレクトリ
DEST = "./" # バックアップ先ディレクトリ
try:
# 現在日時
now = datetime.datetime.now()
# バックアップ先のフルパス名
destDir = os.path.join(DEST, now.strftime("backup_%Y%m%d_%H%M%S"))
if os.path.isdir(destDir):
pv.dispErrorMessageAndExit(f"エラーが発生しました -- バックアップ先ディレクトリ{destDir}が存在します")
ディレクトリを丸ごとコピーする機能を使うため、標準ライブラリ shutil を importする。
現在日時 datetime.datetime.now() を取得し、strftime を使って YYYYMMDD-HHMMSS に変換し、サブディレクトリ名 "backup_"の後に結合して、変数 destDir に代入する。

コピーする前に、バックアップ先のフルパス名 destDir と同じディレクトリが存在しないかどうかを os.path.isdir を使って確認する。あれば、エラーメッセージを表示してプログラムを終了する。
無ければ、shutil.copytree を使って丸ごとコピーする。

これを実用的なツールにするためには、バックアップ先に十分な空き容量があるかなどを調べてから shutil.copytree を実行したほうがいいだろう。これは課題ということで、各自で取り組んでみてほしい。
現在日時 datetime.datetime.now() を取得し、strftime を使って YYYYMMDD-HHMMSS に変換し、サブディレクトリ名 "backup_"の後に結合して、変数 destDir に代入する。

コピーする前に、バックアップ先のフルパス名 destDir と同じディレクトリが存在しないかどうかを os.path.isdir を使って確認する。あれば、エラーメッセージを表示してプログラムを終了する。
無ければ、shutil.copytree を使って丸ごとコピーする。

これを実用的なツールにするためには、バックアップ先に十分な空き容量があるかなどを調べてから shutil.copytree を実行したほうがいいだろう。これは課題ということで、各自で取り組んでみてほしい。
練習問題
次回予告
Python では、インターネット上のコンテンツをファイル入力と同じくらい簡単に取得することができる。そこで次回は――指定したURLにあるHTMLファイルを取得しコンソールに表示する、指定したURLにあるHTMLファイルからTITLEタグの内容を取り出してコンソールに表示する、RSS情報を表示する――という3つのプログラムをつくり、インターネット・コンテンツを扱うためのHTTP通信の基本について学んでいく。
なお、インターネットは信頼境界の外側にあることから、バリデーションなどを行いセキュリティ対策に留意する。
なお、インターネットは信頼境界の外側にあることから、バリデーションなどを行いセキュリティ対策に留意する。
コラム:ディスク・オペレーティング・システム

IBM System/360 Model 65
IBMが1964年(昭和39年)4月に発表したメインフレーム(汎用コンピュータ)「System/360」は、1977年(昭和52年)まで出荷された人気モデルだった。当初、外部記憶装置は磁気テープであったことから、TOS/360 (Tape Operating System/360)というOS(基本ソフトウェア)が搭載されていた。その後、磁気ディスク装置(フロッピーディスクドライブやハードディスクドライブ)の普及に伴い、1966年(昭和41年)に DOS/360(Disk Operating System/360)の販売が始まった。
ディスク装置はテープ装置と異なり、自由にファイルを書き込んだり消去したりすることが可能で、ファイルの頭から任意の位置のデータを読み書きすることもできた(ランダム・アクセス機能)。また、階層化ディレクトリにも対応できるようになった。
磁気ディスク装置を制御する機能はOSの重要な役割の1つとなり、その機能を持つOSを DOS と呼ぶようになった。

1976年(昭和51年)にデジタルリサーチ社が8ビット・パソコン向けにリリースしたOS「CP/M」のコア部分は BDOS(Basic Disk Operating System)と呼ばれる。
1981年(昭和56年)にIBMが発売した16ビット・パソコン IBM PCには、IBM PC DOS が搭載されており、これを開発したマイクロソフトから販売されたOSには MS-DOS の名前が与えられた。
もちろん、macOSやUNIX, LinuxもDOS機能を備えている。

DOSによって扱えるファイルシステムが異なる。
FAT はMD-DOSのファイルシステムとして登場し、その後、多くのDOSがサポートするようになった。扱えるファイルシステムの大きさによって、FAT12, FAT16 FAT32, exFATがある。
NTFS はWindows NT移行で採用されたファイルシステムで、FATより大容量のファイルシステムに対応しており、検索の高速化と、長いファイル名に対応した。
HFS は MacOS用のファイルシステムで、Mac OS X 10.5まで使われていた。データ本体とは別にリソースフォークと呼ばれる情報を持ち、アイコンやメニュー定義などMacOS特有の情報を格納することができた。これはHFS+、APFSと進化していく。
Linuxには複数のファイルシステムが混在している。最初期には ex2 だったが、その後、ext3 や ext4 に進化していく。

ファイルシステムが異なると、そのディスク(USBメモリを含む)のデータを読み書きできないことがある。
本編で紹介した Python のファイルシステム処理は、osモジュールや shutilモジュールがファイルシステムの違いを吸収していることから同じプログラウで動いているのであって、実際にファイルシステム上に作成されるファイル構造は、WindowsとLinuxとでは互換性はない。
磁気ディスク装置を制御する機能はOSの重要な役割の1つとなり、その機能を持つOSを DOS と呼ぶようになった。

1976年(昭和51年)にデジタルリサーチ社が8ビット・パソコン向けにリリースしたOS「CP/M」のコア部分は BDOS(Basic Disk Operating System)と呼ばれる。
1981年(昭和56年)にIBMが発売した16ビット・パソコン IBM PCには、IBM PC DOS が搭載されており、これを開発したマイクロソフトから販売されたOSには MS-DOS の名前が与えられた。
もちろん、macOSやUNIX, LinuxもDOS機能を備えている。

DOSによって扱えるファイルシステムが異なる。
FAT はMD-DOSのファイルシステムとして登場し、その後、多くのDOSがサポートするようになった。扱えるファイルシステムの大きさによって、FAT12, FAT16 FAT32, exFATがある。
NTFS はWindows NT移行で採用されたファイルシステムで、FATより大容量のファイルシステムに対応しており、検索の高速化と、長いファイル名に対応した。
HFS は MacOS用のファイルシステムで、Mac OS X 10.5まで使われていた。データ本体とは別にリソースフォークと呼ばれる情報を持ち、アイコンやメニュー定義などMacOS特有の情報を格納することができた。これはHFS+、APFSと進化していく。
Linuxには複数のファイルシステムが混在している。最初期には ex2 だったが、その後、ext3 や ext4 に進化していく。

ファイルシステムが異なると、そのディスク(USBメモリを含む)のデータを読み書きできないことがある。
本編で紹介した Python のファイルシステム処理は、osモジュールや shutilモジュールがファイルシステムの違いを吸収していることから同じプログラウで動いているのであって、実際にファイルシステム上に作成されるファイル構造は、WindowsとLinuxとでは互換性はない。
(この項おわり)