Linuxでファイルを分割・結合する方法

# はじめに
Linuxでファイルを分割したり結合する場合に、意外と知られていない便利な方法があるので紹介します。
各コマンドの詳細な説明は割愛しますが、ファイルの分割・結合の参考になれば幸いです。

# ファイルを分割する方法
## ・`split`コマンド(行/バイト単位で分割)
代表的なファイル分割のコマンドとして`split`コマンドがあります。指定した行数やバイト数毎にファイルを分割したり、分割するファイル数を指定できたりと、柔軟な分割を行う事ができます。

```hoge.txtの中身(id group date value)
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
```

- 行数を指定して分割 (`-l`オプション)
```bash:3行毎にファイルを分割
$ ls
hoge.txt
$ split -l 3 hoge.txt 
$ ls
hoge.txt  xaa  xab  xac  xad
$ cat xaa 
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
$ 
```

- 分割するファイル数を指定して分割 (`-n`オプション)
```bash:3つのファイルに分割(行の途中で分割しない)
$ ls
hoge.txt
$ split -n l/3 hoge.txt 
$ ls
hoge.txt  xaa  xab  xac
$ cat xaa
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
$ 
```

:::note warn
`-n`オプションを指定すると、指定したファイル数で均等に分割されます。ただ、行の途中で分割されてしまうので、それを回避する場合は`l/(ファイル数)`でファイル数を指定する必要があります。
:::

- バイト数を指定して分割 (`-b`オプション)
```bash:50バイト毎にファイルを分割
$ ls
hoge.txt
$ split -b 50 hoge.txt 
$ ls
hoge.txt  xaa  xab  xac  xad  xae
$ cat xaa 
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/
$ 
```

:::note info
通常`split`コマンドを使用すると分割したデータはファイルに出力されます。ただ、`--filter`オプションを使用する事と、分割したデータをひとまとまりとしてコマンドに渡して処理させる事ができます。

```bash:2行毎にファイルを分割し、分割データと--を標準出力
$ split -l 2 hoge.txt --filter="cat;echo --"
1 4 2022/11/16 100
2 2 2022/10/22 101
--
3 1 2022/09/01 210
4 3 2022/11/13 150
--
5 2 2022/10/16 30
6 4 2022/11/15 100
--
7 1 2022/10/16 200
8 1 2022/08/21 70
--
9 3 2022/09/08 105
10 2 2022/11/11 250
--
```

また、分割したデータ毎にコマンドの処理が実行される特性を利用すると、下記のように集計として利用する事も可能です。

```bash:2行を1つのグループとしてグループ毎にvalueを集計
$ split -l 2 hoge.txt --filter="awk '{s+=\$4}END{print s}'"
201
360
130
270
355
$ 
```

`split`コマンドは単純にファイルを分割してファイルに出力するだけでなく、分割されたデータをコマンドで処理できるので幅広く応用する事ができます。
:::

## ・`csplit`コマンド(文字列パターンで分割)
意外と知られていませんが、文字列パターン毎にファイルを分割する事ができる`csplit`というコマンドがあります。

行数やサイズではなく特定の文字列でファイルを分割する事ができるので、ログファイルやDB等のdumpファイルの調査・解析・作成する際などに有用です。

```:hoge.txtの中身(id groupid date value)
1 4 2022/11/16 100
2 2 2022/10/22 101
--
3 1 2022/09/01 210
4 3 2022/11/13 150
--
5 2 2022/10/16 30
6 4 2022/11/15 100
--
7 1 2022/10/16 200
8 1 2022/08/21 70
--
9 3 2022/09/08 105
10 2 2022/11/11 250
```

- パターンを指定して分割 (引数`//`)
```bash:--毎にマッチした全てを分割
$ ls
hoge.txt
$ csplit hoge.txt '/--/' '{*}'
38
41
40
40
42
$ ls
hoge.txt  xx00  xx01  xx02  xx03  xx04
$ cat xx00
1 4 2022/11/16 100
2 2 2022/10/22 101
$ 
```

- 行数を指定して分割(引数 数値)
```bash:3行毎に3つのファイルに分割
$ ls
hoge.txt
$ csplit hoge.txt 3 '{3}'
38
41
40
40
42
$ ls
hoge.txt  xx00  xx01  xx02  xx03  xx04
$ cat xx00
1 4 2022/11/16 100
2 2 2022/10/22 101
$ 
```

:::note warn
単純に行数で分割したい場合は`split`コマンドをお勧めします。

`csplit`コマンドでは行数を指定したファイルの分割を行えますが、分割するファイル数を繰り返し(`{*}`)として指定した場合、最後の分割でエラーが発生します。

`-k`オプションを付けると分割したファイル自体は出力されますが、あくまで指定した行数をマッチしたとして扱うため1ファイル目は指定した行数 -1しか出力しないので注意してください。

```bash:分割された1ファイル目は2行しか出力されない
$ ls
hoge.txt
$ csplit -k hoge.txt 3 '{*}'
38
41
40
40
42
csplit: '3': 範囲外の行番号 繰り返し 4 回目
$ ls
hoge.txt  xx00  xx01  xx02  xx03  xx04
$ cat xx00
1 4 2022/11/16 100
2 2 2022/10/22 101
$ cat xx01
--
3 1 2022/09/01 210
4 3 2022/11/13 150
$ 
```

ただ、指定した行数をマッチしたとして扱うので、パターンにマッチした行を出力しない`--suppress-matched`オプションなども使用可能です。
```bash:分割された2ファイル目にマッチ行(--)が出力されず、ファイル全体として2行しか出力されない
$ csplit -k hoge.txt 3 '{*}' --suppress-matched
38
38
37
37
39
csplit: `3': 範囲外の行番号 繰り返し 5 回目
0
$ ls
hoge.txt  xx00  xx01  xx02  xx03  xx04  xx05
$ cat xx00
1 4 2022/11/16 100
2 2 2022/10/22 101
$ cat xx01
3 1 2022/09/01 210
4 3 2022/11/13 150
$ 
```
:::

## ・`awk`コマンド(グループ単位で分割)
`awk`コマンドはいわゆるフィルタコマンドになりますが、意外と高速にファイル分割も行えます。

```:hoge.txtの中身(id groupid date value)
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
```

- 特定列の値単位でファイルを分割
```bash:2項目(groupid)単位にファイルを分割
$ ls
hoge.txt
$ awk '{file="group"$2;print $0 >> file}' hoge.txt 
$ ls
group1  group2  group3  group4  hoge.txt
$ cat group1
3 1 2022/09/01 210
7 1 2022/10/16 200
8 1 2022/08/21 70
$ cat group2 
2 2 2022/10/22 101
5 2 2022/10/16 30
10 2 2022/11/11 250
$ 
```

- 偶数行・奇数行でファイルを分割
```bash
$ ls
hoge.txt
$ awk '{file="line"NR%2;print $0 >> file}' hoge.txt
$ ls
hoge.txt  line0  line1
$ cat line0
2 2 2022/10/22 101
4 3 2022/11/13 150
6 4 2022/11/15 100
8 1 2022/08/21 70
10 2 2022/11/11 250
$ 
```

- 行数を指定してファイルを分割 (一応`split`コマンドと同様の分割も可能)
```bash
$ ls
hoge.txt
$ awk 'BEGIN{c=0}{file="xx"c;print $0 >> file;NR%3==0&&c++}' hoge.txt 
$ ls
hoge.txt  xx0  xx1  xx2  xx3
$ cat xx0
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
$ 
```

:::note info
上記はコマンド実行を行なったディレクトリに分割ファイルが展開されますが、別のパスに分割ファイルを展開したい場合は下記の様にファイル名にパスを付与する事ができます。

```bash
awk '{file="tmp/group"$2";print $0 >> file}' hoge.txt
```

リダイレクトの箇所にパスの文字列を付与することは出来ないので注意してください。
:::

## ・`cut`コマンド(横に分割)

今までファイルを縦(行)に分割していましたが、横(列)に分割したい場合は`cut`コマンドを使用します。

また、`split`コマンドなどの一括分割できるコマンドがないので、地道に分割したい箇所を抽出していく必要があります。

```:hoge.txtの中身(id groupid date value)
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
```

- 縦に2つにファイルを分割
```bash:空白を区切り文字としてファイルの1,2列と3,4列を分割
$ ls
hoge.txt
$ cut -d " " -f 1-2 hoge.txt > col0
$ cut -d " " -f 3-4 hoge.txt > col1
$ ls
col0  col1  hoge.txt
$ cat col0
1 4
2 2
3 1
4 3
5 2
6 4
7 1
8 1
9 3
10 2
$ cat col1
2022/11/16 100
2022/10/22 101
2022/09/01 210
2022/11/13 150
2022/10/16 30
2022/11/15 100
2022/10/16 200
2022/08/21 70
2022/09/08 105
2022/11/11 250
$ 
```


# ファイルを結合する方法

## ・`cat`コマンド(分割ファイルを縦に結合)

代表的なファイル結合のコマンドとして`cat`コマンドがあり、指定した複数の分割ファイルを1つに結合する事ができます。

上記で紹介した`split`、`csplit`、`awk`コマンドで分割したファイルは`cat`コマンドで1つのファイルに結合できます。

```bash:分割ファイルの中身
$ ls
xx00  xx01  xx02  xx03  xx04  xx05
$ head xx*
==> xx00 <==
1 4 2022/11/16 100
2 2 2022/10/22 101

==> xx01 <==
3 1 2022/09/01 210
4 3 2022/11/13 150

==> xx02 <==
5 2 2022/10/16 30
6 4 2022/11/15 100

==> xx03 <==
7 1 2022/10/16 200
8 1 2022/08/21 70

==> xx04 <==
9 3 2022/09/08 105
10 2 2022/11/11 250

==> xx05 <==
$
```

- 複数の分割ファイルを結合
```bash:複数ファイルを結合し、リダイレクトで1つのファイルに出力
$ cat xx00  xx01  xx02  xx03  xx04  xx05 > hoge.txt
$ ls
hoge.txt  xx00  xx01  xx02  xx03  xx04  xx05
$ cat hoge.txt 
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
$ 
```

:::note info

`cat`コマンドは引数で指定された順番にファイルを結合していくので、下記の通り順番を入れ替えて結合する事もできる。
```bash:分割ファイルを逆順に結合
$ cat xx05 xx04 xx03 xx02 xx01 xx00
9 3 2022/09/08 105
10 2 2022/11/11 250
7 1 2022/10/16 200
8 1 2022/08/21 70
5 2 2022/10/16 30
6 4 2022/11/15 100
3 1 2022/09/01 210
4 3 2022/11/13 150
1 4 2022/11/16 100
2 2 2022/10/22 101
$ 
```

また、分割ファイルが大量にある場合はワイルドカード(`*`)を使用して結合する事もできます。
```bash:分割ファイルをワイルドカードを使用して結合
$ cat xx*
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
$ 
```

ワイルドカード以外にもブレース展開(`{}`)を使用して結合する事もできます。
```bash:分割ファイルをブレース展開を使用して結合
$ cat xx0{0..5}
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
$ 
```
:::

## ・`paste`コマンド(分割ファイルを横に結合)

`cut`コマンドなどで分割されているファイルを横(列)に結合したい場合は`paste`コマンドを使用します。

また、`split`コマンドなどの一括分割できるコマンドがないので、地道に分割したい箇所を抽出していく必要があります。


```bash:分割ファイルの中身
$ ls
col0  col1
$ head col*
==> col0 <==
1 4
2 2
3 1
4 3
5 2
6 4
7 1
8 1
9 3
10 2

==> col1 <==
2022/11/16 100
2022/10/22 101
2022/09/01 210
2022/11/13 150
2022/10/16 30
2022/11/15 100
2022/10/16 200
2022/08/21 70
2022/09/08 105
2022/11/11 250
$ 
```

- 複数の分割ファイルを横に結合
```bash:空白を区切り文字として分割ファイルcol0,col1を横に結合し、リダイレクトで1つのファイルに出力
$ ls
col0  col1
$ paste -d " " col0 col1 > hoge.txt
$ ls
col0  col1  hoge.txt
$ cat hoge.txt 
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
$ 
```

## ・`join`コマンド(RDBのように横に結合)

こちらも意外と知られていませんが、分割されたファイル同士をRDBのテーブルのようにリレーション(共通項目)を使用して横に結合する事ができます。

```:hoge.txtの中身(id groupid date value)
3 1 2022/09/01 210
7 1 2022/10/16 200
8 1 2022/08/21 70
10 2 2022/11/11 250
2 2 2022/10/22 101
5 2 2022/10/16 30
4 3 2022/11/13 150
9 3 2022/09/08 105
1 4 2022/11/16 100
6 4 2022/11/15 100
```
```:master.txtの中身(goupid groupname)
1 Group1
2 Group2
3 Group3
4 Group4
```

- 二つのファイルを共通項目で結合
```bash:hoge.txtとmaster.txtをgroupidで結合
$ join -1 2 -2 1 hoge.txt master.txt 
1 3 2022/09/01 210 Group1
1 7 2022/10/16 200 Group1
1 8 2022/08/21 70 Group1
2 10 2022/11/11 250 Group2
2 2 2022/10/22 101 Group2
2 5 2022/10/16 30 Group2
3 4 2022/11/13 150 Group3
3 9 2022/09/08 105 Group3
4 1 2022/11/16 100 Group4
4 6 2022/11/15 100 Group4
$ 
```

:::note warn
`join`コマンドは結合に使用する共通項目がファイル内でソートされている必要があります。

```:hoge.txtの中身(id groupid date value)
1 4 2022/11/16 100
2 2 2022/10/22 101
3 1 2022/09/01 210
4 3 2022/11/13 150
5 2 2022/10/16 30
6 4 2022/11/15 100
7 1 2022/10/16 200
8 1 2022/08/21 70
9 3 2022/09/08 105
10 2 2022/11/11 250
```
```:master.txtの中身(goupid groupname)
2 Group2
1 Group1
4 Group4
```

例えば上記のように共通項目がソートされていないファイルの場合は一度ソートしたファイルに修正するか、下記のようにプロセス置換(`<()`)を使用してファイルをソートしたデータを渡す必要があります。

```bash
$ join -a 1 -1 2 -2 1 <(sort -k 2n hoge.txt) <(sort -k 1n master.txt) | sort -k 2n
4 1 2022/11/16 100 Group4
2 2 2022/10/22 101 Group2
1 3 2022/09/01 210 Group1
3 4 2022/11/13 150
2 5 2022/10/16 30 Group2
4 6 2022/11/15 100 Group4
1 7 2022/10/16 200 Group1
1 8 2022/08/21 70 Group1
3 9 2022/09/08 105
2 10 2022/11/11 250 Group2
$ 
```

また、`join`コマンドでは出力する項目の順番や結合方法など細かな操作ができるので、興味がある方はコマンドを調べてみることをお勧めします。

```bash
$ join -a 1 -o 1.1,2.2,1.3,1.4 -1 2 -2 1 <(sort -k 2n hoge.txt) <(sort -k 1n master.txt) | sort -k 1n
1 Group4 2022/11/16 100
2 Group2 2022/10/22 101
3 Group1 2022/09/01 210
4  2022/11/13 150
5 Group2 2022/10/16 30
6 Group4 2022/11/15 100
7 Group1 2022/10/16 200
8 Group1 2022/08/21 70
9  2022/09/08 105
10 Group2 2022/11/11 250
$ 
```
:::