find & rename files & mv: 条件にあったファイルをmvし一つのディレクトリに集約する

find command 2/N

公開日: 2023-08-05
更新日: 2024-04-16

  Table of Contents

Problem Setting 1

カレントディレクトリ以下に以下のような形のフォルダ構造が与えられたとします:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
% tree
.
├── loc_A
│   ├── dir_a
|   |   ├── file_A_1.csv
|   |   ├── file_A_2.csv
|   |   ├──...
|   |   ├── file_A_8.csv
|   |   └── file_A_9.csv
│   └── dir_b
|       ├── file_A_1.csv
|       ├── file_A_2.csv
|       ├──...
|       ├── file_A_8.csv
|       └── file_A_9.csv
└── loc_B
    ├── dir_a
    |   ├── file_B_1.csv
    |   ├── file_B_2.csv
    |   ├──...
    |   ├── file_B_8.csv
    |   └── file_B_9.csv
    └── dir_b
        ├── file_B_1.csv
        ├── file_B_2.csv
        ├──...
        ├── file_B_8.csv
        └── file_B_9.csv
  • locationごとに収集したcsv形式ログをloc_A, loc_Bに格納(アンダースコア以下のアルファベットはロケーションを表しているとする)
  • loc_Aloc_B以下にそれぞれdir_a, dir_bが与えられている
  • dir_a, dir_b以下にファイルがあるがfile_<location-name>_[1-9].csvという構造になっている

上記のフォルダ構造をファイルの移動によって以下のような構造に変更したいケースを考えます:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% tree
.
├── dir_a
│   ├── file_A_1.csv
│   ├── file_A_2.csv
│   ├── ...
│   ├── file_A_8.csv
│   ├── file_A_9.csv
│   ├── file_B_1.csv
│   ├── file_B_2.csv
│   ├── ...
│   ├── file_B_8.csv
│   └── file_B_9.csv
└── dir_b
    ├── file_A_1.csv
    ├── file_A_2.csv
    ├── ...
    ├── file_A_8.csv
    ├── file_A_9.csv
    ├── file_B_1.csv
    ├── file_B_2.csv
    ├── ...
    ├── file_B_8.csv
    └── file_B_9.csv

point

  • dir_a/, dir_b以下のファイルはそれぞれ別の名前になっている
  • 移動後, 空になったディレクトリは削除する

解説

Solution

1
2
3
4
% mkdir ./dir_a ./dir_b
% find . -type f -path '*loc_*/dir_a/*' -name '*.csv' | xargs -I{} mv {} ./dir_a/
% find . -type f -path '*loc_*/dir_b/*' -name '*.csv' | xargs -I{} mv {} ./dir_b/
% find . -type d -empty -delete
  • 格納場所のdirectoryを先に作成
  • find commandでファイルリストを取得し, xargsを用いて1つずつmv
  • 最後に空になったdirectoryを削除

file typeの指定

1
find <search-path> -type f -name '*.csv'
option 説明  
-type f fを指定することでファイルのみを検索対象とする  
-name '*.csv' -nameは検索ファイル名を指定するコマンドだが, .hogehogeというsurfixルールを活かすこ とで実質的に拡張子限定をすることができる

なお, UNIX/Linuxでは, 原則としてファイル名に拡張子は必要なく, 基本的にその付与はユーザーやアプリケーションの裁量となることに留意が必要です = find commandで拡張子検索はあくまでファイル名検索のパラダイムで実現される.

検索PATHの指定: -path option

file nameだけでなく, pathをサーチ条件に含めたい場合は, -path optionを利用します. ただし, 次の点に留意が必要です:

1
2
Note that the pattern match test applies to the whole file name, 
starting from one of the start points named on the command line.

結局の所, pathを含んだfile nameを対象に検索しているので, 上記のケースでは次のようなコードでも機能します:

1
find . -type f -path '*loc_*/dir_a/*.csv' 

-name optionとの違いは, 次のメッセージに集約されます:

1
2
‘-name’ matches against basenames only, 
but the given pattern contains a directory separator (‘/’)

empty directoryの削除

1
find <search-path> -type d -empty -delete

search-path以下の空ディレクトリを削除することができます. 通常は見えないドットファイルやドットディレクトリも踏まえた上でempty判定してくれます.

Problem Setting 2: file names could not be uniquely assigned

上記と異なり, 以下のようにfile nameがかぶっているケースを考えます.

  • これを同じくdir_a, dir_bにassignしたいとします
  • assignにあたってfile nameにlocation-name(loc_A)を付与した上で移動したいとします
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% tree
.
├── loc_A
│   ├── dir_a
│   │   ├── file_1.csv
│   │   ├── file_2.csv
│   │   ├── ...
│   │   └── file_9.csv
│   └── dir_b
│       ├── file_1.csv
│       ├── file_2.csv
│       ├── ...
│       └── file_9.csv
└── loc_B
    ├── dir_a
    │   ├── file_1.csv
    │   ├── file_2.csv
    │   ├── ...
    │   └── file_9.csv
    └── dir_b
        ├── file_1.csv
        ├── file_2.csv
        ├── ...
        └── file_9.csv

上記の構造から以下の構造へ処理することをゴールとします

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% tree
.
├── dir_a
│   ├── loc_A_file_1.csv
│   ├── loc_A_file_2.csv
│   ├── ...
│   ├── loc_A_file_8.csv
│   ├── loc_A_file_9.csv
│   ├── loc_B_file_1.csv
│   ├── loc_B_file_2.csv
│   ├── ...
│   ├── loc_B_file_8.csv
│   └── loc_B_file_9.csv
└── dir_b
    ├── loc_A_file_1.csv
    ├── loc_A_file_2.csv
    ├── ...
    ├── loc_A_file_8.csv
    ├── loc_A_file_9.csv
    ├── loc_B_file_1.csv
    ├── loc_B_file_2.csv
    ├── ...
    ├── loc_B_file_8.csv
    └── loc_B_file_9.csv

解説

Solution

1
2
3
4
% mkdir ./dir_a ./dir_b
% find . -type f -path '*loc_*/dir_a/*' -name '*.csv' |  awk '{X=$1; sub(/\/dir_a\//, "_", X);$1=$1 "\t" X;print $0}' | xargs -L1 bash -c 'mv $0 ./dir_a/$(basename $1)'
% find . -type f -path '*loc_*/dir_b/*' -name '*.csv' |  awk '{X=$1; sub(/\/dir_b\//, "_", X);$1=$1 "\t" X;print $0}' | xargs -L1 bash -c 'mv $0 ./dir_b/$(basename $1)'
% find . -type d -empty -delete
  • find commadでファイルを検索した
  • awkでoriginal file nameと名称変更後のfile nameを定義 & 出力
  • xargsmvコマンドを実行する

-L optionがないとdefaultではline by lineで引数を認識してくれないので入れることが必要です. 挙動確認の例として

1
2
3
4
5
6
7
8
9
10
## with -L option
% echo "x y z\na b c" | xargs -L1 bash -c 'echo this is first:$0 second:$1 third:$2'

this is first:x second:y third:z
this is first:a second:b third:c

## without -L option
% echo "x y z\na b c" | xargs bash -c 'echo this is first:$0 second:$1 third:$2'

this is first:x second:y third:z

-i, -l optionについて

Note that

1
2
3
The -l and -i options appear in the 1997 version of the POSIX standard,
but  do  not appear in the 2004 version of the standard.  Therefore you
should use -L and -I instead, respectively.

Appendix: Data Generating Process

ptouch command

1
2
3
4
5
6
7
8
$ cat $(which ptouch)
#!/usr/bin/bash
## Author: Ryo Nakagami
## Revised: 2023-08-09

set -e

mkdir -p "$(dirname "$1")"

data generating process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
% for loc in {A..B}; do for n ({1..9}); do ptouch "./loc_$loc/dir_a/file_${loc}_$n.csv"; done; done
% for loc in {A..B}; do for n ({1..9}); do ptouch ./loc_$loc/dir_b/file_${loc}_$n.csv; done; done
% tree
.
├── loc_A
│   ├── dir_a
│   │   ├── file_A_1.csv
│   │   ├── file_A_2.csv
│   │   ├── file_A_3.csv
│   │   ├── file_A_4.csv
│   │   ├── file_A_5.csv
│   │   ├── file_A_6.csv
│   │   ├── file_A_7.csv
│   │   ├── file_A_8.csv
│   │   └── file_A_9.csv
│   └── dir_b
│       ├── file_A_1.csv
│       ├── file_A_2.csv
│       ├── file_A_3.csv
│       ├── file_A_4.csv
│       ├── file_A_5.csv
│       ├── file_A_6.csv
│       ├── file_A_7.csv
│       ├── file_A_8.csv
│       └── file_A_9.csv
└── loc_B
    ├── dir_a
    │   ├── file_B_1.csv
    │   ├── file_B_2.csv
    │   ├── file_B_3.csv
    │   ├── file_B_4.csv
    │   ├── file_B_5.csv
    │   ├── file_B_6.csv
    │   ├── file_B_7.csv
    │   ├── file_B_8.csv
    │   └── file_B_9.csv
    └── dir_b
        ├── file_B_1.csv
        ├── file_B_2.csv
        ├── file_B_3.csv
        ├── file_B_4.csv
        ├── file_B_5.csv
        ├── file_B_6.csv
        ├── file_B_7.csv
        ├── file_B_8.csv
        └── file_B_9.csv

References



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)