第43回シェル芸勉強会大阪サテライトに参加しました
参加しました
暑かった
今回は大暴れ!ということで大暴れ!なシェル芸勉強会になりました(?)
問題と解答はここです
問1
echo あいうえお
からはじめて あいいうううええええおおおおお
という出力を得る問題。これはできそう
$ echo あいうえお|grep -o .|paste <(req 5) -|-wk '{fod(i=0;i<$1;i++) pri\| $2}'| あいいうううええええおおおおお
あいうえおを行番号とともに縦に並べて awk
で処理するだけですね。行番号は awk
の NR
でいいな…と後で気づきました
$ $("$(echo あいうえお)".ToCharArray()|% -begin{$NR=0} {"$_"*++$NR}) -join ''
こっちはPowerShellです。厳密には echo あいうえお
から始まってないのでレギュレーション違反かもですが…。順番に解説します
# あいうえおをCharの配列にする $ "$(echo あいうえお)".ToCharArray() あ い う え お # % => ForEach-Object でそれぞれを加工する # -begin {} は awk の BEGIN{} と同じ # PowerShellではstring型に対して掛け算をすると繰り返しになる $ "$(echo あいうえお)".ToCharArray()|% -begin{$NR=0} {"$_"*++$NR} あ いい ううう ええええ おおおおお # 全体をjoinして終わり $ $("$(echo あいうえお)".ToCharArray()|% -begin{$NR=0} {"$_"*++$NR}) -join '' あいいうううええええおおおおお
最後の -join
をパイプ越しにできたらいいんですが・・・
問2
11円のうまい棒、21円のチロルチョコ、54円のよっちゃんイカを223円分買うとき、それぞれ何個買うことができるかを求める問題。ただしそれぞれ最低1つは買っていること
さんすう
$ echo {1..10}_{1..10}_{1..10}|fmt -1|awk -F_ '$1*11+$2*21+54*$3==223{print "うまい棒:",$1,"チロルチョコ:", $2, "よっちゃんイカ:",$3}' うまい棒: 2 チロルチョコ: 7 よっちゃんイカ: 1
厳密には範囲が足りてないんですがこれでも正解が出たのでこれで。こういうのは計算機で全探索したほうが楽ですね!もちろん脳みそでできることも大切だとは思います
$ 1..22 |% {$x=$_; $(1..12|%{echo $x-$_})}|%{$x=$_; $(1..10|%{echo $x-$_})}|? {$x=$($_ -split '-'); [int]$x[0]*11+[int]$x[1]*21+[int]$x[2]*54 -e q 223} 2-7-1
めちゃめちゃになってしまった…。 bash
みたいなブレース展開で組み合わせを作れないのがつらい。1..22 |% {$x=$_; $(1..12|%{echo $x-$_})}|%{$x=$_; $(1..10|%{echo $x-$_})}
までで 1-1-1
のような組み合わせを 22-12-10
まで作っています
# 組み合わせ生成 $ $ 1..22 |% {$x=$_; $(1..12|%{echo $x-$_})}|%{$x=$_; $(1..10|%{echo $x-$_})} 1-1-1 1-1-2 ... 22-12-10 # ? => Where-Object へのエイリアス。評価式がTrueになるものだけ通過させる # $xに - でsplitした配列をぶち込む # intへキャストして計算。223と等しいものだけ通過させる $ 1..22 |% {$x=$_; $(1..12|%{echo $x-$_})}|%{$x=$_; $(1..10|%{echo $x-$_})}|? {$x=$($_ -split '-'); [int]$x[0]*11+[int]$x[1]*21+[int]$x[2]*54 -e q 223} 2-7-1
型を指定してsplit出来たりしたらいいんですがね~。思いつくのは $x=$($_ -split '-' | %{[int]$_})
といった感じでしょうか
問3
IPアドレスを2進数32桁に変換する問題。おもしろそう
$ echo 192.168.10.55 | csharp -e "string.Join(\".\", Console.ReadLine().Split('.').Select(int.Parse).Select(x=>Convert.ToString(x, 2).PadLeft(8, '0')))" "11000000.10101000.00001010.00110111"
mono-csharp-shell
がシェル芸botにも入ったので、使ってみました。えっ!?面倒だったからだろって?違いますよぉやだなぁ
ほぼC#の解説になっちゃいますが一応
string.Join(".", // 後ろのCollectionを.でJoin Console.ReadLine() // 一行読み込み string .Split('.') // .でSplit .Select(int.Parse) // int型へ .Select( x=>Convert.ToString(x, 2) // 基数を指定してstringへ .PadLeft(8, '0') // 8桁で0埋め ) )
IPAddress
という class
に Parse()
、それから IPAddress
には GetAddressBytes()
があるので、もう少し短く書けるかもしれません。しかし…シェル芸とは?となってしまいますね
$ $("192.168.10.55" -split '\.'|% {[Convert]::ToString([int]$_, 2).PadLeft(8,'0')}) -join '.' 11000000.10101000.00001010.00110111
割と簡単でした。解説します
# "192.168.10.55" を . でsplit $ "192.168.10.55" -split '\.' 192 168 10 55 # [Convert]::ToString()で基数を指定して文字列にする $ "192.168.10.55" -split '\.'|% {[Convert]::ToString([int]$_, 2) 11000000 10101000 1010 110111 # 幅があってないので PadLeftで0埋め $ "192.168.10.55" -split '\.'|% {[Convert]::ToString([int]$_, 2).PadLeft(8,'0')} 11000000 10101000 00001010 00110111 # .でjoinする $ $("192.168.10.55" -split '\.'|% {[Convert]::ToString([int]$_, 2).PadLeft(8,'0')}) -join '.' 11000000.10101000.00001010.00110111
復元もやってみます。
$ echo 11000000.10101000.00001010.00110111|csharp -e "string.Join(\".\",Console.ReadLine().Split('.').Select(x=>Convert.ToInt32(x,2)))" 192.168.10.55
$("11000000.10101000.00001010.00110111" -split '\.'|% {[Convert]::ToInt32($_, 2)}) -join '.' 192.168.10.55
Convert.ToInt32()
で基数を指定して int
にできます
問4
双子素数を出力して下さい
$ seq 100 | factor | awk 'BEGIN{s=2}NF==2{print s,$2; s=$2}'|awk '$2-$1==2' 3 5 5 7 11 13 17 19 29 31 41 43 59 61 71 73
factor
で素数をえらび、前の素数とのペアになるようなリストを作ります。最後の awk
で差分が2のものだけ取り出します
$ seq 100 | factor | awk 'BEGIN{s=2}NF==2{print s,$2; s=$2}' 2 2 2 3 3 5 5 7 7 11 ...
$ 1..100|factor|? {$_.split(' ').Length -eq 2}|%{$_.split(' ')[1]}|% -begin{$s=2} {"$s $_";$s=$_}|? {$x=$($_ -split ' '|%{[int]$_}); $x[1]-$x[0] -eq 2} 3 5 5 7 11 13 17 19 29 31 41 43 59 61 71 73
できるだけpowershellの機能だけでやりたいな~と思うんですが、 factor
だけは使おうかな~という感じです。 factor
はbusyboxでインストールできます
問5
1から9の整数の組み合わせで、足して10になり、互いに数字が異なる組み合わせを全部列挙する問題。0-1ナップザックっぽい
$ seq 4321 |grep -v 0 | grep -Ev '(.).*\1'|awk -F "" '$1+$2+$3+$4==10' 19 28 37 46 64 73 82 91 127 136 145
同行に同じ文字列内に同じ文字が含まれているかどうかの正規表現が思いつきませんでした・・・正規表現むずい・・・
$ 1..4321|? {-not "$_".Contains("0")}|?{$($_ -split ''|measure -sum).Sum -eq 10}|? {-not ($_ -match '(.).*\1')} 19 28 37 46 64 73 82 91 127 136 145
measure
は 要素の個数、合計、平均なんかを計算してくれます。
$ 1..30|measure -sum -ave -max -min Count : 30 Average : 15.5 Sum : 465 Maximum : 30 Minimum : 1
このうちの Sum
にアクセスしたい場合は (コマンド).Sum
とします。今回は、桁和が10になるものだけを ?
でフィルターし、そこからさっきと同じ正規表現でフィルターしました。
問6
nums
ファイルから、問5で求めたパターンを探し、それが何行目にあるかを出力する問題。むずSO
$ seq 4321 |grep -v 0 | grep -Ev '(.).*\1'|awk -F "" '$1+$2+$3+$4==10'|while read L; do grep $L nums -n 2>/dev/null | sed "s/:/行目 ${L}がありました /g"; done 4行目 145がありました 124214535 1行目 235がありました 124123541 5行目 325がありました 433251244 3行目 352がありました 352381324 3行目 523がありました 352381324 1行目 541がありました 124123541 3行目 1324がありました 352381324 2行目 3214がありました 321412412 1行目 4123がありました 124123541
while read L; do ~ done
の力業です。各パターンについて、毎回 grep
し、マッチすればその行を出力しています。出力を丁寧にしないのであれば、 xargs
で grep
になげるだけでいいと思います
$ 1..4321|? {-not "$_".Contains("0")}|?{$($_ -split ''|measure -sum).Sum -eq 10}|? {-not ($_ -match '(.).*\1')}|%{sls $_ ./nums}|%{"$($_.LineNum ber): $($_.Matches.Value)"} 4: 145 1: 235 5: 325 3: 352 3: 523 1: 541 3: 1324 2: 3214 1: 4123
sls
は Select-String
へのエイリアスです。 使い方は grep
と大体同じ感じですが、マッチした行を返すのではなく、 MatchInfo
オブジェクトを返します
$ $("ABCDEFG"|sls A).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False MatchInfo System.Object
MatchInfo
オブジェクトは以下のようなメソッドやプロパティを持ちます
$ $("ABCDEFG"|sls A).
Context Line Path Equals GetTypeCode ToByte ToDecimal ToInt32 ToSingle ToUInt16
Filename LineNumber Pattern GetHashCode RelativePath ToChar ToDouble ToInt64 ToString ToUInt32
IgnoreCase Matches CompareTo GetType ToBoolean ToDateTime ToInt16 ToSByte ToType ToUInt64
ここからマッチした行 LineNumber
とマッチした値 Value
を取り出して、整形して終わりです
問7
100x100の市松模様画像を作る問題。
yes $(python3 -c "print('01'*50); print('10'*50)")|head -n50|tr ' ' \\n|sed '1iP1 100 100'|convert - ./a.bmp
yes
で 01010101....1 1010101...0
という無限の出力を作り、必要な数だけ head
で取り出します。必要な情報を1行目に sed
で挿入して convert
に放り込むだけですね。やったぁ
$ 0..99 | %{$x=$_; $(0..99|%{"$x $_"})}|% -begin{$bmp=$(New-Object System.Drawing.Bitmap(100,100))} {$z=$($_ -split ' '; ); $bmp.SetPixel($z[1], $z[0], ([System.Drawing.Color]::White, [System.Drawing.Color]::Black)[($z[0]%2+$z[1]%2)%2])} -end {$bmp.Save("./out.bmp")}
こっちは Bitmap
オブジェクトにピクセルをどんどん割り当てているだけですね
問8
上で作った画像に文字を仕込み、別の画像を作り、さらにそこから文字を取り出す問題
$ xxd -ps a.bmp|tr -d \\n|sed -E 's/.{8}$/64617465/'|xxd -ps -r > b.bmp
$ xxd ./b.bmp | tail 00007520: ff00 0000 ffff ff00 0000 ffff ff00 0000 ................ 00007530: ffff ff00 0000 ffff ff00 0000 ffff ff00 ................ 00007540: 0000 ffff ff00 0000 ffff ff00 0000 ffff ................ 00007550: ff00 0000 ffff ff00 0000 ffff ff00 0000 ................ 00007560: ffff ff00 0000 ffff ff00 0000 ffff ff00 ................ 00007570: 0000 ffff ff00 0000 ffff ff00 0000 ffff ................ 00007580: ff00 0000 ffff ff00 0000 ffff ff00 0000 ................ 00007590: ffff ff00 0000 ffff ff00 0000 ffff ff00 ................ 000075a0: 0000 ffff ff00 0000 ffff ff00 0000 ffff ................ 000075b0: ff00 0000 ffff 6461 7465 ......date
date
を仕込みました
問9
さらにこの画像をpngに変換し、復元、文字を取り出す問題
convert
の無圧縮オプションが必要らしい
LT大会
今回もLTさせていただきました。聴いてくださった方ありがとうございました!!
記号プログラミングが得意なPowerShellへ入門してみました。今回もこの記事を書くのと同時にPowerShellで問題を解いてみましたが、便利なメソッドやらコマンドレットがあって意外と楽だなぁと思いました。
それと他の方のLTも聴いてましたがめちゃヤバいですね・・・どうなってるんだ・・・
おわり
今回もとてもつかれました。参加された皆様、企画、運営の皆様ありがとうございました!
owari kan -a xztaityozx -g | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄| | 終 | | 制作・著作 | |  ̄ ̄ ̄ ̄ ̄ ̄ ̄ | | xztaityozx | |_________| ∧∧ || ( ゚д゚)|| / づΦ