きっかけはコレ。
最初はスルーしていたのですが、ふと良さげな実装を思いついたのでTwitterでこんなつぶやきをしてみました。
意外と愚直な手順でいけたのでやっぱり参戦w
— stknohg.ps1 (@stknohg) 2016年10月30日
"響け!ユーフォニアム" | % {$c,$l=[Char[]]$_,$_.Length;for($i=0;$i-lt$l;$i++){-join $c[($i-$l)..($i-1)]}} #シェル芸 #powershell pic.twitter.com/09uNhSJRSe
で、コレが第25回シェル芸勉強会のお題にもなっているとの事だったので、じゃあこっちもやれるだけやってみようと思いやってみました。
ちなみに勉強会の状況は完全に後追いです。
お題もちょっと考えて行き詰ったらすぐ解答を見ています。
問題を解くことよりPowerShellでどう実現するかに重点を置いています。
お題と解答
お題と解答は以下にあります。
【問題のみ】第25回もう4年もやってんのかシェル芸勉強会 – 上田ブログ
【問題と解答】第25回もう4年もやってんのかシェル芸勉強会 – 上田ブログ
PowerShellでの実装
Q1から順にやっていきます。
実行環境はWindows 10、PowerShell 5.1になります。
Q1. www.usptomo.comのIPアドレス
これは[Net.Dns]::GetHostAddresses
メソッドを使えば一瞬です。
[Net.Dns]::GetHostAddresses("www.usptomo.com").IPAddressToString # もしくは以下の様にしても良い "www.usptomo.com" | % { [Net.Dns]::GetHostAddresses($_).IPAddressToString }
実行結果)
シェル芸というには邪道かもしれませんが、PowerShellとしては王道です。
【ちょっと追記】
コマンドレットの使用にこだわるのであれば以下の様にしてもOKです。
この場合も内部的には[Net.Dns]::GetHostAddresses
メソッドが使われています。
(Test-Connection "www.usptomo.com" -Count 1).IPV4Address.ToString()
Q2. ひらけ!ポンキッキ
こちらはTwitterで書いた通りです。
"ひらけ!ポンキッキ" | % {$c,$l=[Char[]]$_,$_.Length;for($i=0;$i-lt$l;$i++){-join $c[($i-$l)..($i-1)]}}
実行結果)
ちょっとだけ補足すると、このコードはPowerShellの配列インデックスが負値を取れることを利用しています。
ForEach-Object(%)
内部のループを展開すると以下の様なイメージで処理が行われています。
# 処理イメージ $c = [Char[]]"ひらけ!ポンキッキ" -join $c[-9..-1] # ひらけ!ポンキッキ -join $c[-8..0] # らけ!ポンキッキひ -join $c[-7..1] # け!ポンキッキひら # ・・・(中略) ・・・ -join $c[-1..7] # キひらけ!ポンキッ
Q3. rbash
PowerShellではrbashに相当する機能制限はありません。
この問題はパスで。
Q4. すけふぇにんけん
こちらはPowerShell2.0以降である必要がありますが、以下の様にして可能です。
echo すけふぇにんけん ` | % { Add-Type -AssemblyName "Microsoft.VisualBasic" } ` { $w = -join ([Char[]]$_ | % { $_ + "゛" }); [Microsoft.VisualBasic.Strings]::StrConv($w,[Microsoft.VisualBasic.VbStrConv]::Wide) -replace "゛","" }
実行結果)
解答ではnkf
コマンドを使っていますが、Windowsには無いので近い機能としてVB.NETのStrConv
関数で代用しています。
PowerShellからVB.NETの機能を使う詳細は、
を参考にしてください。
ネタで書いたエントリだったのですが意外なところで役に立ちました。
Q5. 1秒に一つ*が伸びていくアニメーション
コンソールに文字を出すだけという条件であればこんな感じで。
while(!(Start-Sleep 1)){Write-Host "*" -NoNewline}
PowerShellには進捗状況を表示するためのWrite-Progress
コマンドレットがありますので、これを使っても良いと思います。
while(!(Start-Sleep 1)){$m+="*";Write-Progress $m}
実行結果)
Q6. 文字列を復元
こちらはエンコーディングさえわかってしまえばあっさり行くと思います。
# "b730a730eb30b8820a00" cat ".\crypt" ` | % { $n = 2; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } } ` | % { $b = @() } ` { $b += [Convert]::ToByte($_, 16) } ` { [Text.Encoding]::Unicode.GetString($b) }
実行結果)
処理の細かいところは
を参考にしてください。
Q7. UNIX時刻で素数
これはPowerShellだとワンライナーで書くのは不可能です...
スクリプトブロックになってしまいますが、こんな感じでご容赦ください。
# 検算できていないので間違いがあるかも... &{ # 素数判定 function IsPrime([int]$n){ if($n -eq 1){return $false} if($n -eq 2){return $true} $b = [Math]::Floor([Math]::Sqrt($n)); for($i = 2; $i -lt $b; $i++){ if ($n % $i -eq 0){return $false} } return $true } # 621355968000000000 = ([DateTime]::ParseExact("1970/01/01 00:00:00", "yyyy/MM/dd HH:mm:ss", $null)).Ticks $s = [Math]::Floor(([Datetime]::Parse("2016.10.29").Ticks - 621355968000000000 ) / 10000000); $s..($s+86399) | ? { IsPrime $_ } | % {(New-Object "Datetime" (($_*10000000 )+621355968000000000 ))} }
※実行結果は省略
まず、解答ではfactor
コマンドを使って素数の判定をしていますが、Windowsにはもちろん存在せず代替できそうなコマンドやライブラリもありません。
仕方ないので私でも書けるエラトステネスの篩で簡単な関数IsPrime
を書いてやります。
そしてUnixTimeは.NET FrameworkのTicksから頑張って変換する必要があります。
UnixTimeは1970/01/01 00:00:00から1秒単位のTicks、.NET FrameworkのTicksは0001/01/01 00:00:00からナノ秒単位なので頑張れば普通に変換できます。
余談
余談ですが、Get-Date
コマンドレットにUnix形式の表示書式を指定できる-UFormat
があり、このパラメーターに+%s
を指定してやるとUnixTime風の結果を返すことが出来ます。
UnixTime風と書いたのは、Githubの以下のIssueにある様に、
- 精度がナノ秒で小数表示
- UTCではなくローカル時刻の1970/01/01 00:00:00を基準としたTicks
となっているためです。
Issue中に記載されていますが、正しいUnixTimeとするには以下の様に書く必要があります。
[Math]::Floor((Get-Date ([DateTime]::UtcNow) -UFormat +%s))
8. サイン波
流石にお題と完全に同じ答えを出すことができませんでした。
概ね一致する結果であれば以下の様にすれば可能です。
0..19 ` | % {$l=20;$a=[Array]::CreateInstance([int],$l,$l);for($y=0;$y-lt$l;$y++){$a[([Math]::Ceiling([Math]::Sin(($y+1)/3)*10)+9),$y]=1}} ` {$x=$_;-join (0..19 | % {if($a[$x,$_]){"* "}else{" "}})}
実行結果)
解答ではrs
コマンドを使って軸の変換をしていますが、上のコードでは二次元配列の変換で代用しています。
PowerShell標準では二次元配列を生成できないため[Array]::CreateInstance
を使って生成しています。
その他の部分については気合で何とかしています。
ワンライナーにする前のコードは以下の様な感じで、こちらの方が分かりやすいかと思います...
# [int]型の二次元配列を生成 $l=20 $a=[Array]::CreateInstance([int],$l,$l) # *を描画する要素を1としている for($y=0;$y-lt$l;$y++){ $a[([Math]::Ceiling([Math]::Sin(($y+1)/3)*10)+9),$y]=1 } # 縦横変換 0..19 | % { $x=$_;-join (0..19 | % { if($a[$x,$_]){"* "}else{" "}}) }
補足
補足ですが、私の環境(Bash on Ubuntu on Windows)では
# rs -t 23 でなく rs -t 22 でないと波形がずれる seq 1 20 | awk '{a=sin($1/3) * 10 + 10;for(i=0;i<a;i++)printf "@ ";printf "* ";for(i=a;i<20;i++)printf "@ ";print ""}' | rs -t 22 | tr @ ' '
としないと綺麗な波形になりませんでした。
最後に
正直疲れました...
PowerShellでも頑張ればシェル芸をこなすことはできますが、やっぱり用途が違うなぁという印象です。