Pythonを利用して
- ディレクトリ配下全てのファイル名を取得したい
- ファイル名を特定キーワードで絞り込みたい
- 特定の拡張子のみを対象にしたい
- ファイルが壊れないように安全にRnameした
と思っていませんか?
Pythonで対応可能ですがいくつか気を付けたて点があります。
というのも私がPythonで自動化した結果、失敗したな~と思うことが多々あり押さえておきたいポイントがあると感じたからです。
例えば下記ケースです。
- ファイルを開いている時にリネームしてファイルが壊れる
- ファイル名だけ変更のつもりがディレクトリ名も変わってしまった
- ファイルのリネームを間違い元に戻すのが大変
リネームは便利な反面、誤操作によりファイル破壊や意図せぬ動作が起こり、影響範囲が大きいです。
今回は私が多くの失敗から学んだ経験を活かし、特定条件での検索方法や安全にRnameする方法をご紹介していきます。
- Pythonを使った意図したリネーム方法を知りたい
- ファイルを壊さずにリネームしたい
- ディレクトリ配下のサブディレクトリも含めて一括でリネームしたい
拡張子を意識すると便利な理由
ディレクトリから対象ファイルを機械的に抽出する時は目で見ることはなく、ディレクトリ構造が複雑な程どのようなファイルがあるかわかりません。その状態でデータ抽出するということは意図しないファイルを抽出する可能性が高いことを意味します。
データを抽出するということは対象となる拡張子が明確になっていることが多く、拡張子でまず対象を絞り込み、その後キーワードで更に絞り込む方が意図した結果になります。
今回の実践の流れ
それぞれのタスク別にPython関数を作成し、最後に関数をまとめて実行する流れです。
- 対象の拡張子のみを抽出する方法
- ファイル名に対象KWを含むものを抽出する方法
ライブラリのimport
今回の検証を実施する為に事前にライブラリをインポートしておきます。
import glob,os
import pandas as pd
import re
import psutil
ディレクトリ構造と中身
私のデスクトップ上にあるディレクトリ「第1階層」を利用して検証します。第1階層ディレクトリをサブディレクトリ含めて再帰的探索を行い、抽出した結果が下記です。
['C:\\Users\\abi00\\Desktop\\第1階層\\', 'C:\\Users\\abi00\\Desktop\\第1階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\image_1.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\image_2.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_2.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\image_1.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\image_2.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_2.csv']
第2階層までのディレクトリに拡張子(.txt .jpg .csv)があることがわかります。
対象ディレクトリ内を再帰的探索する方法
再帰的探索とは
探索アルゴリズムの1つで、再帰的な呼び出しを使用して問題の解を求める手法です。ディレクトリ構造での再帰的探索と考えるとrootディレクトリ以下のすべてのサブディレクトリを全て検索して、対象キーワードに該当するファイルパスのリストを取得します。
再帰的探索はglob.globの「recursiv=Trueにする」
以下のコードは、glob.glob を使用して指定ディレクトリ配下のサブディレクトリも含めてファイルを再帰的に探索し抽出する方法です。再帰的探索はglobのrecursive=Trueを指定することで可能になります(デフォルトはFalse)
input_dir = "対象ディレクトリをここに記載"
glob.glob(input_dir + "\**",recursive = True)
glob.glob 関数にinput_dir + “**” を渡すことでディレクトリとそのサブディレクトリ内の全てのファイルが抽出されます
拡張子を指定してファイルを抽出する方法
拡張子を指定してファイル抽出する2つの方法をご紹介いたします。
if文と正規表現を使う方法
【正規表現re.searchの利用】
正規表現を使って任意の拡張子ファイルのみを抽出するにはre.searchを利用します。re.searchは指定したパターンが文字列内に存在するかどうかを調べるために使用されます。
具体的な手順は以下の通りです。
- re.searchを使ってファイル名に対象の拡張子が含まれるかチェック。
- 対象の拡張子を正規表現のorパターンを繋げて指定します。$を使うと文末指定になるので.csv$のように拡張子だけを抽出できます。
- re.search関数に対象のファイル名と正規表現パターンを渡してマッチングを行います。
- マッチしたファイルは対象拡張子のファイルとみなすことができます。
それでは上記ケースで作成したPython関数をご紹介します。
#関数
#任意ディレクトリをインプット、抽出したい拡張子を指定
def Path_List(input_dir,ext_=".csv$|.txt$"):
return [path for path in glob.glob(input_dir + "\**",recursive = True) if re.search(ext_,path)]
#実行
input_dir = r"C:\Users\abi00\Desktop\第1階層"
Path_List(input_dir)
上記コードはglob.globで対象のディレクトリ(input_dir)配下を再帰的に探索し、対象の全Pathをlist形式で抽出します。その後、for文を使ってlistからpathを1件づつ取り出し、正規表現で指定した拡張子のみを抽出し、再度list型に返す処理をしています。
['C:\\Users\\abi00\\Desktop\\第1階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_2.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_2.csv']
対象の拡張子をcsv,txtにしたのでディレクトリのみとjpgファイルが除外されたことがわかります。
参考にPython正規表現の公式ドキュメントをのせておきます。
Path分解で抽出する方法
path分解することで拡張子だけを取り出すことができるので、そこから必要な拡張子のみを絞り込む考え方です。
pathを分解するためにPythonライブラリのosを利用します。os.path.splitやos.path.basenameを使うことでディレクトリ名や拡張子を分解した値を得ることができます。
Path分解する関数を作成しましたのでご紹介します。
#path分解関数
def PathSep_(path):
dir_ = os.path.split(path)[0] #ディレクトリ名
file_ = os.path.splitext(os.path.basename(path))[0] #ファイル名拡張子抜き
ext_ = os.path.splitext(path)[1] #拡張子のみ抽出
return dir_,file_,ext_
#実行
path = r'C:\\Users\\abi00\\Desktop\\第1階層\\1234.txt'
PathSep_(path)
上記コードの実行結果です。1つのpathが3つの要素に分解されたことがわかります。
('C:\\\\Users\\\\abi00\\\\Desktop\\\\第1階層', '1234', '.txt')
最後の項目(上記例では’.txt’)が拡張子になるのでこれを絞り込むコードを作成します。絞り込む拡張子を辞書型で指定します。(リストよりも辞書の方が速い傾向があります)
input_dir = "対象ディレクトリをここに記載"
exten_ = {".csv",".txt"}
path_list_=glob.glob(input_dir + "\**",recursive = True)
#exten_ で指定した拡張子のみを抽出する
for i in path_list_:
dir_,file_,ext_=PathSep_(i)
if ext_ in exten_:
print(i)
これらの要素を利用して必要部分をリネームする処理を実施します。
ファイル名から任意キーワードで絞り込む方法
今回は拡張子だけではなくファイル名に任意のキーワードがあるものをリネーム対象から除外する方法をご紹介します。具体的にはos.path.basename(path)でファイル名を抽出して、re.searchの正規表現で対象を絞り込みます。
正規表現はRegExp_ =’^(?!.sample_1).$’を使い、「sample_1」という文字以外を抽出するように記載しました。
#sampleが入っていたら候補から除外
def TargetPath(input_path,RegExp_):
return [path for path in input_path if re.search(RegExp_,os.path.basename(path))]
#実行
input_path = Path_List(input_dir)
RegExp_ = '^(?!.*sample_1).*$'
TargetPath(input_path,RegExp_)
['C:\\Users\\abi00\\Desktop\\第1階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_2.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\1234.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_2.csv']
結果をみると下記2つが除外されたことがわかります。
‘C:\\Users\\abi00\\Desktop\\第1階層\\sample_1.csv’,
‘C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_1.csv’
ファイル名のみをrenameする方法
先程作成したpathを3要素に分解する関数をインプットとして使い、ファイル名のみを抽出後にファイル名をリネームする処理をします。具体的には既存ファイル名に1が含まれていれば値を消して既存ファイル名の最後に「リネームしたよ」を追加します。
def RenameFile_(def_PathSep_):
dir_,file_,ext_=def_PathSep_
#ファイル名をリネーム
file_ = file_.replace("1","") + "リネームしたよ"
return dir_ + "\\" + file_ + ext_
#実行
#インプットはpathを3要素に変換する関数
RenameFile_(PathSep_(path))
元のpath:'C:\\Users\\abi00\\Desktop\\第1階層\\1234.txt' path分解:('C:\\\\Users\\\\abi00\\\\Desktop\\\\第1階層', '1234', '.txt') リネーム:'C:\\\\Users\\\\abi00\\\\Desktop\\\\第1階層\\234リネームしたよ.txt'
リネームと成果物を見るとファイル名の「1」が削除され、「リネームしたよ」が追加されたことがわかります。
もし、ファイル名だけでなくpath全体を指定してリネームしていたらディレクトリ名である「第1階層」が「第階層」になります。ディレクトリ名が変わると配下のファイル名のpathも全て変わるので影響が大きいで気をつけたいところです。
リネーム時のファイル破損リスクを防止する方法
リネームするさいにファイルが開いているとファイルが壊れる可能性があるので、リネーム前にファイルが開いていないか確認する必要があります。
ファイルが開いているか確認する関数を作成しました。FileUse_check_(file_path)に確認したいfile_pathをいれてFalseがでてくればそのファイルは開いておらずリネーム可能と判断します。
#ファイルopen確認関数(Falseだと開いていない)
def FileUse_check_(file_path):
for proc in psutil.process_iter():
try:
files = proc.open_files()
for file in files:
if file.path == os.path.abspath(file_path):
return True
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return False
file_path = r"C:\Users\abi00\Desktop\第1階層\1234.txt"
FileUse_check_(file_path)
リネーム実行
今まで作成した関数をつなげて対象ディレクトリのファイル名を変更します。尚、ファイルのリネームは下記のようにosを利用します
#ファイルのリネーム方法
os.rename(befor_path,after_path)
それではリネームするための全コードをご紹介します。
#変数
input_dir = r"C:\Users\abi00\Desktop\第1階層"
RegExp_ = '^(?!.*sample_1).*$'
input_dir,ext_=".csv$|.xlsx$|.txt$"
##リネーム実行
#関数実行
input_path_1=Path_List(input_dir)
input_path_2=TargetPath(input_path_1,RegExp_)
#ループ処理でpathの変更前後リストを作成
path_list=[(befor_path,RenameFile_(PathSep_(befor_path))) for befor_path in input_path_2]
for i in path_list:
#i[0] befor_path
#i[1] after_path
try:
#ファイルの存在確認
if os.path.exists(i[0]) == True:
#ファイルが開いていないか確認
if FileUse_check_(i[0]) == False:
#リネームする
os.rename(i[0],i[1])
except Exception as e:
print(e)
下記の実行結果をみると対象のみリネームされました。
['C:\\Users\\abi00\\Desktop\\第1階層\\', 'C:\\Users\\abi00\\Desktop\\第1階層\\234リネームしたよ.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\image_1.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\image_2.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\sample_2リネームしたよ.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\234リネームしたよ.txt', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\image_1.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\image_2.jpg', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_1.csv', 'C:\\Users\\abi00\\Desktop\\第1階層\\第2階層\\sample_2リネームしたよ.csv']
コメント