4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

フォルダごとに階層化されていたファイルをフラットにしながらコピーする

Last updated at Posted at 2017-08-13

概要

  • フォルダごとに階層化されていたファイルをフラットにしながらコピーします。
  • shutilとかいろいろ探したのですが、見つからなかったのでちょいプロ(ちょっとしたプログラム)つくりました

基本動作

フォルダごとに階層化されていたファイル
└─test
   └─a
       ├─01
       │      1.xlsx
       │
       └─02
           ├─01
           │      2.xlsx
           │
           └─02
               └─01
                       03.txt

↓こうします。(test2/b以下にはフォルダ階層がない)

フラットに
├─test
│  └─a
│      ├─01
│      │      1.xlsx
│      │
│      └─02
│          ├─01
│          │      2.xlsx
│          │
│          └─02
│              └─01
│                      03.txt
│
└─test2
    └─b
            03.txt
            1.xlsx
            2.xlsx

使い方

In [16]: import copy_flat

In [17]: copy_flat.copy_flat(r"test\a", r"test2\b")
3 files is to be copied.
    1/    3: ==> test2\b\1.xlsx
    2/    3: ==> test2\b\2.xlsx
    3/    3: ==> test2\b\03.txt
すでに同名ファイルがあってコピーしないとき
In [24]: copy_flat.copy_flat(r"test\a", r"test2\b")
3 files is to be copied.
    1/    3: ignored. it already exists. test2\b\1.xlsx
    2/    3: ignored. it already exists. test2\b\2.xlsx
    3/    3: ignored. it already exists. test2\b\03.txt
  • パスは相対パスでも絶対パスでもOKです。
  • Windowsのフォルダによく作られてしまうThumb.dbは、一回コピーしたらスキップしたい・・・というイメージでつくってます。

コード

copy_flat.py
# -*- coding: utf-8 -*-
import os
import pathlib
import shutil

def copy_flat(src, dest):

    # コピー元ファイルリスト化しておく(if f.is_file()でフォルダを除外している)
    src = pathlib.Path(src)
    src = [f for f in src.glob("**/*") if f.is_file()]

    # コピー先のディレクトリがなければ作成しておく(os.makedirsで階層構造も一気に作成する)
    #   http://www.gesource.jp/programming/python/code/0009.html
    dest = pathlib.Path(dest)
    if not dest.is_dir():
        os.makedirs(dest)

    # コピー先ファイルリスト化しておく(コピー先のフォルダ名とコピー元の"ファイル名"をパス結合(/)しておく)
    #   https://docs.python.jp/3/library/pathlib.html
    #   11.1.2.2. 演算子
    dest = [ (dest / f.name) for f in src]

    print("{0} files is to be copied.".format(len(src)))

    # ファイルをコピーする(zipで複数のリストをまとめてループする)
    # http://python.civic-apps.com/zip-enumerate/s
    for i, (s, d) in enumerate(zip(src, dest)):
        print("{0:>5}/{1:>5}: ".format(i+1, len(src)), end="")
        if d.is_file():
            print("ignored. it already exists. {0}".format(d))
        else:
            # ファイルをコピー
            shutil.copy(s, d)
            print("==> {0}".format(d))

改造

  • 複数のフォルダパスを入力として受け取れるようにした
    • コードからはリストとしてで受け取る
  • コマンドラインで実行できるようにした
    • -iのパラメータで複数フォルダパスを指定できるようにした

(「フォルダかどうかはちゃんとチェックしてない」とかあるけど、これ以上やるとちょいプロというには手間をかけすぎになるのでやめる)

In [59]: copy_flat.copy_flat(r"test\a", r"test2\b")
4 files is to be copied.
    1/    4: ==> test2\b\test.txt
・・・略・・・

In [60]: copy_flat.copy_flat([r"test\a",  r"test2\b"], r"test3\c\d")
8 files is to be copied.
    1/    8: ==> test3\c\d\test.txtt
・・・略・・・

In [61]: %run copy_flat.py -i test\a -o test4
4 files is to be copied.
    1/    4: ==> test4\test.txt
・・・略・・・
copy_flat.py
# -*- coding: utf-8 -*-
import os
import pathlib
import shutil
from argparse import ArgumentParser

def copy_flat(src, dest):
    # コピー元ファイルリスト化しておく(if f.is_file()でフォルダを除外している)
    src = _make_file_list(src)

    # コピー先のディレクトリがなければ作成しておく(os.makedirsで階層構造も一気に作成する) - http://www.gesource.jp/programming/python/code/0009.html
    dest = _get_dest_folder(dest)
    if not dest.is_dir():
        os.makedirs(dest)

    # コピー先ファイルリスト化しておく(コピー先のフォルダ名とコピー元の"ファイル名"をパス結合(/)しておく) - https://docs.python.jp/3/library/pathlib.html - 11.1.2.2. 演算子
    dest = [ (dest / f.name) for f in src]

    print("{0} files is to be copied.".format(len(src)))

    # ファイルをコピーする(zipで複数のリストをまとめてループする) - http://python.civic-apps.com/zip-enumerate/
    for i, (s, d) in enumerate(zip(src, dest)):
        print("{0:>5}/{1:>5}: ".format(i+1, len(src)), end="")
        if d.is_file():
            print("ignored. it already exists. {0}".format(d))
        else:
            # ファイルをコピー
            shutil.copy(s, d)
            print("==> {0}".format(d))

def _set_argparser():
    # 【Python】コマンドライン引数を解析する(argparseの利用) - Flat Leon Work - http://flat-leon.hatenablog.com/entry/python_argparse
    parser = ArgumentParser(
    description = '''
    This script is used to copy the files in structured folders to a folder.(this script copies the files flatly)
    ''')

    # 16.4.3. add_argument() メソッド(原文)
    # ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
    parser.add_argument(
        "-i",
        type=str,
        nargs = '+',
        required = True,
        help = '''
        The source folder paths to the structured folders which inlude some files.
        Absolute or relative path can be used.
        This parameter must include more than one(1) absolute or relative path.
        ''')

    parser.add_argument(
        "-o",
        type = str,
        nargs = 1,
        required = False,
        default = [os.getcwd()],
        help = '''
        The destination folder path will store the files.
        if not set, current working folder(os.getcwd()) is to be used.
        ''')

    return parser.parse_args()

def _make_file_list(srcs):
    file_list = []
    if isinstance(srcs, list):
        for s in srcs:
            file_list += [f for f in pathlib.Path(s).glob("**/*") if f.is_file()]
    elif isinstance(srcs, str):
        file_list += [f for f in pathlib.Path(srcs).glob("**/*") if f.is_file()]
    else:
        print("Error. unkown instance at _make_file_list.")
    return file_list


def _get_dest_folder(dest):
    dest_folder = ""
    if isinstance(dest, list):
        if len(dest) > 0:
            dest_folder = str(dest[0])
    elif isinstance(dest, str):
        dest_folder = dest
    else:
        print("Error. unkown instance at _get_dest_folder.")

    # https://support.microsoft.com/ja-jp/help/879749
    # スラッシュ (/)、円記号 (\)、不等号記号 (<>)、アスタリスク (*)、疑問符 (?)、ダブル クォーテーション (")、縦棒 (|)、コロン (:)、セミコロン (;) 
    # の置き換えをしたほうがいいかも
    return pathlib.Path(dest_folder)

if __name__ == '__main__':

    args = _set_argparser()

    copy_flat(args.i, args.o)

関係ないけど

ちょいプロ(ちょっとしたプログラム)って、Rubyを256倍使うための本 邪道編とかで使われてて個人的に好きなんだけど、ネットだとBorland C++ Builderでちょいプロ。くらいでしか使われてない。
別のはやりの言い方があるんだろうか。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?