第61回シェル芸勉強会に参加しました
今回も午後のみ。余裕があればPowerShell解も提示します。出題された問題などは配信アーカイブのこの辺から
Q1
inout
ファイルにA,B,Cさんの入退出記録が書いてあります。入退出が矛盾する人がいるので、その人を見つけてくださいという問題。
A 入 C 入 B 入 B 出 A 出 C 入 B 入 B 出 C 出
$ cat Sh*/vol.61/inout | awk '{a[$1]=a[$1]""$2}END{for(i in a)print i, a[i]}' | grep -Pe "(.)\1"
1列目をキーに連想配列に入退出記録を詰めていき、それを出力します。そうすると以下みたいになります
A 入出 B 入出入出 C 入入出
出出
とか入入
みたいになっているのは矛盾している部分なのでそれを見つけるようなgrep
を書いてひっかけるって感じですね。これだと 出
から始まる矛盾に対応できないというのをTwitterで見かけて確かにと思いました。その場合は grep -Pe "(.)\1|^\d+ 出"
みたいにすればできそうですね。
PowerShell解も示します。方針は同じ感じです
$ cat .\inout | %{@{K=$_[0];V=$_[2]}} | group -Property K | %{$_.Name + " " + (($_.Group|%{$_.V}) -join "")} | sls '(.)\1'
Group-Object
を使うために一回Hashtableにしたんですがこれやらない方法ないんですかね?
Q2
data
ファイルから3行連続で文字列が記述されている部分を抜き出す問題。ファイルの内容はともかく、割と普段もやる感じな問題ですね。
$ cat Sh*/vol.61/data | tr \\n @ | sed -E 's/@@+/\n/g;s/@$//g' | grep -Pe '^[^@]*@[^@]*@[^@]*$' | tr @ \\n $ cat Sh*/vol.61/data | awk '!NF{print a;a=0}NF{a++;print}END{print a}' | grep 3 -B3 | tr -d 3-
一つ目のほうは改行を@
に置き換え。そのあと特定の個数だけ@
が現れるようなものだけ取り出して改行を復元しています。二つ目の方は連続が途切れたタイミングで連続していた量を出力しgrep
の-B
でひっかけるというものですね。
二つ目の方いいな~と思ったので、同じ方針でPowerShellでもやってみます。
$ cat .\data | %{if($_ -eq ""){echo $x;$x=0}else{$x++;echo $_}} -Begin {$x=0} -End {if($x -eq 3){echo $x}} | sls -Context 3,0 3
Select-String
は-Context
で前後行を出力できます。a,b
みたいにすると前a
行、後ろb
行になります。しらんかった
Q3
1~100までの数値が区切り無しで一行に書かれたnums
というファイルがあります。これに適切にスペースをいれてくださいという問題。難しそう
$ cat Sh*/vol.61/nums | sed -E 's/[1-9]0?0/\n&/g' | sed '1s/./& /g;2,10s/../& /g' | xargs
1つめのsed
までで以下のようになります
123456789 10111213141516171819 20212223242526272829 30313233343536373839 40414243444546474849 50515253545556575859 60616263646566676869 70717273747576777879 80818283848586878889 90919293949596979899 100
いい感じに別れましたね。あとは2つめのsed
でスペースを挿入していきます。a,b
を付けて適用する行を選んでいます。
PowerShellでもやり…ますか…。
$ ((cat .\nums) -replace "([1-9]0?0)","`n`$1" -split "`n" | % -Begin{$NR=0} {$NR=$NR+1;if($NR -eq 1){$_ -replace "(.)","`$1 "}elseif($NR -ge 2 -and $NR -le 10){$_ -replace "(..)","`$1 "}else{$_}}) -join ""
愚直~。
Q4
file1
の最後の改行を削除する問題。この問題の逆はたまにやりますね
$ cat Sh*/vol.61/file1 | xxd -p | sed 's/..$//' | xxd -p -r $ cut -zb -"$(($(wc -c She*/vol.61/file1|sel 1)-1))" She*/vol.61/file1
1つめはxxd
でバイト列にし後ろの1バイトを削除。後ろの1バイトは改行なのでバイト列から元の文字列に戻すと改行が消えるという感じですね。二つ目はcut
コマンドで先頭からNバイト取り出すという感じ。Nバイトを動的に計算するためにwc
を使っています
PowerShellでもやってみます。
$ $a=(cat -AsByteStream ./file1);[System.Text.Encoding]::UTF8.GetString($a[1..($a.Length-2)])
後ろの1バイトを削る方針です。.NETのクラスとかが使えるので魔術感が減って読みやすい感じが…しますね?
Q5
nums2
というファイルは以下のような内容になっています。
12423285230975943
これを以下のように成形するという問題。
1 3 5 3 9759 3 242 28 2 0 4
$ cat Sh*/vol.61/nums2 | sel -D@ 1 1 | teip -d@ -f2 -- sed -E 's/[13579]/ /g' | teip -d@ -f1 -- sed 's/[24680]/ /g' | tr @ \\n
teip
を使いたかった…。1行1列を1行2列にし、それぞれ奇数をスペースに、偶数をスペースにするsed
を書いて改行して終了。という感じですね。でもこれ2行1列にしてsed
のアドレスでいいですね…。
PowerShellだと以下のような感じです。
$ cat .\nums2 | %{$_ -replace "[24680]"," "; $_ -replace "[13579]"," "}
複製しなくても$_
に対して2回置換をやるだけでいいってわけですね。
ところでこの問題。数値を使わずにというオプションもあったのですが、それをPowerShellでやろうとすると
$ cat ".\nums$($true+$true)" | %{$_ -replace "[$($true+$true)$($true+$true+$true+$true)$($true+$true+$true+$true+$true+$true)$($true+$true+$true+$true+$true+$true+$true+$true)]"," "; $_ -replace "[$(+$true)$($true+$true+$true)$($true+$true+$true+$true+$true)$($true+$true+$true+$true+$true+$true+$true)$($true+$true+$true+$true+$true+$true+$true+$true+$true)]"," "}
ウワー!!!
Q6
nums3
のうち現在の行からみて3つ先が3倍の数な行を取り出す問題。ムズ
$ cat Sh*/vol.61/nums3 | fmt -1 | paste - <(fmt -1 Sh*/vol.61/nums3|tail +4) | awk '$1*3==$2{print $1}' # awkなし $ cat Sh*/vol.61/nums3 | fmt -1 | sel 1 1 1 | surge -- jq -s add | paste <(fmt -1 Sh*/vol.61/nums3) <(fmt -1 Sh*/vol.61/nums3|tail +4) - | grep -Pe '(\d+)\s(\d+)\s\2$' | cut -f1
paste
を使ってnums3
にnums3
をくっつけます。このときtail
で3行先が横に来るようにしておきます。あとはawk
で左右を比較すればいいですね。
そしてこの問題、awk
を使わずに…というものがありました。それが2つめの解答なんですが…。面倒なので割愛です。
PowerShellでは以下のようにしてみました
$ $a=((cat .\nums3) -split ' ');[System.Linq.Enumerable]::Zip($a, ($a[3..($a.Length-1)]), [Func[Object,Object,Object[]]]{ param($x,$y) @{x=$x;y=([int]$x*3 -eq $y)}}) |?{$_.y}|%{$_.x}
Zip
思ったよりつらいですね
Q7
再帰的に計算を行い表を完成させる問題。
$ a() {awk '{s+=$1*$NF}END{print s}' Sh*/vol.61/prior} ; b() {awk -v X=$1 -v Y=$2 '{print $0,(Y=="表"?$1:(1-$1))*$NF/X}' Sh*/vol.61/prior}; cat Sh*/vol.61/coin | grep -o . | while read c; do b "$(a)" "$c" | sponge She*/vol.61/prior; done;
正規か前の次の$NF
の合計をするa()
と、その合計をもらって正規化しながら$NF
を追加していくb()
を定義していい感じに呼び出します。わざわざ関数にしたんですがしなくてよかったやつですねこれね。再帰的に行いたいので結果はsponge
で元のファイルに書き込んでいます。
PowerShell版は以下のような感じ。めちゃめちゃや
$ cat coin | %{$c=$_; $content=($content | %{$x=($_ -split " ");$y=+$x[0];if($c -eq "裏"){$y=1-$x[0]} "$_ $($y*+$x[1]/$s)"} -Begin{$content=(cat .\prior); $s=($content | %{$x=($_ -split " ");+$x[0]*+$x[1]} | measure -Sum).Sum})}; $content
LT
今回もLTさせていただきました!前回の続き物という感じです。
読み取り間違いをどうにか抑えていこう!って感じの取り組みでした。でも本番一発勝負では失敗しました。魔術師になるのは難しい。
おわり
今回はデータ整形系の問題が多くて試行錯誤とか変換過程をみるのが楽しかったです!企画運営の皆様ありがとうございました!