たいちょーの雑記

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

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

参加しました

今回は声を出す参加者を募集してYouTubeで配信というスタイルだったようです。会場のうめき声を思い出させるような感じになるかと思いましたが、最後の方は無言になってましたね…そういえば会場の時もうめき声というよりは無言だったような…

さて今回も最中に解けたものに関して解答を残しておきます。先にネタバレをしておきますが、今回は最後の数問が激ムズでした。

問題と解答はここYouTubeの配信はここです

Q1

big_dipperファイルにはAAで北斗七星が書いてあります。

$ cat big_dipper
*

      *

      *

       *

   *


             *
       *

ほんとだ・・・。さてこれの*に「チキチキボーン」を順番に当てはめてくださいという問題。早速やってみます。

$ cat ./Sh*/v*52/chiki | grep -o . | xargs -I@ echo '0,/\*/ s/\*/@/' | sed -f- ./Sh*/v*52/b*
チ

      キ

      チ

       キ

   ボ


             ー
       ン

これがかの有名なチキチキボーン七星か。さて解答についてですが、まずはxargssedの命令を生成していきます。

$ cat ./Sh*/v*52/chiki | grep -o . | xargs -I@ echo '0,/\*/ s/\*/@/'
0,/\*/ s/\*/チ/
0,/\*/ s/\*/キ/
0,/\*/ s/\*/チ/
0,/\*/ s/\*/キ/
0,/\*/ s/\*/ボ/
0,/\*/ s/\*/ー/
0,/\*/ s/\*/ン/

最初にマッチした*だけを置換してほしいので、0,/\*/で置換範囲を制限しています。あとはこれをsedに与えればよいですね。

PowerShellでの解答。こちらもsedを使うのが楽なので、sedを使います。ということで、上の命令を生成するところまでやってみましょう

$ (cat .\chiki -Encoding UTF8) -split '' | ?{$_} | %{"0,/\*/ s/\*/$_/"}
0,/\*/ s/\*/チ/
0,/\*/ s/\*/キ/
0,/\*/ s/\*/チ/
0,/\*/ s/\*/キ/
0,/\*/ s/\*/ボ/
0,/\*/ s/\*/ー/
0,/\*/ s/\*/ン/

特に難しそうなところはないですね。あえて取り上げるとしたら、?{$_}で空行を削除しているところですかね。

ところでチキチキボーンって何?

Q2

チキチキボーンを連続して出力してください。ただし文字は^の上かvの下に出力してください。^vupdownというファイルに記述されています。という問題

$ cat Sh*/vol.52/updown | grep -o . | paste - <(yes $(cat Sh*/vol.52/chiki)|head -n3|grep -o .|head -n20) | awk '$1 == "^"{print $2" ^  "}$1 == "v"{print "  v "$2}' | rs -t | sed 's/  //g'
チ チキボー チキチキボ     キ  
^ v ^ ^ ^ ^ v ^ ^ ^ ^ ^ v v v v v ^ v v
 キ    ン     ーンチキチ ボー

横に展開するデータをシェル芸で作るのは難しいので、まずは縦で作ってから転地するという方法。たまにありますよね。

$ cat Sh*/vol.52/updown | grep -o . | paste - <(yes $(cat Sh*/vol.52/chiki)|head -n3|grep -o .|head -n20)
^       チ
v       キ
^       チ
^       キ
^       ボ
^       ー
v       ン
^       チ
^       キ
^       チ
^       キ
^       ボ
v       ー
v       ン
v       チ
v       キ
v       チ
^       キ
v       ボ
v       ー

まずはこんなのを作っておきます。そしてawkなりで、^なら左に、vなら右にチキチキボーンを並べてきます。成形のため、反対側には全角スペースを置いておくといいと思います

$ cat Sh*/vol.52/updown | grep -o . | paste - <(yes $(cat Sh*/vol.52/chiki)|head -n3|grep -o .|head -n20) | awk '$1 =
= "^"{print $2" ^  "}$1 == "v"{print "  v "$2}'
チ ^  
  v キ
チ ^  
キ ^  
ボ ^  
ー ^  
  v ン
チ ^  
キ ^  
チ ^  
キ ^  
ボ ^  
  v ー
  v ン
  v チ
  v キ
  v チ
キ ^  
  v ボ
  v ー

なんかそれっぽくなりましたね。これの縦と横を入れ替えれば目的の形になりそうですが、これにはtateyokors -Tを私はよく使います

$ cat Sh*/vol.52/updown | grep -o . | paste - <(yes $(cat Sh*/vol.52/chiki)|head -n3|grep -o .|head -n20) | awk '$1 =
= "^"{print $2" ^  "}$1 == "v"{print "  v "$2}' | rs -T | sed 's/  //g'
チ チキボー チキチキボ     キ  
^ v ^ ^ ^ ^ v ^ ^ ^ ^ ^ v v v v v ^ v v
 キ    ン     ーンチキチ ボー

最後のsedは成形用ですね。

今度はPowerShellでの解答です。

$ (cat .\updown) -split '' | ?{$_} |% -Begin { $a=$b=$i=0; $c=@("","",""); $chiki=(cat -Encoding UTF8 ./chiki) }  { $d=[int]($_ -eq "^"); $c[0]+=@(" ", $chiki[$i%7])[$d]; $c[1]+="$_ "; $c[2]+=@($chiki[$i++%7], " ")[$d] } -End { $c }
チ チキボー チキチキボ     キ  
^ v ^ ^ ^ ^ v ^ ^ ^ ^ ^ v v v v v ^ v v
 キ    ン     ーンチキチ ボー

そう…ですね…Foreach-Objectのごり押し…awkごり押しみたいなもんです。

Q3

出来るをできるにして、side-by-sideでdiffを表示する問題

自分はdiffの代わりにdeltaをよく使うのでそれでやりました。diff -yでも同じことができるとのこと。

github.com

$ delta -s <(sed 's/出来/でき/g' Sh*/vol.52/dekiru) ./Sh*/vol.52/dekiru 

Q4

aho.htmlから間違ってるタグを見つけてください

一発で見つける必要はなくて、使われているタグを集計して、目grepで間違いを探すというのがよさそうです。

$ cat Sh*/v*52/aho.html | grep -oPe "<[^ >]+>|</[^>]+>" | sort | uniq -c | sort -rn 
    269 </a>
    258 </span>
     54 </div>
     31 </p>
     29 <p>
     25 </li>
     23 <li>
     18 </code>
     17 </script>
     16 </pre>
     12 </ul>
     11 <ul>
     12 </ul>
     11 <ul>
     10 </i>
      9 </h2>
      8 <code>
      7 </h4>
      7 </h3>
      6 <pre>
      6 </aside>
      5 <script>
      3 <span>
      3 </button>
      2 <aside>
      2 </ins>
      2 </h5>
      2 </h1>
      1 <title>
      1 <hr>
      1 <head>
      1 <h1>
      1 <footer>
      1 <body>
      1 </title>
      1 </strong>
      1 </spin>
      1 </nav>
      1 </html>
      1 </head>
      1 </form>
      1 </footer>
      1 </body>

</spin>があるのでこれがおかしいですね。あとは場所を特定するだけです

$ cat Sh*/v*52/aho.html | grep --color=auto -n spin
293:<a class="sourceLine" id="cb10-2" title="2"><span class="fu">awk</spin> <span class="st">&#39;{t=2*3.14*$2/1000000000;c=cos(t)*5+5;s=sin(t)*10+13;</span></a>}

293行目とのことでした。

さて次はPowerShellですね。集計は以下のようにしてやります

$ cat .\aho.html | sls "<[^ >]+>|</[^>]+>" | %{$_.Matches.Value} | group | sort Count -Descending

Count Name                      Group
----- ----                      -----
  152 </span>                   {</span>, </span>, </span>, </span>...}
   69 </a>                      {</a>, </a>, </a>, </a>...}
   29 <p>                       {<p>, <p>, <p>, <p>...}
   24 </div>                    {</div>, </div>, </div>, </div>...}
   23 <li>                      {<li>, <li>, <li>, <li>...}
   14 </script>                 {</script>, </script>, </script>, </script>...}
   12 </ul>                     {</ul>, </ul>, </ul>, </ul>...}
   10 </i>                      {</i>, </i>, </i>, </i>...}
    8 </h2>                     {</h2>, </h2>, </h2>, </h2>...}
    7 </h3>                     {</h3>, </h3>, </h3>, </h3>...}
    6 </aside>                  {</aside>, </aside>, </aside>, </aside>...}
    6 <pre>                     {<pre>, <pre>, <pre>, <pre>...}
    6 <ul>                      {<ul>, <ul>, <ul>, <ul>...}
    4 <script>                  {<script>, <script>, <script>, <script>}
    4 </code>                   {</code>, </code>, </code>, </code>}
    3 </button>                 {</button>, </button>, </button>}
    2 </ins>                    {</ins>, </ins>}
    2 </p>                      {</p>, </p>}
    2 <aside>                   {<aside>, <aside>}
    2 </li>                     {</li>, </li>}
    1 </body>                   {</body>}
    1 </html>                   {</html>}
    1 <footer>                  {<footer>}
    1 </spin>                   {</spin>}
    1 <hr>                      {<hr>}
    1 </footer>                 {</footer>}
    1 <body>                    {<body>}
    1 </form>                   {</form>}
    1 <title>                   {<title>}
    1 </head>                   {</head>}
    1 </nav>                    {</nav>}
    1 </h1>                     {</h1>}
    1 </strong>                 {</strong>}
    1 <h1>                      {<h1>}
    1 <head>                    {<head>}

grep -oに相当するのが sls "pattern" | %{$_.Matches.Value}です。普段使いしていないので、すぐ忘れてしまう。

Q5

annotation.mdには脚注があります。これをTeX形式の\footnote{...}にしながら元の場所に埋め込んで下さいという問題。

激しそうだけど、sedの命令を作り上げるのが良さそうということで、早速やってみます

$ cat Sh*/v*52/annotation.md | tail -n4 | rargs -p "\[\^(.+)\]: (.+)" echo "s/([^^])\[\^{1}\]/\1\\\footnote{{2}}/" | sed -E -f- ./Sh*/v*52/annotation.md
# Aについて

A\footnote{Aの起源は室町時代に遡る。}は素晴らしい。
Aのに似たものとしてB[^about_b]が存在するが、やはりAには及ばない。
他方でAに匹敵すると言われるC\footnote{Cの起源は江戸時代に遡る。}が近年注目を集めているが、これについても触れたい。
CとはもともとはDを発展させたものであり、F[^abort_f]という別名もある。

[^about_a]: Aの起源は室町時代に遡る。
[^about_c]: Cの起源は江戸時代に遡る。
[^about_d]: Dの起源はわからない。
[^about_f]: Fはおいしい。

rargsを使って、sedの命令を作ります。生成されるのは以下の感じ

$ cat Sh*/v*52/annotation.md | tail -n4 | rargs -p "\[\^(.+)\]: (.+)" echo "s/([^^])\[\^{1}\]/\1\\\
footnote{{2}}/"
s/([^^])\[\^about_a\]/\1\\footnote{Aの起源は室町時代に遡る。}/
s/([^^])\[\^about_c\]/\1\\footnote{Cの起源は江戸時代に遡る。}/
s/([^^])\[\^about_d\]/\1\\footnote{Dの起源はわからない。}/
s/([^^])\[\^about_f\]/\1\\footnote{Fはおいしい。}/

エスケープだらけで意味判らねえなおい。簡単に意味を書くと「行頭でない[^about_hoge]を\footnote{HOGE}に置換する」です。\1で後方参照しているのは「行頭でない」の[^^]が一文字拾ってしまうからです。否定後読みとかならいらないのかな

PowerShellでも今回はsedに食わせるスクリプトを生成することにします。

$ cat .\annotation.md -Encoding UTF8 | sls -Pattern '^\[\^(about_.)\]: (.
+)' | % {"s/([^^])\[\^" + $_.Matches.Groups[1].Value + "\]/\1\\\footnote{" + $_.Matches.Groups[2].Value + "}/"}
s/([^^])\[\^about_a\]/\1\\\footnote{Aの起源は室町時代に遡る。}/
s/([^^])\[\^about_c\]/\1\\\footnote{Cの起源は江戸時代に遡る。}/
s/([^^])\[\^about_d\]/\1\\\footnote{Dの起源はわからない。}/
s/([^^])\[\^about_f\]/\1\\\footnote{Fはおいしい。}/

slsではグルーピングもできます。MatchInfoGroupsでグループが参照できるので、それを使ってスクリプトを生成します。

Q6

TeXでは\label{hoge}に対して\ref{hoge}で参照が取れる。contents.texの中から\labelがあるのに\refがない。またはその逆なものを検索してくださいという問題。

$ cat Sh*/v*52/contents.tex | grep -oPe "\\(ref|label)\{[^\{]+\}" | tr -d '\}' | sort -u |awk '{a[$2]=a[$2]" "$1;}END{for(x in a){print x, a[x]}}' FS=\{ | column -t | awk 'NF!=3{print $1}' | grep -f- -n Sh*/v*52/contents.tex 
19:\subsection{ロボットの姿勢と座標系}\label{sub:pose}
116:    \varphi_{c,t} \sim \mathcal{N}\left(\varphi_{c,t}^*,(3\pi/180)^2\right) \label{eq:phidist}\\
157:\ref{sub:noise}項でモデル化した雑音から計算する。
170:    \V{e}_{c,t,t'} = \V{x}_t' - \V{x}_t - \V{\mu}_{c,t,t'}\label{eq:e}
220:また、式(\ref{eq:unko})から、
242:    \end{bmatrix} \label{eq:e2}
319:また、\ref{sub:noise}項の定義では、遠いところでランドマークを

途中のawkで、以下のようなデータを作りました。

$ cat Sh*/v*52/contents.tex | grep -oPe "\\\(ref|label)\{[^\}]+\}" | tr -d '\\}' | sort -u |awk '{a[$2]=a[$2]" "$1;}END{for(x in a){print x, a[x]}}' FS=\{ | column -t
eq:ddist               label  ref
sub:fullslam           label  ref
sub:pose               label
fig:observation_noise  label  ref
eq:e2                  label
eq:unko                ref
fig:coordinate         label  ref
sub:noise              ref
eq:phidist             label
eq:psidist             label  ref
fig:two_poses          label  ref
fig:observation        label  ref
eq:e                   label

labelがあるかrefがあるかの表です。あとはカラムの個数が足りない(=どちらかが足りない)ものについて検索をすれば終わりという感じ。

PowerShellではちょっとPowerShell感を使って解いてみます

$ cat .\contents.tex -Encoding UTF8 | sls '\\(ref|label)\{([^}]+)\}' | %{ $_.matches.groups[2].value + "::" + $_.matches.groups[1].value  } | sort -Unique | group { ($_ -split "::")[0]  } | ?{$_.count -eq 1} | %{ sls -Pattern $_.name -Path .\contents.tex -Encoding UTF8  } | sort -Unique

contents.tex:116:       \varphi_{c,t} \sim \mathcal{N}\left(\varphi_{c,t}^*,(3\pi/180)^2\right) \label{eq:phidist}
\\
contents.tex:117:       \psi_{c,t} \sim \mathcal{N}\left(\psi_{c,t}^*,(3\pi/180)^2\right) \label{eq:psidist}
contents.tex:157:\ref{sub:noise}項でモデル化した雑音から計算する。
contents.tex:170:       \V{e}_{c,t,t'} = \V{x}_t' - \V{x}_t - \V{\mu}_{c,t,t'}\label{eq:e}
contents.tex:19:\subsection{ロボットの姿勢と座標系}\label{sub:pose}
contents.tex:220:また、式(\ref{eq:unko})から、
contents.tex:242:       \end{bmatrix} \label{eq:e2}
contents.tex:275:$\theta$方向の共分散であり、式(\ref{eq:ddist}-\ref{eq:psidist})
contents.tex:319:また、\ref{sub:noise}項の定義では、遠いところでランドマークを

方針自体は一緒です。違うのは中間データとして、以下のようなものを作って

> cat .\contents.tex -Encoding UTF8 | sls '\\(ref|label)\{([^}]+)\}' | %{ $_.matches.groups[2].value + "::" + $_.matches.groups[1].value } | sort -Unique
eq:ddist::label
eq:ddist::ref
eq:e::label
eq:e2::label
eq:phidist::label
eq:psidist::label
eq:unko::ref
fig:coordinate::label
fig:coordinate::ref
fig:observation::label
fig:observation::ref
fig:observation_noise::label
fig:observation_noise::ref
fig:two_poses::label
fig:two_poses::ref
sub:fullslam::label
sub:fullslam::ref
sub:noise::ref
sub:pose::label

これの各行に対し::splitした一個目をキーにGroup-Objectしています。こういった集計はやりやすくていいなあと思います。

Q7

$ cat Sh*/v*52/contents.tex | awk '/\\begin\{bmatrix\}/,/\\end\{bmatrix\}/{printf $0"@@@"}/\\end\{bmatrix\}/{print ""}' | awk '{print gsub("&","&"), $0}' | awk '$1==6{$1="";print}' | sed -z 's/@@@/
/g'

ほぼ先生の解答通り。awk '/begin/, /end/'を最近AWK処方箋で読んだので、使えてよかったです

Q8

重複するcontents.texには存在するので、その位置を出力する問題。ぜんっぜんわからなかった。

終わりに

今回はQ1から時間ギリギリにしかできなくてやばかったですね。sedスクリプトを生成するっていうのはいかにもシェル芸的なので楽しいですね。でも、あんまり普段はやらない作業かもなぁと思いました。文章の修正とかをする人ならよくあるのかな?って感じです。

今回もとても楽しかったです。企画・開催ありがとうございました!!

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

参加しました

今回もリモート開催ということでした。そろそろここの前置きに書くこともなくなってきました。

さて毎回の通りですが、時間中に出したzsh/bashでの解答と、これを書きながら考えたPowerShellでの解答の両方を載せていこうと思います。PowerShell全然わからないので難しい。

問題と解答はここから見られます。リンク先はYouTubeのリストで、各問題ごとに分けられています。いつもの問題と解答ページは更新されたらここに追記しようと思います。

Q1

pepoファイルに隠れたうんこを探し出す問題。出うんこ

これはまぁなんというかターミナルのせいだけど、探す前から若干うんこが見えてました

bash/zsh

$ cat pepo | sed "s/$(echo -e 'ほ\U309a'/(/g" | sed "s/$(echo -e 'へ\U309a'/)/g"|sed 's/[ぽ|ぺ]/  /g'

()が他の「ぽ」や「ぺ」とは違い、「ほ」+「゜」になっていることがポイントですね。そのコードポイントは\U309aなので、echo -eで「ほ」や「へ」にくっつけてからsedに渡し、置換。先の画像が有ったのですぐ気づいたのですが、本当はxxdなんかを見た方がいいですね。

PowerShellの解答は以下のかんじで

PowerShell

$ (cat pepo -Encoding UTF8) -replace "ぽ","  " -replace "ぺ","  " -replace "ほ\u309a","(" -replace "へ\u309a",")"
                  人
              (      )
            (          )
          (              )
        (                  )
      (                      )
    (                          )
  (                              )

sedを重ねるよりも簡単に欠ける感じがして結構好きです。方針は先のものと同じですね。

Q2

暗号化されたC言語のソースを解読してプログラムを復元する問題。ヒントが無かったら解けなかったです。

bash/zsh

$ echo -e "$(cat q2 | sed 'y/1234567890bcdefg/0123456789abcdef/' | sed 's/../\\x&/g' )" | uconv -x FullWidth-HalfWidth
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char a[] = {0x75,0x6e,0x6b,0x6f,0x00};
    puts(a);
    exit(0);
}

シーザー暗号ですというヒントがあったので、それを頼りに。数値は一つシフト、アルファベットはa-fb-gになっているので、それぞれ置換。全角文字になっているので、uconvを使って半角へ戻します。nkf -Zでもいいんですが、全角スペースが半角にならないらしいです。なんでなんだろう。

ところでこれを実行すると

$ ... | gcc -x c - && ./a.out
unko

うんこになります。PowerShellでもやってみますが、置換の部分だけ。半角<=>全角はuconvでよくない?ということで・・・。まぁそんなこと言ったら全部さっきの解答でいいんですけどね。

PowerShell

$ $hash=@{b='a';c='b';d='c';e='d';f='e';g='a';'1'=0;'2'=1;'3'=2;'4'=3;'5'=4;'6'=5;'7'=6;'8'=7;'9'=8;'0'=9}; [regex]::Replace((cat .\q2), ($hash.Keys -join "|"), {$hash[$args.value]}) | xxd -ps -r

-replacesedy相当の者が有ればよかったんですがね~。

Q3

factorawkを使って127が素数なことを判定する問題。ただし数字を含めてはだめらしい。難読化だ。

bash/zsh

$ @ &>/dev/null; echo $? | factor | awk "NF==(($$/$$+$$/$$))"

PowerShell

$ echo (((([int]('a' -eq 'a')) -shl (([int]('' -eq '') -shl ([int]('b' -eq 'b')) -shl ([int]($null -eq $null)) -shl [int]$?))) -shr [int]$?) - [int]$?)

どちらも特に解説することは・・・ないですね。Trueな値をintへキャストすると、1になるということさえ分かっていれば、PowerShellの方はわかるかと思います。

Q4

アルファベットを使わずにq2ファイルの中身を出力する問題。zshだと<?2でできるんだけど。

bash/zsh

$ $0 ?q

bashに与えて、コマンドとして評価させます。エラーが出るけど、中身はチェックできるのでOKだよね。

Q5

messageファイルを解読する問題、なんだけど順を追って解読していくので、ここではサブ問題みたいな感じ。この後の問題とつながとのこと。ここでは暗号をデカい16進数として見て、素数で順番に割って行く問題となっています。

bash

$ seq 20 | factor | awk -v V=$(cat She*/vol.51/message) 'NF==2{printf("%s/%X\n",V,$2)}' | sed 1i'obase=16;ibase=16' | bc
1E507BF014FBCE45D6079643F0151B79E27113EF90AA1B15EA94D9D
1435A7F563528983E4050ED7F56367A696F60D4A60716763F1B8913
C2031933B97EC1BEF363C1B2CD53E30C0FA07F96D10D7A25DD523E
8A947FB73B5A8A63D26BD37FB73BEB51C204ED6BB9E50E1B0BCD08
582FF42EC8ADFAF9B473297FD1B1C44B4CEBDCE7764BF1B41EC84B
4A9EBAEC826BD45D2288D458765B57536878CE9C77CA1B49A3E48E
390FF85A81D9FCBFB0FF38F85A82159A22B6BC1D4C8B7E475F364E
330E4A00235740759E5D9EC34381DD6EF6A37FE451F612ABA6082B

16進数での式を組み立ててbcに計算させます。sed 1iがポイントですね。これヘッダーを付けたりするときによく使います。

PoweShell版はこちら

PowerShell

$ $V=([System.Numerics.BigInteger]::Parse((cat .\message), [System.Globalization.NumberStyles]::AllowHexSpecifier)); (1..100 | %{ openssl prime $_ } |sls "is prime" |%{ ($_ -split ' ')[1] }) -replace '[(|)]','' | %{ ($V/$_).ToString("X") }

まずはHexな文字列をBigIntegerとして読ませるために[System.Numerics.BigInteger]::Parseを使います。その後は openssl prime素数判定、切り出し、計算の順番に処理をしていきます。($V/$_).ToString("X")BigIntegerHexな文字列として出力させる部分ですね。

Q6

上のあとに続けてメッセージを探す問題。雑にxxdへ渡すだけなんだけど、検索が上手くできなくて、時間内に解けませんでした。にゃーん

bash/zsh

$ seq inf | factor | awk -v V=$(cat message) 'NF==2{printf("%s/%X\n",V,$2)}' | sed 1i'obase=16;ibase=16' | bc | xxd -ps -r | grep -m1 -aoP '[\p{Han}\p{Hiragana}!]{3,}'
目だ!目を狙え!

解説でもあったけど、grep-a\p{Han}とか\p{Hiragana}を良く忘れてしまう。好きなんだけどな~~。

PowerShellのほうは多分こんな感じ

PowerShell

$ ... |xxd -ps -r |sls "[\p{IsHiragana}\p{IsCJKUnifiedIdeographs}!]{3,}"

.NETでは\p{IsHan}がないっぽいのでこれで仕方なく代用してますが、CJKなので無限に引っかかる。いつか見つかると思います・・・

Q7

ssh localhostした後、記号だけでbashを立ち上げる問題。echo $SHLVLの出力が2になるような記号ワンライナー

bash/zsh

$ ${!##-}

${!#}は関節参照展開。${#}0なので、${!#}${0}と評価される。これでシェルが呼び出せそうなんだけど、$0-bashとか-zshになることがあるので、${var#-}で先頭のハイフンを削っているということ。解説通りなので、配信のアーカイブを見た方がいいかもです。

Q8

echo evilという文字列をランダムな文字列が書かれたファイルの中に隠す問題。隠し方は以下

  1. 元の文章を2進数にする
  2. 各ビットをランダム文字列の各文字の最下位ビットとして仕込む

bash/zsh

$ echo echo evil | xxd -b -c1 | sel 2 | grep -o . | while read L; do [[ "$L" == "1" ]] && shuf -n1 -e {A..Z..2} || shuf -n1 -e {B..Z..2}; done
ZMSXHSFYJCAFJJOYNCIZWJNLTKMHGAISJJYTFZVFLAMZREZYVKGWZQKDNKOBMXFWJKORKAJNPHZDCBOP

最初はAかBしか出力しないやつだったんだけど、ランダム化しました。ちなみに{A..Z..2}という書き方、zshでは出来ませんでした。

出来てほしいな~という感じですね。

さて復元もしてみます。

bash/zsh

$ echo -n ZMSXHSFYJCAFJJOYNCIZWJNLTKMHGAISJJYTFZVFLAMZREZYVKGWZQKDNKOBMXFWJKORKAJNPHZDCBOP | xxd -ps -u | sed 's@..@&%2\n@g' | sed 1i'obase=16;ibase=16;' | bc | tr -d \n | fold -b8 | sed 1i'obase=16;ibase=2'| awk 4 | bc | xxd -ps -r 
echo evil

基数変換が面倒すぎません・・・?なんかエイリアス貼るなどしたいですね。

PowerShell版もサクッと

PowerShell

$ ((echo "echo evil" | xxd -b | %{($_ -split ' ')[1..6]}) -split '' |? {$_} | %{if($_%2){"ACEGIKMOQSUWY"[((Get-Random)%13)]}else{"BDFHJLNPRTVXZ"[((Get-Random)%13)]}}) -join ""
HYIPJELUHUSXZFKKHGCTQFFJVIWTEMMMLFOHDTHJHWKHTWNMDAEGLKMRLWSPOBPQLWAFEAFNLLPHEKDUXPNNMTQN

$ $A=((((echo HYIPJELUHUSXZFKKHGCTQFFJVIWTEMMMLFOHDTHJHWKHTWNMDAEGLKMRLWSPOBPQLWAFEAFNLLPHEKDUXPNNMTQN | xxd -ps -u -c1 | %{ [int]::Parse($_, [System.Globalization.NumberStyles]::AllowHexSpecifier)%2}) -join "") -replace "(.{8})",'$1 ') -split " " | %{[System.Convert]::ToInt32($_, 2).ToString("X2")});$A[0..($A.Length-2)] | xxd -ps -r

サクッとと言いましたがすごくggったりしました。むずい

LT

今回もLTさせていただきました。

www2.slideshare.net

t.co

12月なのでワタナベエンコーディングについて再考し、再実装しました。とてもいいものが出来たと思います。聴いてくださった方ありがとうございました!!

おわり

今回は基数などの変換が多かったイメージです。難読化系の問題は得意なんですが、制限時間があると、どうしてもよくある解答以外私から出てこなくなってしまうのが痛いですね。難読火力を磨かなければですね。

とても楽しかったです!企画・開催ありがとうございました!!

雑記 2020-11-13

焼きそば

昼飯はよく焼きそばを作る。予め切っておいた野菜やら肉やらをエイと炒めるだけ。洗い物が少々面倒だが楽なので良い。 買い物に行くときは冷蔵庫を覗いて材料が残っているかをチェック、次の買い物までに足りなくなりそうなら買い足しておく。なんか得意げに書いているが当然のことだ。

ところが「足りなくなりそうなら」と言う部分のロジックがバグっていたのか、今はなんと麺が6袋ある。つまり6日分。近々冷蔵庫を空にしなければならない予定があるので、こいつをどうにか消費しきらなければならない。とりあえずイレギュラーだが、今日の晩ごはんに2袋使おうと思う。さて…僕は食いきれるだろうか…

man

[[ について調べるとき、あまりにもggりにくくてmanに駆け込むことになった。なんかmanってあんまり得意じゃなかったんだけど、いつの間にか普通に使えたので僕も進化しているんだなあと思った。

おわり

おわりです

雑記 2020-11-10

チョコ

あんまり間食はしない…というよりはコーヒーをゆっくりゆっくり啜っているので、他になにか食うタイミングがない。にも関わらずおまけ欲しさにコンビニでチョコをたくさん買ってしまった。
皆いつおやつタイムを取るんだろう。思い返せば僕がもっと小さかった頃はバリバリおやつを食っていた気がする。あれは何時ぐらいだったんだ?

ワイヤレスイヤホン

料理をするとき、動画を見ながらよくやる。換気扇とかフライパンとか他にも音のなるものが多いので、動画の音声をよく聞くためにコード付きのイヤホンをしている。ところがこれ、かなり邪魔。というか危ない。
それを解決したくて、少し前ワイヤレスイヤホンを買った。店頭で結構迷って選んだもの。最初に決めた予算は超えたが…まぁオタクの買い物ってだいたいこうなるしいいか、と思った。
ところがいざ試してみると、電子レンジで詰んだ。そりゃあそうだよなあと思った。そう、それはそうなんだよな。

おわり

はいはいおわりおわり

雑記 2020-11-09

ボタン押せないマン

毎日朝と夜に押さないといけないボタンがある。このボタンを押すようになってからしばらく経つが定期的に、それも結構な頻度で忘れる。 推し忘れたことによる不都合は他のところで補填できるので、まぁいいんだけど「またやってしまった」と毎回落ち込む。 何も対策していないわけじゃなくて、ボタンを押すことを今日のTODOにしてみたりとか、まぁなんかいろいろやった。でもダメだった。意識ではもうどうにもならないので、機械に任せるのがいいんだろうなあ

実は割と運動をしている。でもムキムキになるような運動はしていない。ので体型は普通。 ストレッチも毎日している。少しずつ柔らかくなっているけど、日中の体勢が悪いせいか腰痛とかは治らない。長座体前屈が良くできるようになる一方である。

おわり

久しぶりに書いたけど結構書くことあるなあ

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

参加しました

今回もリモート開催でした。毎度書きますけど、やっぱり問題の辛さに呻く声とかが聞こえなくて寂しいですね

YouTubeの配信はこちら

今回も時間中に解答したものと、これを書くと同時にPowerShellでの解答も書いていこうと思います。できるのか…俺に…?

Q1

un.txt.gzから、UNITY, UNKO, unity, unkoの数をそれぞれなるべく短時間で数えてください。 単語の途中に改行が入っているものも数えてください。

問題の意味は簡単に理解できるので、パッと思いつくような書き方で行くなら

$ zcat un.txt.gz | tr -d \\n | grep -io -e "UNKO" -e "UNITY" | sort | uniq -c | sort -rn

ところが、これでは時間がかかりすぎるということで、速度の改善が必要とのこと。最初はzgrepで改行を巻き込んだ検索をしようとしました

$ zgrep -oiP "(U\n?N\n?K\n?O\n?|U\n?N\n?I\n?T\n?Y\n?)" ./un.txt.gz | sort | uniq -c | sort -rn

これだとうまく引っかけられなかったので、長考。解答例では以下のように aだけの行を削ってから検索すればよいとのこと

zgrep -vP "^a+$" *a/*50/un.txt.gz | tr -d \\n | grep -ioP "un(ko|ity)" | sort | uniq -c | sort -rn

ところがこれは

aaaaaaaaaaaaaaaaun
aaaaaaaaaaaaaaaaaa
koaaaaaaaaaaaaaaaa

というデータにぶつかったときに正しくなくなる。(参考)

それを踏まえてもう一度考える。

$ time zcat ./un.txt.gz| sed -E 's/a+/a/g' | tr -d \\n | grep -ioP "UN(KO|ITY)" | sort | uniq -c | sort -rn 
      3 unko
      3 unity
      2 UNKO
      2 UNITY
zcat ./un.txt.gz  5.39s user 0.83s system 61% cpu 10.102 total
sed -E 's/a+/a/g'  7.67s user 2.43s system 99% cpu 10.102 total
tr -d \\n  0.03s user 0.24s system 2% cpu 10.102 total
grep -ioP "UN(KO|ITY)"  1.23s user 0.00s system 12% cpu 10.190 total
sort  0.00s user 0.00s system 0% cpu 10.189 total
uniq -c  0.00s user 0.00s system 0% cpu 10.189 total
sort -rn  0.00s user 0.00s system 0% cpu 10.188 total

連続するaを1文字のaにしてから改行削除、grepで絞り込みをしてみた。これだと10秒ぐらい。もっと速い解答もTwitterにあるけど、僕のはこれとしておきます。

続いてPowerShellです。

$ (7z e ./un.txt.gz -so |%{ $_ -replace "a+","a" }| Get-Unique) -join "" | Select-String -AllMatches "UN(KO|ITY)" | %{$_.Matches.Value} | Group-Object -CaseSensitive

Count Name                      Group
----- ----                      -----
    2 UNITY                     {UNITY, UNITY}
    3 unko                      {unko, unko, unko}
    2 UNKO                      {UNKO, UNKO}
    3 unity                     {unity, unity, unity}

やることは同じです。PowerShellではgrepの代わりにSelect-Stringをよく使いますが、grep-oに相当する機能を単独で持ちません。Select-StringMatchInfoというオブジェクトを出力するので、そこからマッチした部分(MatchInfo.Matches.Value)を取り出すことでgrep -o相当のことができます。あとはGroup-Objectで数え上げればOKですね。

Q2

un.txt.gzでaのみが書いてある行の行数をなるべく早くカウントしてください。

とのこと。

$ zgrep -cP "^a+$" *a/*50/un.txt.gz
$ rg -cz "^a+$" *a/*50/un.txt.gz

このどちらかがまあ割と高速。解説することがないですね。

PowerShellでの解答も書いていこうと思います。

$ 7z e ./un.txt.gz -so | group

これで出るんじゃないかな・・・(手元じゃ終わらなかったのでわからない)

Q3

catコマンドを遅くする問題

cat ./un.txt > /dev/null

こう書くと、一瞬で終わるのだけど、このコマンドの前に何かを仕込んで遅くするというもの

$ function cat() { \cat $1 | \cat }
$ cat a > /dev/null

単純に二段重ねにしたら遅くなるよねって感じですね。解答例としては、/proc/sys/vm/drop_cachesをいじってやるというもの。勉強になるなあ

$ echo 3 | sudo tee /proc/sys/vm/drop_caches
$ cat a > /dev/null

Q4

1から1億までの数字をシャッフルして2列にしたデータ(ファイル名はaにしましょう)を作ってください。速いワンライナーを考えてください。

とのこと。最初に自分が書いたのがこれ

$ seq 1000000 | paste -d' ' - - | teip -f1 shuf | teip -f2 shuf > a'

xargsでは永遠に終わらないだろうなと思っていたので、paste、シャッフルは後続のteipに任せることに。これ結構いいと思ったんですが…

マシンがやられてしまった。物理ボタンぶち押して再起動しました。悲しい。

再起動中TLを見ているとshuf-iオプションというのがあるのを知ってへぇと思いました

$ shuf -i 1-10
8
3
7
2
9
10
1
6
4
5

PowerShellでの解答も書いていこうと思います。

$ 1..100000000 | Sort-Object { Get-Random } | %{[PSCustomObject]@{k=$_}} | Format-Wide -Column 2

横に並べるって言うのが中々大変でした。Format-Wideでそのようなことができるのですが、一度PSCustomObjectにして置く必要があるんですねこれが。あとこれも全然終わりません。悲しい。どうしたらいいんだ

一応、Get-Content-ReadCountを使えばファイルの中身を分割できるので、少しずつなら出力できますね。ただし、区切られた区間でのみのシャッフルになりますが

$ 1..100000000 > nums
$ Get-Content ./nums -ReadCount 10000 | %{ $_ | Sort-Object{Get-Random} } | %{[PSCustomObject]@{k=$_}} | Format-Wide -Column 2

この問題の出力は次の問題で使うので、aというファイルに書き出しておきます。

Q5

cat a | sort -k2,2n > b を出力内容を変えずに高速化する問題。

これは知識問題。気づけるかな?という問題でした。catから始めていますが、sortに直接渡した方がCPU使用率に差がでるとのこと。そういえばcatからおもむろに始めることが多いので、知らないうちにワンライナーが遅くなっちゃってるかもですね。勉強になる

Q6

出題時マシン再起動中でした(前問を試していたら死んだ)。問題を見る限り、teipが使えそうと思っていたらTLにもあったのでさすがteipだなあと思いました

復活して書いたのがこれです。

$ awk '$1%2==$2%2==1' a | teip -og \\d+ -- factor | awk 'NF==4' > c

前段では、両方とも奇数なカラムだけを取り出して前処理、次に-og \\d+で数値だけなカラムに対してfactorを適用します。あとは awkで切り出しですね。

PowerShellでは、なんだろう…teipが使えないので、ForEach-Objectfactorするとかかな…?

Q7

aに対して二つの操作をする問題。

  • 行頭に A, B, C, ... , Z, A, ...を付ける
  • 行頭のA-ZでGroupByする

これ、最中は同時にやるもんだと思っていたので以下の解答をしました

$ awk '{print $1"_"$2}' a | paste -d' ' -{,,,,,,,,,,,,,,,,,,,,,,,,,} | rs -T| tr -d _ | paste <(echo {A..Z} | fmt -1) -'

26個なのは決まっているので、pasteで横に26個並べてから rs -Tで転地。あとは行頭に A-Zを付けただけですね。まぁこれは題意とは違うのでダメなのですが・・・

Q8

Q7について、さらに各行の数字を小さい順にソートするという条件をつけてください。ans2というファイルに保存します。

Q5の解きなおしをしていたらマシンが死んだので再起動していました。本当に悲しい。

LT

今回はLTしました。以前のエントリで書いたワンライナーC#ocsと、カラム選択を短縮できるselの紹介です。

ocsの方はまぁ良かったんですが、selの発表中に、すでにselfという同じ感じのあるよというナイフを突きつけられてめちゃめちゃ焦りました。

一応冷静にselfselの比較をしていたんですが、selには入力と出力のデリミタを指定できる、sed -iみたいな上書きができるなどの優位点があります。

速度的にはチューニング後のselHaskell版のselfで大体同じ。若干selfの方が速いです。ただしこれは、selの入力デリミタを固定文字(スペース)としたときなので、正規表現で分割するときは負けてしまいます。

あとはselgo getするだけ、selfPythonの特定バージョン以上が必要。もしくは、ghcなどでHaskell版をコンパイルする必要があるなどが違いですかね。

正直 sel 1 2 3 みたいな使い方をしたいだけならどちらでもよいので、好きな方を使おうって感じですね。今回のことでパフォーマンスチューニングに興味も沸いたのでselを爆速にしてやりたいと思っております。

まとめ

今回もとても楽しかったです!あととっても疲れました!!Q8のあたりとかマシンもぼくも疲労困憊でした!次回も楽しみです!ありがとうございました!

ワンライナー中にC#を書きたかったのでocsというコマンドを作った

作りました

github.com

経緯

自分は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を作りました。アイデアrbopyと同じです。

インストール

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

BEGINENDも使えます

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を使いました

github.com

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が動かなくなるのであきらめました。悲しいのだ

まぁ良かったら使ってやってください