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_A
とloc_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を定義 & 出力xargs
でmv
コマンドを実行する
-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
(注意:GitHub Accountが必要となります)