1週間Rust漬け: CSV周りのコマンドラインツールをいくつか作りました
ECの業務でCSVをいじるタスクが多いことは以前の投稿にも書きました。
この1週間は Rust の基礎を勉強しつつ、普段の業務で使っているコマンドラインツールを Rust で書き直してみました。
コードは非常に粗粗ですが、とりあえずコンパイルは通ってるし、最低限のテストも書いてあるし、あと、特定のクライアントの情報が含まれるわけじゃないので Public なリポジトリとして公開してあります。
余談ですが、Rust に夢中になってたので2ヶ月ぐらい続いた note 連続投稿が止まってしまいましたが、それを補って余りある充実した感じが今週はずっとありました。とりあえず Rust が好きになりました。
以下は、ざっとどんなものを作ったか、それはどんなタスクを消化するために役に立つツールなのかを紹介程度に書いていきたいと思います。
コーディングを進める上で学んだ Rust の詳細に分け入ることは、またの機会に気が向いたら書きます。
Shift_JISからUTF8へ変換が必要
Windowsが長らく Shift_JIS(正確には cp932 もしくは Windows31J)という文字コードをOSの標準にしていた影響で、EC業界でなんらかのプラットフォームからCSV形式のデータをダウンロードすると、
文字コード: Shift_JIS
改行コード: CRLF
でしかダウンロードできない=この文字コード・改行コード固定、というケースに遭遇します。
しかし、今回僕が書いている Rust しかり、オープンソースのプログラミング言語や、それが動作する Unix 系環境、そして、最近の Windows 環境の標準的な文字コードは UTF8 になります。そして改行コードは LF です。
なので、Shift_JIS(CRLF) の形式のCSVを落としてきたら、なにはともあれ UTF8(LF) に変換する必要があります。
従来この作業は僕の場合 nkf というオープンソースのコマンドラインツールを使わせてもらってました。(今でもちょっとした業務ではかなり頻繁に使います。)
かなり歴史のあるツールで機能的にもこの変換タスクに関しては万能です。
しかし、万能ゆえに、対象とするCSVファイルがギガバイト級の大きさだったりすると結構遅いことがあったり、すでにメンテモードに入っているツールなので nkf では対応できないケースが存在したりと、ちょっと残念な部分があるのも事実です。
そこでこれまでは Go 言語製の自作変換ツールを業務スイートには組み込んでいたんですが、これを今回は Rust で書き直してみました。
コマンドラインツールの名前がこっ酷いのはご愛嬌ということでお許しください。練習用ですから。
このツールの本編は src/main.rs になります。
UTF8からShift_JISへ変換が必要
今度は上のツールの逆です。
nkf の場合はスイッチオプションの設定で一つのコマンドラインツールが何役もこなしますが、今回は Rust の練習中ということで素朴にこれはこれで別ツールとしてまずは作ってみました。
ECのプラットフォームでデータの取得が Shift_JIS(CRLF) 縛りの場合は、CSVでのデータのアップロードも同様の縛りがあるのが普通なので、こちらの作業中は UTF8(LF) でこしらえたものを Shift_JIS(CRLF) に変換してプラットフォームに送り返す必要があります。
最初の Shift_JIS → UTF8 変換器は、コーディングを簡潔にするためにインプットはファイルパスを指定する方式一択でまずは作りました。
しかし、この UTF8 → Shift_JIS 変換器は、もともとのフィルターを通ったデータがストリームで流れてきてパイプで受け取ることがどちらかというとデフォなので(← とても個人的な「デフォ」です)、コマンドラインの引数指定で "-" を見つけたら、もしくは、あるべき箇所にファイルパス指定がなかったら標準入力から UTF8 のデータを読み取るように実装されています。
→ src/main.rs
CSV: 特定のレコードを抜き出す
文字コード、改行コードの処理を2つ紹介しましたが、これらは一連の作業の流れの一番最初と一番最後の部分で必要になる処理です。
ここからはその間の処理、つまり、CSVをなんらかの条件でフィルターしたり、書き換えたりするツールをいくつか作ったので紹介します。
CSV編集ツールのひとつめは、ほしいレコードだけ抜き出すツールです。
今日紹介しているコマンドラインツール全般に言えることですが、ここまで元のファイルを直接上書きするわけではありません。
nkf はそれ用のオプションを設定すれば直で上書きすることもできるようです(個人的には使ったことがありません)。
元のファイルやデータを直接変更するのは基本的に「破壊行為」という認識をプログラマはします。
Ruby や Lisp などを勉強したことがある人は「破壊的関数」「非破壊的関数」などという言い方に出くわしたことがあるかもしれません。破壊的という接頭辞がついたら、メモリ上のオリジナルデータに変更を加えるということです。これが非破壊的となっていれば、オリジナルをコピーしてそのコピーに変更を加えたものを返します。オリジナルはそのまま残ります。
これと同じ概念がコマンドラインでのファイル操作にも当てはまります。
オリジナルのファイルを直接に削除したり移動したり上書きしたりすることがそもそも目的のコマンドというのがあり、それはもう元からそういうものなのと、破壊的であることに意味があるコマンドなので、十分注意して使います。
しかし、今回の投稿で扱っているような性質のタスクではどうするかというと、オリジナルファイルをそのままの状態で残したまま、目的の編集が施された別の新しいファイルを結果ファイルとして作成します。つまり必要でない限り「非破壊的に」データ操作を進めます。
ですので、CSVの特定の行だけ抜き出すツールは、入力に使用したオリジナルはそもままで、条件に一致した行だけを別に書き出します。
どこに書き出すかというと標準出力です。
この標準出力の内容を保存するのはインプットに使ったオリジナルとは別のファイルパスになるか、もしくは、ファイルに保存せず次のコマンドにストリームを通ってパイプで渡されまさす。
ちなみに、特定のレコードだけ抜き出す方法というのは、プログラミング的には非常に深いテーマです。抜き出し方、その条件の指定の仕方、実行効率など、考えるに値する要素がたくさん含まれたものになります。
たぶん、あるラインの複雑さ以上のことを条件指定でやりたいのなら、自分でツールを作らずに世にあるメジャーなツールを使ったほうが賢明です。
(例えばですが、CSVを一旦JSONに変換して jq を使う、とか、CSVをデータベースに突っ込んで SQL を使うとか)
なので、自分でこのようなツールを作るのは、
ひとつにはもちろん Rust のコーディングの練習のため
もうひとつは、必要最低限の機能に限定して機能を実装する
というのをポイントにして行ってます。
ちなみに、CSVを云々するツールたちのコード構成では、src/main.rs はプロセスをキックするだけ(+エラーがあったら死ぬだけ)で、コードの主役は src/lib.rs のほうに移っています。
それから、テストはちょっと手を抜いていて、_test.sh というシェルスクリプトが、事前に用意した期待値と実行結果を比較するだけの簡単なものになってます。
CSV: 特定の列だけ抜き出す
特定のレコード(行)だけに縛るのと同様、特定の列だけに絞ってコンパクトなCSVを作成することもよく行います。
これはそのためのツールです。
例えばほしいレコードだけに絞るツールが標準出力に書き出したCSV形式のストリームをこのほしい列だけに絞るツールがパイプで受け取れば、中間ファイルをひとつ生成する手間が省けます。
ECのプラットフォームが提供してくれる商品情報などの載ったCSVは「全部入り」なことが多いです。とにかく提供できる情報は一発でダウンロードできるようにしてある、その代わり、細かな条件指定での絞り込みはユーザのほうでやってね、というわけです。
「全部入り」の場合、使わない列項目(カラム、Column)が大量に含まれており、CSVの性質上、閲覧する際は「横に」長くなります。これつまり、横スクロールが必要になるということです。
個人的には、ですが、横スクロールが必要なほど幅が広い表というのはヒューマンエラーの温床になりがちだと思ってます。
***
文字コード・改行コードを適切なものに変換し、使わないレコードや項目を削ぎ落とすことまでできると、あとは、そのフィルターされたCSVを元にしてより細かい編集タスクを実行していく流れになります。
この細かな部分についてはまたいつかの機会に書ければいいなと思ってます。クライアントワークなので、クライアント特有の処理を一段階抽象化してオープンにする必要があります。このあたり、自分にとっては取り組み甲斐のある課題だなと思っています。
おまけ
この土日にもう2つ今日の投稿に関連した Rust 練習用ツールを作ってみたので、それをさらっと紹介して終わりたいと思います。
CSV: 必要な項目列以外を空欄にする
ECプラットフォームから落とせるCSVは「全部入り」なことが多いと書きました。
しかし、編集したい項目はごく一部だったりします。
プラットフォームの規定にもよりますが、編集タスクを遂行した結果CSVをアップロードする際=プラットフォーム側に反映させる際に、
CSV内の項目になにかデータが入っていればその内容で上書き
項目が空欄ならスルー(もしその項目を空白状態にクリアしたい場合は別の所定の手順を踏む)
というケースがあります。
このケースの場合にあると便利なのが、編集した項目以外を一発で空欄にするツールです。
CSV: 特定の項目をなんらかの値で一括上書き
CSVのレコードごと=商品やカテゴリなど、そのレコードを構成する個別の要素ごとに編集が必要な内容は、手作業したり、条件分岐を含むプログラムを組んだりする必要があります。
しかし、それとは逆に特定の値を一括で当てはめればOKという項目もあります。
例えば、プラットフォーム上の情報を上書きする用のCSVでは「コントロールカラム」などの名前で、新規登録なら "n"、更新なら "u" など指定された単純な値をセットする項目が用意されていることがあります。
CSVの中身のレコードは全レコード共通で上記のような固定値を入力すればOKという場合のため、言い換えれば、レコードごと個別の処理が不要な場合、コマンドライン上で項目と固定値を指定して一発入力できると便利です。
そのためのツールがこちらです。
SN