作りました
経緯
自分はC#が好きなので、シェルでワンライナーを描いているときもふと、C#が使いたくなります。そういう時はMonoのREPLを使ったりしていたんですが、各行に対してC#を適応したいとき、書くコードが増えてしまうのでイマイチでした。
$ seq 10 | csharp -e 'string s;while((s=Console.ReadLine())!=null)Console.WriteLine(int.Parse(s) * 10)'
こんなの書いているうちに日が暮れてしまうので、大体awk
とかですますのですが、やはりワンライナー中にC#を書きたいんですよね。ええ。え!?PowerShell!?なにそれ!!!
というわけでawk
っぽく使えるC#ラッパーとしてocs
を作りました。アイデアはrbやopyと同じです。
インストール
dotnetがインストールされているならクローンしてdotnet publish
してもいいですが、ビルド済みのバイナリをReleaseにアップロードしているのでそれをダウンロードするのがいいかと思います
それでもビルドしたい人
$ git clone https://github.com/xztaityozx/ocs $ cd ocs # win向け、linuxならlinux-x64, macOSならosx-x64 $ dotnet publish --self-contained true -c Release -r win-x64 -p:PublishSingleFile=true -p:PublishReadyToRun=true -o ./bin $ ./bin/ocs --help #バイナリはこれ
zinitで管理する
zinit ice from"gh-r" as"program" pick"*/ocs" zinit light xztaityozx/ocs
使い方
まぁawk
と似た感じです
echo a b c | ocs '{println(F[1], F[2])}' a b
ブロックに対するパターンも使えます
seq 10 | ocs 'int.Parse(F0)%2==0{ println(F0) }' 2 4 6 8 10
本当はパターンだけを書けるようにしたかったんですけど、パーサーを書くのに疲れてしまいました。Issueにはするので、そのうち追加するかもしれないです。
# こうしたかった seq 10 | ocs 'int.Parse(F0)%2==0' 2 4 6 8 10
パターンにはbool
を返す式であればどんな式でも書けます(パーサーが壊れてなければですが)
$ seq 10 | awk '{print $1%2 ? "" : $1}' | ocs 'F0.Any(){println(F0)}' 2 4 6 8 10
BEGIN
とEND
も使えます
seq 100 | ocs 'BEGIN{var sum=0}{sum+=i(F0)}END{println(sum)}' 5050
usingしたいなら-I, --imports
を使います。
ocs -ISystem.IO '{ ... }' ocs -ISystem.IO,System.Text '{ ... }'
フィールドセパレータも指定できます。
$ cat csv | ocs -F, '{...}' # 正規表現もいける $ echo 1abc2ebc3dbc | ocs -F'.bc' '{println((F[1], F[2], F[3]))}' (1, 2, 3)
技術的な話
今回はC#コードの実行にRoslynのScripting APIを使いました
ocs
がやっていることはC#内でC#のソースを生成して、をコンパイルして、実行している。だけですね。生成されたコードを出力するオプションも用意しているので、試しにocs
が生成するコードを見てみます
$ seq 100 | ocs --show 'BEGIN{var sum=0}{sum+=i(F0)}END{println(sum)}' [ 14:36:14 | Information ] Generated Code var sum=0; using(Reader) while(Reader.Peek() > 0) { F0 = Reader.ReadLine(); sum+=i(F0); } println(sum); 5050
こんな感じです。内部で処理するだけなのでインデントもくそもないですが、読みやすくするとこうですね
var sum=0; using(Reader) while(Reader.Peek() > 0) { F0 = Reader.ReadLine(); sum+=i(F0); } println(sum);
それぞれwhile
の前か後か中かにコードが展開されるだけですね。F0やReaderなんかは予約されている変数です。この辺はREADMEを読んでもらうことにします。
パターン付きのアクションはどうなるんでしょう。
$ seq 100 | ocs --show 'BEGIN{int a,b}{a+=i(F0)}F0.Length==2{b+=i(F0)}END{println((a,b))}' [ 14:39:12 | Information ] Generated Code int a,b; using(Reader) while(Reader.Peek() > 0) { F0 = Reader.ReadLine(); a+=i(F0); if(F0.Length==2){b+=i(F0);}; } println((a,b)); (5050, 4905)
int a,b; using(Reader) while(Reader.Peek() > 0) { F0 = Reader.ReadLine(); a+=i(F0); if(F0.Length==2){b+=i(F0);}; } println((a,b));
こうですね。パターンを条件としたifが生成されます。とっても単純ですね。
おわり
とりあえずやりたかったことはできたのでうれしいです。ひとつ難点として挙げられるのはバイナリサイズがめちゃめちゃデカいことですね。Releaseにアップロードしているものは40MBぐらいなのでwgetするのに若干時間がかかるんですよね。まぁ仕方ないんですが・・・。バイナリサイズを落とすコンパイルとかも試したんですが、ocsが動かなくなるのであきらめました。悲しいのだ
まぁ良かったら使ってやってください