たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

第45回シェル勉強会に参加しました

参加しました

参加しました。

午前

今回は前回の続き、ぷるさんによるJavaScriptの非同期処理についてでした。

www.pscp.tv

例とかを出しながら解説していただきました。JSは各機能の名前ぐらいしか知りませんでしたが、いくつかの単語について分かったので良かったです。ところでニコ生感ありましたね
午後からの会場に移動しないと行けなかったので最後の方は見れなかった…もう一度見直そうと思います。

午後

午後はいつものようにつらい問題を解きました。問題と解答はここです。
とてもつらかったです

時間中はbash/zshのみでの解答をしてましたが、参加記ではPowerShellでの解答にもチャレンジします。

Q1

Shift-JISで書かれたCSVを読み、トマト、ピーマン、バナナについて最後に書かれている日付と個数を出力する問題

$ cat Sh*/vol.45/data.csv|iconv -f sjis|sort -V|tr -d \r|awk -F, '{box[$2]=$1" "$3" "$2}END{for(x in box)print box[x]}'
2019/12/30 4個 トマト
2019/12/21 5個 バナナ
2019/11/21 32個 ピーマン

iconv -f sjis を使ってShift-JISからUTF8に変換します。次にsort-Vを使って各レコードを日付順に並べ替えます。最後にawk連想配列に入れて出力しています。

$ (cat -Encoding sjis .\ShellGeiData\vol.45\data.csv) -replace "/(.),","/0`$1,"|sort|% -begin {$box=@{}} {$L="$_".Split(',');$box[$L[1]]="$_"} -end {$box}
Name                           Value
----                           -----
バナナ                            2019/12/21,バナナ,5個
トマト                            2019/12/30,トマト,4個
ピーマン                           2019/11/21,ピーマン,32個

PowerShellでも方針は同じにしました。解説しています。
PowerShellcatGet-Contentエイリアスされています。 Get-Content-Encoding オプションでエンコードを指定できます。sjisを指定して data.csv を読み出します。 sort を使って日付を並べ替えたいですが、日付順にソートができなさそうだったので、日付を文字列としてソートしたいです。そのために、0埋めします。 -replace を使います。replaceの引数は old,new という感じで置換文字列を指定します。正規表現も扱えて、キャプチャーも可能です。今回だと (.) で1文字を引っ掛けて、後ろの `$1 で参照しています。

置換後の出力を載せておきます

$ (cat -Encoding sjis .\ShellGeiData\vol.45\data.csv) -replace "/(.),","/0`$1,"
2019/12/06,トマト,3個
2019/11/23,バナナ,2個
2019/11/08,ピーマン,31個
2019/12/30,トマト,4個
2019/11/02,トマト,1個
2019/12/09,バナナ,4個
2019/12/21,バナナ,5個
2019/11/21,ピーマン,32個
2019/12/01,トマト,7個

これで sort すると日付順に並ぶので、後は ForEach-Object連想配列に入れて出力しています。

Q2

日経平均株価の日時データから毎月の終値の最大値と最小値を求める問題

$ cat nikkei_stock_average_daily_jp.csv|iconv -f sjis|sed 1d|awk -F, 'NF>=2{print $1,$2}'|awk -F"[/ ]" '{print $NF,$1","$2}'|tr -d \"|sort -k2,2 -k2,1|pee "uniq -f1" "tac|uniq -f1"|sort -k2|xargs -n4|awk '{print $2, $1,$3}'
出力略
...

これもエンコーディングがShift-JISなので変換します。1行目はヘッダーなので、sed を使って削除します。次に awk で1カラム目と2カラム目を取り出します。入力がCSVなので -F, でフィールドセパレーターをカンマにしています。
sort-k でキーを指定して並び替えます。日付で並べ替えた後、値で並べ替えます。 pee に渡します。pee では uniq -f1tac|uniq -f1 にSTDINから受け取った文字列を渡し、最大値と最小値のレコードを取り出します。そして日付で sort し直し、xargs,awkで整形して終わりです。

PowerShellでもやってみます

$ Import-Csv -Encoding sjis .\nikkei_stock_average_daily_jp.csv|? {$_.終値 -gt 0}|% {@{D=(($_.データ日付) -replace "/..$",""); V=$_.終値}}|% -begin{$box=@{}} 
 {if($box.Contains($_.D)){$box[$_.D].Max=[System.Math]::Max($_.V, $box[$_.D].Max); $box[$_.D].Min=[System.Math]::Min($_.V, $box[$_.D].Min)}else{$box[$_.D]=@{Max=$_.V; Min=$_.
V}}} -end {$box.Keys|%{"$_"+",Max="+$box[$_].Max+",Min="+$box[$_].Min}}
出力略
...

長いな…。解説します。
Import-Csv を使うと1行目をカラム名としたオブジェクトの配列を作ってくれます。

$ Import-Csv -Encoding sjis .\nikkei_stock_average_daily_jp.csv|head -n15

データ日付 : 第一カラム
終値    : 第二カラム
始値    : 第三カラム
高値    : 第四カラム
安値    : 第五カラム
...

最後の注意書きの部分もパースされちゃうので、そこを削除するために ?(Where-Object) を使って除外しました。日付のフォーマットを整えつつ日付と終値のペアを作り、それぞれのMaxとMinを取っていきます。最後に出力して終わりです。Max,Minを取るところがちょっと冗長なのが辛いですね。

Q3

flags_aflags_b には国旗が書いてある。2つのファイルの間で左上から数えて何個目の国旗が違うかを出力する問題。なんだこれは

$ cat Sh*/vol.45/flags_a|grep -o .. | diff - <(grep -o .. Sh*/vol.45/flags_b)|xargs -n3|awk -F"[a-z]" '{print $1}'
39
65

国旗は2文字の組み合わせで表現されるので、2つのファイルを grep -o .. で2文字ずつに分解し、diff にかけます。後は出力を整形して終わりです。

PowerShellでもやってみます。

$ diff ([System.Text.Encoding]::UTF8.GetBytes( (cat .\ShellGeiData\vol.45\flags_a))|xargs -n8|nl) ([System.Text.Encoding]::UTF8.GetBytes( (cat .\ShellGeiData\
vol.45\flags_b))|xargs -n8|nl)|sls "=>"|%{$_}|awk 'NF>=1{print $2-1}'
36
65

こっちでは grep -o .. できれいに分割できなかったので、バイト列に変換し、8バイトずつに切り分けてから diff しました。ちょっと整形に awk 使ってます…セパレートするの面倒なんですよねPowerShell

Q4

ワタナベエンコードされた文字列をIVSを考慮しながら1文字ずつに分解する問題。母語じゃん

$ cat Sh*/vol.45/nabe|perl -C -lne "print for /\X/g"
部
邊
邊󠄓
邊󠄓
邉
邉󠄊
邉󠄂
邊
邊󠄓
邊󠄓

perl\X を使いました。書記素クラスタを考慮した文字列分解がこれで可能です。 uconv を使ってユニコード正規化し、grep -o . で分解できるかな〜と思ったんですけどダメでした。

PowerShellでもやってみます

$ [System.Globalization.StringInfo]::GetTextElementEnumerator((cat .\ShellGeiData\vol.45\nabe))
部
邊
邊
邊
邉
邉
邉
邊
邊
邊
邉

(うちのターミナルだと文字化けちゃったのでここでは整形してます。)
.NETのDLLが使えるのでこっちでも楽ですね。System.Globalization.StringInfo.GetTextElementEnumerator を使うと書記素クラスタを意識した文字列分割ができます。比較的シェルフレンドリーですねワタナベエンコード

Q5

文字列の部分文字列から回文を列挙する問題。全問までで疲れ果てていたので時間内に解けてない。

この問題はやり直してみたところ解答とほぼ同じになった(rubyではなくxargsecho @ $(echo @|rev)したぐらい)なのでbash/zshは省略で

PowerShellでもやってみます。

$ cat ./ShellGeiData/vol.45/message|%{for($i=0;$i -le $_.Length;$i++){$_.SubString(0,$i)}}|%{for($j=0;$j -le $_.Length;$j++){$_.SubString($j)}}|Sort-Object -Unique|?{$_.Length -ge 2 -and $_ -eq ([string]::Join("",$_[($_.Length-1)..0]))}
おかしがすきすきすがしかお
かしがすきすきすがしか
がすきすきすが
きすき
きつつき
けやぶやけ
しがすきすきすがし
すきす
すきすきす
たけやぶやけた
つつ
とまと
やぶや
ゆんゆ
んゆん

2回の ForEach-Object で部分文字列を全列挙してから Where-Object で条件に合う文字列を絞り込んでいます。stringにはReverse()メソッドが生えてると思ってたんですけどあれはLinqのだったんですね…。ということで [string]::Join("",$_[($_.Length-1)..0]) でReverseした文字列を作っています。

Q6

Q5の答えから部分文字列を削除する問題。例えば「つつ」は「きつつき」の部分文字列なので削除する。という感じですね。やってみます。

$ cat ./vol.45/message.ans|xargs -I@ grep -o @ ./vol.45/message.ans|sort |uniq -c|awk '$1==1{print $2}'
おかしがすきすきすがしかお
きつつき
たけやぶやけた
とまと
ゆんゆ
んゆん

xargs で1レコードずつ grep に渡します。-o をつけているので、マッチした部分だけ出力されます。こうすると部分文字列は複数マッチし、そうでない文字列は1度しかマッチしません。そこで sort|uniq -c で個数とともに出力し、awk で整形して出力しています。

PowerShellでもやってみます。

$ cat ./ShellGeiData/vol.45/message.ans|%{cat ./ShellGeiData/vol.45/message.ans|sls $_|%{$_.Matches.Value}}|group|?{$_.Count -eq 1}|select Name

Name
----
おかしがすきすきすがしかお
きつつき
たけやぶやけた
とまと
ゆんゆ
んゆん

slsSelect-String へのエイリアスです。STDINなどの入力からマッチする部分をMatchInfo型で返します。bash/zsh版でもやったようにマッチした部分だけほしいので MatchInfo.Matches.Value プロパティを取得します。ここまでの出力を見てみます

$ cat ... $_.Matches.Value}}
つつ
つつ
きすき
きすき
きすき
きすき
きすき
きすき
すきす
すきす
すきす
すきす
すきす
すきす
とまと
やぶや
やぶや
やぶや
ゆんゆ
んゆん
きつつき
けやぶやけ
けやぶやけ
すきすきす
すきすきす
すきすきす
すきすきす
すきすきす
がすきすきすが
がすきすきすが
がすきすきすが
がすきすきすが
たけやぶやけた
しがすきすきすがし
しがすきすきすがし
しがすきすきすがし
かしがすきすきすがしか
かしがすきすきすがしか
おかしがすきすきすがしかお

いい感じですね。ここからレコードの個数を取得して、個数が1であるレコードだけ出力すれば良さそうです。Sort-Object なんかをこねこねすれば行けそうですが Group-Object という便利なコマンドレットがあります。Group-Object は レコードを GroupBy し、GroupInfo型を返します。GroupInfo はグループに含まれるレコード数を表す Count プロパティがあるので、これが1なグループだけ出力すればOKですね

Q7

循環少数を永遠に出力する問題。言い換えるとシェル上で割り算の筆算をする問題

$ echo 1 7|awk '{print "0."; for(i=0;i<10;i++){$1*=10; print int($1/$2); $1=$1%$2}}'|tr -d \n
0.1428571428

無限とのことだったんですが10桁にしました。無限にするなら while(1) にすればいいですね。
やっていることはまぁ割り算の筆算を実装しただけですね。うん

PowerShellでもやってみます。

$ echo "1 7"|%{$L=$_ -split ' '|%{[int]$_};$X=$L[0];$Y=$L[1];for($i=0;$i -le 10;$i++){$X*=10;[int]($X/$Y);$X=$X%$Y}}|% -begin{$O="0."} {$O=$O+$_} -end {$O}
0.14396714396

特にややこしいことはしていません。

Q8

循環少数の何桁目から何桁目が循環部分になっているのかを出力する問題。この辺の記憶はない

$ echo 1 7|awk '{while(1){$1*=10; print int($1/$2), $1%$2; $1=$1%$2}}'|awk 'box[$2]==2{exit}{box[$2]++;if(box[$2]==1){printf $1}}'|awk '{print "0."$0, 1, length($0)}'
0.142857 1 6

同じ余りが出現すると循環少数が一周したことになるので循環が見つかるまで出力し続けて最後に長さとともに出力しています。しかしこれ 0.xyzxyz みたいなパターンしか無理ですね

LT

今回もLTしました。聴いてくださった方々ありがとうございました!

www.slideshare.net

ワタナベエンコーディング1周年ということなのでワタナベシェルを作りました

github.com

まとめ

今回もめちゃくちゃ疲れました。翌日まで披露が残るので大変です。設営、企画の皆様いつもありがとうございます。

雑記 2019-12-28

雑記

毎日楽しい

シェル芸勉強会

第45回シェル芸勉強会に参加しました。簡単な感想は「はちゃめちゃ疲れた」です。詳しくは別記事にしようと思います

グッチー

ただ文句言いたいだけというのは、まぁある。できれば共有しないでほしいんだけど、ただ文句言いたいだけの人に対して文句を言っているこの文章もできれば書かないほうがいいってことなんだよね。そうしてるうちに書くことなくなっちゃいそうだね

終わり

終わり

雑記 2019-12-27

雑記

ひえ

gonum

論文に貼るためのグラフを、Excelでやるのが面倒だったのでgonumを使って書いてた。結局対数グラフのメモリをいい感じにするのに2時間ぐらいかかった。多分その間にExcelでグラフかけてたけど…まぁ使い回せるしOKかな

おわり

ここのところ毎日更新してたけど3日に1度くらいということを忘れていた。

雑記 2019-12-25

雑記

めりくり

信じること

就活してるとき、面談の場で「ある情報が正しいかどうかどうやって判断しますか」みたいな質問をされた。詳しそうな人に聞く?そいつは正しいのか?公式のサイトを見る?そこが公式のサイトなのか?そもそも公式が正しいと誰が決める?
僕は何かの情報を得たとき無意識に受け入れていると思った。

夜中の電車

いつもより遅い時間に電車に乗った。なんだか車内の雰囲気が違った。
割と人が乗っていた。みんなこんな時間に電車になんの用があるんだろうね

おわり

油ものが胃にキツイ。体がどんどんおわる

雑記 2019-12-24

雑記

徹夜は良くない

ねむい

研究室で徹夜して朝8時。家に帰ろうと思って学校を出、駅に向かう途中歩きながら寝ていた。人間歩きながら眠ることが可能なんだなぁと思った。

おわり

日付変わってから投稿してちゃ意味ないんだなこれが

誕生日なので難読化シェル芸した

今日はお誕生日

今日は紲星あかりちゃんのお誕生日です!

www.ah-soft.com

ついでに私のお誕生日でもあります。というわけでお誕生日に関係する文字列から date コマンドを作っていきたいと思います

1

お誕生日おめでとう始動

$ : $(echo お誕生日おめでとう|base64|grep -oP "(?<=5)([a-z][a-z])"|sed 's/.$/&&/g'); man $_ | grep -m1 -oP "(?<=- )..|..(?=rd)"|sort|tr -d 
|bash
20191222日 日曜日 12:03:45 JST

こんなかんじ

$ echo お誕生日おめでとう|base64|grep -oP "(?<=5)([a-z][a-z])"|sed 's/.$/&&/g'
pee

「お誕生日おめでとう」という文字列に不思議なシェル芸をすると pee という文字列が得られます。

$ man pee | grep -m1 -oP "(?<=- )..|..(?=rd)"
te
da

pee のmanページからこんな感じの正規表現で引っ掛けてやるとなんと date の部品が得られます。というわけで改行と順番を入れ替えてやれば date が出来上がります。

$ man pee | grep -m1 -oP "(?<=- )..|..(?=rd)" | sort | tr -d \n
date

2

$ : xztaityozx; : ${_/??}; eval $(echo ?${_/${_/??}}?/???/|rev)
20191222日 日曜日 12:55:44 JST

私の名前から date を作ります。 ta を変数展開で取り出し、glob展開用の文字列を作ってから eval で評価します。

$ : xztaityozx; : ${_/??};
taityozx
$ : xztaityozx; : ${_/??}; echo ?${_/${_/??}}?/???/|rev
/???/?at?

3

$ echo 紲星あかり|sha256sum|grep -oP "(?<=f19).{6}"|sed 's/[0-9]../?/g;s@^@/???/@e'
20191222日 日曜日 13:24:08 JST

せっかくなのでハイパーキュートVOICEROIDな紲星あかりちゃんからも date を生成しました。とくに追加で説明するところと言えば sede を使っているところぐらいですかね

$ echo |sed 's/^/date/'
date
$ echo |sed 's/^/date/e'
20191222日 日曜日 13:25:55 JST

結局いつもやってることと同じじゃないか…

最近は正規表現をこねこねして date するのが楽しいです。任意の文字➡アルファベットや数値という処理で sha1sumbase64xxd に頼らないといけないので、ここを何とかする方法が見つかればいいなぁと思っています。