多言語間で配列データを共有する:sed, awkの利用

投稿者: | 2019年1月19日

計算結果の他言語への移植のためにsed/awkを利用する

問題意識

Cloudやデスクトップ機で計算した行列をAndroidやiOSで使いたい。

計算結果を使い回したい理由はデータがそれなりに多かったり、計算量が多い場合に多くの計算機資源(つまり利用料)が必要となるからである。単に出力ファイルの形式が異なるという理由で言語ごとに同じ計算を繰り返すのは勿体ないからである。

例えばPythonやC++で計算した結果がCVSで出力されているとする。それをAndroidアプリケーションやiOSのアプリケーションで使うためにJavaやKotlin、Swiftに読み込ませたいことはよくある。

一つの方法としてAndroid/iOS双方で使えるSQLiteを介するというものがある。

特に大きなものならそれでもよいが、中程度のものの場合、オーバーヘッドが大きくなる。

そこで配列にそれらCVSを経由してデータを読み込ませる方法がある。読み込みは実行時ではなくてコンパイル時に済ませておきたい。つまり変数(定数)として入力しておきたい。

(ただし別項目で見たようにKotlinでは(Swiftでも場合によって)多次元配列は初期化、代入のステップを動的に「実行」して作成しなければならない。)

また言語によってデリミタなど成型方法が異なるため、生のCVSをそのままコピーして利用することもできない。

それに3x3程度の小さな行列ならいいが、100行以上にもなる場合もあるので、できるだけ自動的にヘッダなどを準備できるようにしたい。

そこで登場するのがsed/awkである。

sed/awkについて

いずれもUNIX系のOSにはほぼ標準で付いてくる。ただ、awkに関してmacosではかなり古いものしか入っていないのでLinuxとの整合性を考えてgawkを入れ、それを用いることにする。

sedのお役立ちパターン、コマンド集

sedはファイルそのものは変更しないで検索、置換後どうなるかを標準出力してくれる。必要に応じてパイプで新規ファイルに結果を入れればよい。

もちろん標準出力なので標準入力を要求するコマンドラインツールの入力へパイプで繋いでもよい。

具体的にsedを単発で使う場合、

sed パターンを含むコマンド 操作対象ファイル名

とやると操作結果が標準出力に出てくる。OKならパイプでそれを新たなファイルに入れればよい。

sed パターンを含むコマンド 操作対象ファイル名 > 操作結果ファイル名

実際にはいくつかコマンドがあるが、今回の目的には置換コマンドsだけで事足りる。

  1. シングルクォーテーションとダブルクォーテーションの交換
    やや分かりにくいパターンの一つ。
"s/\'/\"/g"

クォーテーションマークはエスケープしなくてはならないことが多い。そして内部にシングルクォーテーションを含むときにはダブルクォーテーションでコマンドを囲むのが「お約束」
(普通はシングルクォーテーションで囲む)
この例ではシングルクォーテーションをダブルクォーテーションへ置き換えている。
あとダブルクォーテーションを内部に一つだけ含む場合はエスケープしなくてよく、シングルクォーテーションで囲むことで扱える。
ここでgはグローバルフラグでファイルの最後まで、と言う意味である。

  1. 文字列の一部を置き換える
    デリミタや配列の入力形式を変換するのに用いる。
    この場合、なるべく共通する部分を長く取って「誤解」が生じないようにすることが大切。

  2. 行の先頭、または末尾の操作
    行単位でレコードを構成するケースが多いので、行頭、行末では括弧などの記号を付加する必要がある場合も少なくない。
    3.1. 行の先頭
    ‘/s/^置換対象/置換内容/g’
    3.2. 行の末尾
    ‘/s/置換対象$/置換内容/g’
    具体例は次の項目の3,4番目にある。

  3. 複数コマンドの連続適用(左から右の順)
sed -e "s/\'/\"/g" -e 's/000\"/\"/g' -e 's/^"/arrStr +=arrayOf("/g' -e 's/"$/")/g' targetFile > editedFile

-eオプションを使えば連続適用ができる。上の例ではシングルクォーテーションをダブルクォーテーションに交換、ついで「000″」を「”」に変換、行頭、行末にそれぞれarrStr +=arrayOf(、)を追加している。

便利だと思うコマンドができたらファイル化して-f オプションを使い

sed -f コマンドファイル 操作対象ファイル名 > 操作結果ファイル名

等とするのもよいアイデアである。

具体例

Pythonで出力した2次元String配列がある。内容はDate型を文字列にしたものだ。それをJava, Swift, Kotlinで扱うという例を挙げる。

今回は時間関係のデータを扱うためPython上で(特殊な)マイクロ秒だったものをミリ秒に変換するため末尾の000を取り除く必要もある。実際にはミリ秒までしか計算していないので下三桁は0なのである。

具体的な元データCSVファイルの中身はDate型へ変更したいString配列で

'2018-01-05 09:48:44.355000','2018-01-20 03:09:01.385000','2018-02-03 21:28:29.265000','2018-02-18 17:18:00.625000','2018-03-05 15:28:10.795000','2018-03-20 16:15:27.355000'
'2019-01-05 15:39:34.925000','2019-01-20 09:00:09.565000','2019-02-04 03:14:56.385000','2019-02-18 23:04:33.425000','2019-03-05 21:10:21.585000','2019-03-20 21:59:02.845000'
'2020-01-05 21:30:43.535000','2020-01-20 14:55:17.235000','2020-02-04 09:03:56.965000','2020-02-19 04:57:37.235000','2020-03-05 02:57:30.325000','2020-03-20 03:50:14.155000'

のような感じのものを想定する。

具体例1:上記Pythonで作成したシングルクォーテーションで囲まれたStringの2次元配列CSVファイルをJavaで使える形式に変換する

sed -e "s/\'/\"/g" -e 's/000\"/\"/g' -e 's/^"/{"/g' -e 's/"$/"},/g' targetFile.csv > editedFileJava.csv

あとはeditedFileJavaをJavaのソースコードに貼り付けて最初と最後を{}で閉じれば2次元配列が完成する。(Javaでは最終行に付け加わる,は取らなくても問題ないが取った方が気持ちが良いだろう。あとでSwiftやKotlinに使い回すなら取らないといけない。)

具体例2:Javaで使える形式になっている2次元配列をSwiftで使える形式に変換する

sed -e 's/{/[/g' -e 's/}/]/g' editedFileJava.csv > editedFileSwift.csv

あとはeditedFileSwiftをSwiftのソースコードに貼り付けて最初と最後を[]で閉じれば2次元配列が完成する。Swiftでは残っているなら最後の,を取らなくてはならない。

具体例3:Javaで使える形式になっている2次元配列をKotlinで使える形式に変換する

sed -e 's/{/arrayOf(/g' -e 's/}/)/g' editedFileJava.csv > editedFileKotlin.csv

あとはeditedFileKotlinをKotlinのソースコードに貼り付けて最初と最後をarrayOf()で閉じれば2次元配列が完成する。Kotlinでは残っているなら最後の,を取らなくてはならない。

具体例4:クォーテーションなしの「文字列」をダブルクォーテーションで囲む処理

sed -e 's/^/"/g' -e 's/000,/","/g' -e 's/000$/"/g' targetFile.csv > editedFile.csv

ここでも元ファイルは000を取り除く必要があるものとしている。不要なら2,3番目のパターンは’s/,/”,”/g’, ‘s/$/”/g”でよい。

awk

sedは基本的にパターンマッチングでファイルテキストを操作するだけなので、それだけでは足りない場合にawkの出番ということになる。

プログラミング言語なので条件分岐やカウンターが使える。

早速Kotlinに配列を取り込むためのコマンドを試みる。sedだけでは+=arrayOf()の形しかできないがawkを使えば

arr[0] = arrayOf()
arr[1] = arrayOf()
arr[2] = arrayOf()

の形式でエクスポートできそう。
実際

gawk '{printf("arr[%d] =  %s\n", NR-1, $0)}' sedで作ったファイル

とすれば上のパターンが標準出力に表示されるので、それをパイプでファイルへ導いてやれば、2次元配列を固定長配列に効率良く取り込むことが可能となる。

ここでprintfはC言語のものと同様のフォーマッタが使える。NRはレコード番号、$0はレコード全体、つまりデフォルトではそれぞれ行番号(1で始まる)、行全体が入っている。

まとめ

言語間における配列データのやり取りにsedやawkといったUNIX系のOSにはほぼ標準で付いているユーティリティを使う方法を学んだ。まさに温故知新とはこのこと・・・。

コメントを残す