【Head/Tail/Cut/Trコマンド活用】CSVデータから効率で高速に行&列の範囲を絞り出す方法


2021/04/09

csvファイルが行・列とも膨大な数で巨大なファイルを利用したい場合があります。

このような場合には一気にスクリプトに読み込ませて捌くにはとても処理負荷のかかるため、AwkやSedなどの作業を部分的に分割したファイルの一部を取り出してパイプライン処理させてあげたほうがより高速かつ効率的です。

今回はこれらの作業を行の抽出にHead/Tailコマンドで、列の抽出にCut他を紹介します。


はじめに

当サイトではオフィス業務のComputer-Aidedなハイブリッドな方法を模索し、より効率的なExcel業務を実現したい多忙なオフィスワーカー向けの主にAwkとSedを使うシェル講座です。

シェルスクリプトはどこでもどんなOSでも基本的に使えて、しかも一度使い方を覚えると、Excelと組み合わせて最高に効率の良いオフィスワークツールが作れることでしょう。

合同会社タコスキングダム|蛸壺の技術ブログ


HeadとTail

これまではcatコマンドを使ってcsvを読み込んできました。

通常のcatコマンドを使ったCsvテキスト読み込みは便利ですが、一度処理が走り出すとファイルの最初から最後まで全てを処理しないと処理が完了しないため、巨大な容量のファイルで使うときにはあまり効率的なスクリプト処理でないときがあります。

そこで使うのがheadとtailを組み合わせた部分読み出しです。

以下ではまず基本的な使い方を紹介していきます。

headの使い方

巨大なCsvファイルで例を出すのはアレですので、以下のようなデータを巨大データと見立てて説明していきます。とりあえず以下のコマンドでローカルにcsvファイルをhoge.csvとして保存して利用します。

            
            $ cat <<EOF > hoge.csv
山下モゲ雄,営業部,本社,3年
島田フガ子,経理部,名古屋支部,15年
岡田ピポ太,製造部,山口工場,8年
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年
上岡ムメ美,営業部,本社,23年
京谷マハ次,製造部,山口工場,3年
園田フマ由,人事部,本社,17年
田川ポゥ子,製造部,ベトナム工場,12年
満田クタ郎,営業部,本社,2年
島寺ルン大,営業部,本社,18年
香下ウル蔵,製造部,山口工場,5年
蒲田ウオ奈,海外部,メキシコ支部,9年
郷田ポポ生,営業部,名古屋支部,4年
梅岡ボル伍,経理部,本社,25年
亀川ヲル士,製造部,山口工場,14年
EOF
        
それではまずheadコマンドの簡単な使い方を復習します。

headコマンドはデフォルトではファイルの先頭から指定の行数までを出力してくれるcatのようなコマンドです。

用法としては先程の保存したファイルで使うと以下のように利用できます。

            
            #👇-nオプションで先頭から指定の行数の長さを出力
$ head -n 5 hoge.csv
山下モゲ雄,営業部,本社,3年
島田フガ子,経理部,名古屋支部,15年
岡田ピポ太,製造部,山口工場,8年
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年

#👇-nオプションで負の行数を指定すると最後から指定の行数を
#👇遡った位置までの行を全て表示
$ head -n -11 hoge.csv
山下モゲ雄,営業部,本社,3年
島田フガ子,経理部,名古屋支部,15年
岡田ピポ太,製造部,山口工場,8年
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年
        
ということで基本的にHeadはその名の通り、ファイル先頭から指定の行までを表示してくれる操作をやってくれるコマンドです。

tailの使い方

ではもう一つのtailコマンドも復習してみます。

            
            #👇-nオプションで最後から指定の行数の長さを遡って出力
$ tail -n 5 hoge.csv
香下ウル蔵,製造部,山口工場,5年
蒲田ウオ奈,海外部,メキシコ支部,9年
郷田ポポ生,営業部,名古屋支部,4年
梅岡ボル伍,経理部,本社,25年
亀川ヲル士,製造部,山口工場,14年

#👇-nオプションで'+行数'で指定すると先頭から指定の行数番目
#👇以降の位置から最後までの行を出力
$ tail -n +11 hoge.csv
島寺ルン大,営業部,本社,18年
香下ウル蔵,製造部,山口工場,5年
蒲田ウオ奈,海外部,メキシコ支部,9年
郷田ポポ生,営業部,名古屋支部,4年
梅岡ボル伍,経理部,本社,25年
亀川ヲル士,製造部,山口工場,14年
        
Tailは先程のHeadと対になるコマンドです。

上の利用例に示した通り、-nオプションは
+無しで最終行からの相対的な行数の長さを遡って出力しますが、+有りでは先頭から数えた絶対位置にある行数を基準にしてそこから最後まで表示してくれます。

行の範囲取り出し

ということでHeadは表示させる行の下限を与え、Tailはその上限を与えてくれます。

応用として例えば、Csvデータで4行目から7行目までのデータを部分的に取り出すテクニックは以下のように出来るでしょう。

            
            #👇4~7行目のデータの範囲取り出し
$ head -n 7 hoge.csv | tail -n +4
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年
上岡ムメ美,営業部,本社,23年
京谷マハ次,製造部,山口工場,3年
        

Cutによるcsvデータの部分列分割

Csvデータの指定の列の抽出はCutコマンドで実現することができます。

            
            #👇1列目と4列目の取り出し
$ cut -f 1,4 -d "," hoge.csv
山下モゲ雄,3年
島田フガ子,15年
岡田ピポ太,8年
#...中略
亀川ヲル士,14年

#👇-fオプションでは取り出し列リストの順番を変えても
#👇出力される列の順序は変わらない
$ cut -f 4,2,1 -d "," hoge.csv
山下モゲ雄,営業部,3年
島田フガ子,経理部,15年
岡田ピポ太,製造部,8年
#...中略
亀川ヲル士,製造部,14年
        
使い方は-dオプションでデリミタ文字を指定することで区切り位置を検知し、-fオプションで区切った列のデータから表示する列リストを指定します。


部分Csvデータの取り出し

以上、Head/Tail/Cutの3つのコマンドで、巨大なCsvデータから解析に利用するのに必要な情報だけを部分的に取り出せることが分かると思います。

例えば、hoge.csvから3~8行目の1列目と3列目だけを調査したいとすると、

            
            $ head -n 8 hoge.csv | tail -n +3 | cut -f 1,3 -d ","
岡田ピポ太,山口工場
沢口モフ代,本社
銭形ガメ吉,メキシコ支部
上岡ムメ美,本社
京谷マハ次,山口工場
園田フマ由,本社
        
というように部分Csvデータが取り出すことができるようになります。


セル内文字列の置換・削除

上記で見てきたHead/Tail/CutでのCsvデータの部分取り出しと併せて文字の処理をおこなうTrコマンドについても触れておきます。

これまで当ブログの記事ではテキスト全体に渡るような文字列の整形にはSedコマンドを利用する方法を何回か特集してきましたが、場合によってはHead/Tail/Cutを使うテキスト処理と併用してTrコマンドを使うことで、Sedよりも高速な文字の置換・削除が可能となることがあります。

            
            #👇区切り文字の,を&に変える
$ head -n 8 hoge.csv | tail -n +3 | cut -f 1,2,3 -d "," | tr , \&
岡田ピポ太&製造部&山口工場
沢口モフ代&人事部&本社
銭形ガメ吉&海外部&メキシコ支部
上岡ムメ美&営業部&本社
京谷マハ次&製造部&山口工場
園田フマ由&人事部&本社

#👇-dオプションで一括削除
$ head -n 8 hoge.csv | tail -n +3 | cut -f 1,2 -d "," | tr -d ,
岡田ピポ太製造部
沢口モフ代人事部
銭形ガメ吉海外部
上岡ムメ美営業部
京谷マハ次製造部
園田フマ由人事部

#👇POSIXキャラクタも指定できる(例では句読点文字をスペース文字'\ 'に置換)
$ head -n 8 hoge.csv | tail -n +3 | cut -f 1,2,3 -d "," | tr '[:punct:]' \ 
岡田ピポ太 製造部 山口工場
沢口モフ代 人事部 本社
銭形ガメ吉 海外部 メキシコ支部
上岡ムメ美 営業部 本社
京谷マハ次 製造部 山口工場
園田フマ由 人事部 本社
        
trコマンドで融通が効かないのは、基本的に一文字を別の一文字に置き換える操作になるので、任意の文字列の置き換えにはやはりSedコマンドの方を利用しないといけません。

ちなみに一文字とは言っても、trコマンドで置換・削除できるのは1バイト文字であり、漢字のようなマルチバイト文字を指定すると意図としない結果になるので注意は必要です。

            
            $ head -n 8 hoge.csv | tail -n +3 | cut -f 1,2 -d "," | tr -d 部
岡田��太,製��
沢口��代,人事
��形ガ�吉,海外
上岡��美,営業
京谷��次,製��
園田��由,人事
        
簡単な文字の置き換えならばパフォーマンスの面からTrコマンドを使うことも検討できます。


実践編〜巨大なCsvファイルを効率的に処理するスクリプト

以上の内容を踏まえて、サイズの大きなCSVファイルを高速にバッチ処理させることを想定して、スクリプトに仕立ててみようと思います。

今回はhoge.csvという大きくないファイルでバッチ処理させますので、バッファサイズは小さく取っていますが、本番でサイズの大きいCSVで試されるときには適度に大きい数に変更ください。

            
            #!/bin/bash

FILE_NAME=hoge.csv

TOTAL_LINES=$(wc -l hoge.csv | cut -d' ' -f1)
#👇本番ではバッファサイズは大きく変更
BUFFER_SIZE=3
START_READ=1
END_READ=$(($TOTAL_LINES / $BUFFER_SIZE))
LEFT_LINES=$(($TOTAL_LINES % $BUFFER_SIZE))

#👇1と3列目の取り出す例
COLUMN_LIST=1,3

echo "TOTAL:${TOTAL_LINES} START:${START_READ} END:${END_READ} LEFT:${LEFT_LINES}"

do_something() {
    #DO SOMETHING...
    echo "$1"
}

for i in $(seq "$END_READ" -1 0); do
    while read LINE; do
        do_something $LINE
    done< <(
        if [ "$i" != 0 ] ; then
            #👇バッファサイズ毎にバッチ処理
            head -n "$(( ($END_READ - $i + 1) * $BUFFER_SIZE ))" $FILE_NAME |
            tail -n "$(( $BUFFER_SIZE ))" |
            cut -f "$COLUMN_LIST" -d ","
        else
            #👇最後に余ったバッチのための処理
            tail -n "$(( $LEFT_LINES ))" $FILE_NAME |
            cut -f "$COLUMN_LIST" -d ","
        fi
    )
done
        
このスクリプトをhoge.shとしてhoge.csvと同じフォルダに保存し、以下のように実行すると、今回はただ出力させている例ですが、バッチ毎に処理が実行されていることが分かります。

            
            $ chmod +x hoge.sh
$ ./hoge.sh
#👇結果
TOTAL:16 START:1 END:5 LEFT:1
山下モゲ雄,本社
島田フガ子,名古屋支部
岡田ピポ太,山口工場
沢口モフ代,本社
銭形ガメ吉,メキシコ支部
上岡ムメ美,本社
京谷マハ次,山口工場
園田フマ由,本社
田川ポゥ子,ベトナム工場
満田クタ郎,本社
島寺ルン大,本社
香下ウル蔵,山口工場
蒲田ウオ奈,メキシコ支部
郷田ポポ生,名古屋支部
梅岡ボル伍,本社
亀川ヲル士,山口工場
        

まとめ

今回は入力するCSVファイルのサイズに着目して、より巨大なCSVデータを取り扱うときにSedコマンドのようにファイル全体をどのように処理させることができるかを検討してみました。

今後多少時間が取れるときにでも、今回の方法と、通常のSedスクリプトでどのくらいの処理時間に差が生じるのか検証してみるもの面白いかもしれませんが、取り急ぎ今回はここまでとさせて頂きます。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。