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. ソリューション

–filter = COMMANDオプションを使用すると、分割結果をシェルコマンドに書き込むことができます。 言い換えれば、分割されたピースを後処理することができますフィルター指図。

次に、このオプションが問題の解決にどのように役立つかを見てみましょう。

$ 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コマンドを使用してジョブを実行する方法の例を見てきました。