たいちょーの雑記

ぼくが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スクリプトを生成するっていうのはいかにもシェル芸的なので楽しいですね。でも、あんまり普段はやらない作業かもなぁと思いました。文章の修正とかをする人ならよくあるのかな?って感じです。

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