ヘッダー行でファイルを分割する
1. 概要
ご存知のように、 split コマンドは、大きなファイルを特定の行数でいくつかの小さなファイルに分割するのに役立ちます。
ただし、入力ファイルにヘッダー行が含まれている場合は、ヘッダー行を各分割ファイルにコピーする必要がある場合があります。 デフォルトでは、splitコマンドはそれを行うことができません。
このチュートリアルでは、この問題を解決する方法について説明します。
2. 問題の紹介
具体的な例は、問題をすばやく理解するのに役立ちます。
まず、入力例を見てみましょう。 tokyo_medal.tsv ファイルには、東京オリンピックのメダルテーブルのトップ10のデータが含まれています。
$ cat tokyo_medal.tsv
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
10 Italy 10 10 20 40
上記の出力でわかるように、ファイルはTSVファイルです。 さらに、ファイルには、各列の値の意味を示すヘッダー行が含まれています。 TSVまたはCSVファイルにヘッダー行が含まれていることはよくあることです。
ここでの目標は、tokyo_medal.tsvファイルを分割することです。 各ピースに3つのレコードを持たせたいとしましょう。 さらに、各ピースにはヘッダー行も必要です。
このチュートリアルでは、問題を解決するための3つの異なる方法について説明します。
- バージョン>=8.13でsplitコマンドを使用する
- 簡単なシェルスクリプトを書く
- awkコマンドの使用
次に、それらの動作を見てみましょう。
3. 新しいsplitコマンドの使用
split コマンドは、 GNUCoreutilsパッケージのメンバーです。
バージョン8.13以降、 splitユーティリティは新しい–filter =COMMANDオプションを導入しました。
このオプションを使用して問題を解決します。 まず、その仕事をするコマンドを見てみましょう。 次に、それが機能する理由を理解します。
3.1. ソリューション
次に、このオプションが問題の解決にどのように役立つかを見てみましょう。
$ tail -n +2 tokyo_medal.tsv | split -d -l 3 - --filter='sh -c "{ head -n1 tokyo_medal.tsv; cat; } > $FILE"' part_
$ ls -1 part*
part_00
part_01
part_02
part_03
上記の出力で見たように、コマンドを実行した後、4つのファイルが作成されました。 それでは、ファイルの内容を確認しましょう。
$ head part*
==> part_00 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> part_01 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> part_02 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> part_03 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
したがって、期待どおりの結果が得られます。 このようにして、問題を解決しました。
3.2. コマンドのしくみ
それでは、コマンドの各部分を見ていき、それがどのように機能するかを理解しましょう。
tail -n +2 tokyo_medal.tsv | split -d -l 3 - --filter='sh -c "{ head -n1 tokyo_medal.tsv; cat; } > $FILE"' part_
- tail -n +2 tokyo_medal.tsv – tail コマンドは、入力ファイルからヘッダー行を切り取り、すべてのデータレコードを次のコマンドにパイプします
- …| split -d -l 3 – –filter=’…’part_ –最初に –filter=’…’の部分をスキップしましょう。 split コマンドは、 stdin ( – )からデータレコードを読み取り、3行ごとに分割します( -l 3 )。 -d オプションは、splitに生成されたファイル名に数値のサフィックスを使用するように指示します
- –filter =’sh -c“ {head -n1 tokyo_medal.tsv; 猫; }> $ FILE”’ – –filterオプションのコマンドは、分割されたデータを後処理します。コマンドグループをヘッドで宣言しました]およびcatコマンド。 入力ファイルからヘッダー行を読み取り、分割されたレコードを結合します。 最後に、ヘッダー行のあるレコードを $FILEにリダイレクトします。これはpart_xファイルです。
ただし、システム上のCoreutilsパッケージのバージョンが8.13より古い場合は、さまざまな方法で問題を解決する必要があります。 そこで、他のいくつかのアプローチに注意を向けましょう。
4. 簡単なシェルスクリプトを書く
古いsplitコマンドだけでは問題を解決できませんが、ヘッダー行を処理するためにシェルスクリプトでラップすることができます。
4.1. 問題の解決
簡単に言えば、2つのステップで問題を解決できます。
- ステップ1:ヘッダー行なしで入力ファイルを分割する
- ステップ2:各分割ファイルにヘッダー行を追加する
このアイデアに従って、スクリプトを作成できます。
#!/bin/bash
INPUT=tokyo_medal.tsv
# Step 1: split the input file without the header line
tail -n +2 "$INPUT" | split -d -l 3 - sh_part_
# Step 2: add the header line to each split file
for file in sh_part_*
do
head -n 1 "$INPUT" > with_header_tmp
cat "$file" >> with_header_tmp
mv -f with_header_tmp "$file"
done
スクリプトが示すように、手順2を実装するときに、ヘッダー行を保持する一時ファイル with_header_tmp を作成し、splitの結果を追加しました。
このサンプルスクリプトでは、引数の処理がスキップされていることに注意してください。 たとえば、入力ファイルとsplitオプションはスクリプトにハードコーディングされています。
これは、このチュートリアルがファイル分割の実装に焦点を合わせているためです。 ただし、スクリプトを再利用可能にする場合は、現実の世界で引数処理を追加する必要があります。
4.2. スクリプトをテストする
次に、スクリプトに split_with_header.sh という名前を付けて、期待どおりに機能するかどうかをテストします。
$ ./split_with_header.sh
$ head sh_part_*
==> sh_part_00 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> sh_part_01 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> sh_part_02 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> sh_part_03 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
すごい! スクリプトは機能します。
通常、ファイル分割の問題が発生すると、splitコマンドが最初に表示されます。 しかし、実際には、他のLinuxコマンドでもこの種のファイル分割タスクを実行できます。
次に、awkコマンドを使用して問題を解決しましょう。
5. awkコマンドの使用
awk は、テキスト処理のための強力な武器です。 さらに、awkは独自のCのようなスクリプト言語を定義しています。 外部コマンドを使用せずにこの問題を解決できます。
5.1. awkソリューション
まず、awkがどのように問題を解決するかを見てみましょう。
$ awk -v lines="3" -v pre="awk_part_" '
NR==1 { header=$0; next}
(NR-1) % lines ==1 { fname=pre c++; print header > fname}
{print > fname}' tokyo_medal.tsv
$ head awk_part_*
==> awk_part_0 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> awk_part_1 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> awk_part_2 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> awk_part_3 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
上記の出力が示すように、入力ファイルは予想どおりヘッダー行で分割されています。
5.2. awkコマンドのしくみ
それでは、 awk コマンドを通過して、それがどのように機能するかを理解しましょう。
- awk -v lines =” 3” -v pre =” awk_part _” –最初に、2つの awk 変数を宣言して、各分割ファイルとプレフィックスに含まれるレコードの数を定義しました。ファイル名の
- NR ==1{ヘッダー=$0; next} – awk が最初の行を読み取ると、その行を header 変数に格納し、それ以上の処理を停止します
- (NR-1)% li nes == 1 {fname = pre c ++; print header> fname} –現在の行がトランクの最初のレコードである場合、カウンター( c )をインクリメントしてファイル名( fname )を更新する必要があります。 また、これは新しいファイルになるため、header変数の値を最初の行としてファイルに追加します
- {print> fname}’tokyo_medal.tsv –次に、各レコード行を現在のfnameファイルにリダイレクトできます。
このようにして、 awk は入力ファイルを1回だけ読み取り、問題を解決します。
6. 結論
この記事では、入力ファイルをヘッダー行で分割する方法を学びました。
システムのCoreutilsバージョンが8.13以降の場合、 splitコマンドの新しい–filter =COMMANDオプションを使用して目標を達成できます。
それ以外の場合でも、2つのステップで問題を解決するための簡単なbashスクリプトを作成できます。ヘッダー行なしでファイルを分割することと、分割された各ファイルにヘッダー行を追加することです。
また、強力なawkコマンドを使用してジョブを実行する方法の例を見てきました。