しばたテックブログ

気分で書いている技術ブログです。

シェル芸勉強会の問題にPowerShellでチャレンジしてみた

きっかけはコレ。

togetter.com

最初はスルーしていたのですが、ふと良さげな実装を思いついたのでTwitterでこんなつぶやきをしてみました。

で、コレが第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 }

実行結果)

f:id:stknohg:20161031095518p:plain

シェル芸というには邪道かもしれませんが、PowerShellとしては王道です。


【ちょっと追記】

コマンドレットの使用にこだわるのであれば以下の様にしてもOKです。
この場合も内部的には[Net.Dns]::GetHostAddressesメソッドが使われています。

(Test-Connection "www.usptomo.com" -Count 1).IPV4Address.ToString()

f:id:stknohg:20161031181552p:plain

Q2. ひらけ!ポンキッキ

こちらはTwitterで書いた通りです。

"ひらけ!ポンキッキ" | % {$c,$l=[Char[]]$_,$_.Length;for($i=0;$i-lt$l;$i++){-join $c[($i-$l)..($i-1)]}}

実行結果)

f:id:stknohg:20161031095823p:plain

ちょっとだけ補足すると、このコードは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 "゛","" }

実行結果)

f:id:stknohg:20161031100709p:plain

解答ではnkfコマンドを使っていますが、Windowsには無いので近い機能としてVB.NETのStrConv関数で代用しています。
PowerShellからVB.NETの機能を使う詳細は、

stknohg.hatenablog.jp

を参考にしてください。
ネタで書いたエントリだったのですが意外なところで役に立ちました。

Q5. 1秒に一つ*が伸びていくアニメーション

コンソールに文字を出すだけという条件であればこんな感じで。

while(!(Start-Sleep 1)){Write-Host "*" -NoNewline}

PowerShellには進捗状況を表示するためのWrite-Progressコマンドレットがありますので、これを使っても良いと思います。

while(!(Start-Sleep 1)){$m+="*";Write-Progress $m}

実行結果)

f:id:stknohg:20161031103701p:plain

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) }

実行結果)

f:id:stknohg:20161031101637p:plain

処理の細かいところは

stknohg.hatenablog.jp

を参考にしてください。

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にある様に、

github.com

  • 精度がナノ秒で小数表示
  • 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{"  "}})}

実行結果)

f:id:stknohg:20161031103435p:plain

解答では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 @ ' '

としないと綺麗な波形になりませんでした。

f:id:stknohg:20161031113859p:plain

最後に

正直疲れました...

PowerShellでも頑張ればシェル芸をこなすことはできますが、やっぱり用途が違うなぁという印象です。

PowerShellでDockerを操作する方法についてあれやこれや

うまくまとめることが出来ず雑多な話になっています。

基本的にはWindows Server 2016およびWindows 10を対象としていますが、その他のOSも対象になる場合があります。

1. 変更されたDockerのインストール手順について

基本的にWindows Server 2016を対象とした話しです。
ただ今後Windows 10も対象になるかもしれません。

Windows Server 2016向けのDockerのインストール手順はMSDNの以下のページに記載されています。

msdn.microsoft.com

以前はInvoke-WebRequestコマンドレットを使ってdocker.exeおよびdockerd.exeをダウンロードしサービス登録を行うといった手順だったのですが、つい先日その手順が更新され、コンテナーの機能も含めてInstall-Packegeから行う様に変更されていました。

リンク先にありますが、手順はざっとこんな感じになります。

# Providerのインストール
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
# コンテナーの機能の追加+Dockerのインストール
Install-Package -Name docker -ProviderName DockerMsftProvider
# 再起動
Restart-Computer

最初にDockerMsftProviderという新しいProviderをインストールし、このProviderからパッケージとしてDockerをインストールします。

Install-Packegeをした時点で、コンテナーの機能を追加し、C:\Program Files\Dockerdocker.exedockerd.exeがインストールされ、dockerd.exedockerサービスとして登録されます。

コンテナーの機能を有効にするにはサーバーを再起動があるため、再起動後にDockerを使える様になります。

あと、DockerMsftProviderのIssueに

github.com

が挙げられており、現在Windows 10ではこの方法は使えません。
(Windows10ではInstall-WindowsFeatureは使えずEnable-WindowsOptionalFeatureを使う必要があるため)

このIssueがどう対応されるかまだわかりませんが、おそらくは将来的にWindows 10サポートもあるのかなと考えています。

なお、現時点ではこのProviderにあるパッケージはDockerのみです。
いずれ周辺ツールのインストールも可能になるかもしれませんが、まだなんとも言えない感じです。

# 現在 Find-Package しても Docker 以外のものは存在しない
PS C:\> Find-Package –ProviderName DockerMsftProvider

Name                           Version          Source           Summary
----                           -------          ------           -------
Docker                         1.12.2-cs2-ws... DockerDefault    Contains the CS Docker Engine for use with Windows ...

2. 利用可能なContainer Cmdletについて

これはWindows Server 2016およびWindows 10を対象とした話しです。

コンテナーの機能を追加するとWindows Serverコンテナー/Hyper-Vコンテナーを操作するためのContainer Cmdletsを利用することができます。

Get-Commandを使って公開されているコマンドレットを見ると以下の様になります。

PS C:\> Get-Command -Module Containers

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-ContainerNetwork                               1.0.0.0    Containers
Cmdlet          New-ContainerNetwork                               1.0.0.0    Containers
Cmdlet          Remove-ContainerNetwork                            1.0.0.0    Containers

2016/10/20現在のMSDN(Containers Cmdlets)を見ると21個のコマンドレットが定義されているにも関わらず、実際にはコンテナー用仮想ネットワークに関するGet-ContainerNetworkNew-ContainerNetworkRemove-ContainerNetworkの3つだけしかありません。

これについては、PowerShell for Docker | Microsoft Docs に理由が記載されています。
いつドキュメントが更新されるかわからないので、要所だけ引用しておくと、

フォーラム、Twitter、GitHub を介したお客様との対話、さらには直接の対話を通して、"どうして PowerShell から Docker コンテナーを表示できないのか" という問いをよく耳にします。
 
長所、短所、およびさまざまなオプションについて、お客様と話し合った結果、コンテナー PowerShell モジュールの更新が必要であるという結論に達しました。 そのため、Windows Server 2016 のプレビュー ビルドに同梱されているコンテナー PowerShell モジュールを非推奨とし、Docker 用の新しい PowerShell モジュールへの置き換え作業を開始しました。

とある様に、当初(私の記憶ではWindows Server 2016 TP3~TP5くらいの間)はDockerに依存しないContainer Cmdlets(上記のMSDNに記載されているもの)を開発していたのですが、これを中止(中断?)しDocker専用のコマンドレットの開発にシフトしたため利用可能なコマンドレットが減っています。

今後Container Cmdletsのコマンドレットが増えるのかは不明です。

3. PowerShell Module for Docker

で、前項で触れたDocker専用のコマンドレットがこちらになります。

github.com

概要

こちらは雑に言ってしまうとdockerコマンドのPowerShell Cmdlet版です。
まだアルファ版のためすべての機能を網羅でききていませんし、バギーな部分もありますが、1コマンドレットが1dockerコマンドに対応する様です。

そして、このコマンドレットは内部でDocker.DotNetという.NETからDockerを操作するためのライブラリを使用しています。
Docker.DotNetはLinux用のDocker Engine、Windows用のDocker Engine両方に対応している*1ので、コマンドレットもどちらのDocker Engineに対して利用可能です。

余談ですが、Docker.DotNetは内部でDocker Remote APIを呼んでいるだけなのでdockerの実行バイナリが必要といった依存関係はありません。

対象OS

このコマンドレットを利用可能なOSは、

PowerShell 5 (available in Windows 10, Server 2016 Preview, or by installing WMF 5) or PowerShell 6 Preview (available for Windows, Linux, and Mac OS X)

との事で、PowerShell 5.0以降であればどの環境でも動く様です。
私は全機能ではありませんが、Windows 10とWindows Server 2016でコマンドレットが使えることを確認しています。

インストール手順

まだアルファ版のためAppVeyor上のリポジトリからモジュールを取得する必要があります。
手順は以下。

# AppVeyor上のリポジトリからインストール
Register-PSRepository -Name DockerPS-Dev -SourceLocation https://ci.appveyor.com/nuget/docker-powershell-dev
Install-Module Docker -Repository DockerPS-Dev -Scope CurrentUser
# 必要があればUpdate-Moduleする
# Update-Module Docker

まだなんとも言えませんが、もしかしたら将来、先述のDockerMsftProviderからインストール可能になるのかもしれませんね...

コマンドレットの一覧

全てのコマンドレットの説明をするのはつらい(というか無理)なので、コマンドレット・エイリアスの一覧と対応するdockerコマンドのリストを記載しておきます。

本エントリ時点でのバージョンはVer.0.1.0.100です。

PowerShell Cmdlet Alias (おおむね)対応するdockerコマンド
ConvertTo-ContainerImage Commit-Container docker commit
Copy-ContainerFile - docker cp
Enter-ContainerSession Attach-Container docker attach
Export-ContainerImage Save-ContainerImage docker save
Get-Container - docker ps -a
Get-ContainerDetail - docker inspect
Get-ContainerImage - docker images
Get-ContainerNet - docker network ls
Get-ContainerNetDetail - docker network inspect
Import-ContainerImage Load-ContainerImage docker load/docker import (tarballの入力のみ可)
Invoke-ContainerImage Run-ContainerImage docker run
New-Container - docker create
New-ContainerImage Build-ContainerImage docker build
New-ContainerNet - docker network create
Remove-Container - docker rm
Remove-ContainerImage - docker rmi
Remove-ContainerNet - docker netowrk rm
Request-ContainerImage Pull-ContainerImage docker pull
Set-ContainerImageTag Tag-ContainerImage docker tag
Start-Container - docker start
Start-ContainerProcess Exec-Container docker exec
Stop-Container - docker stop
Submit-ContainerImage Push-ContainerImage docker push
Wait-Container - docker wait

実際にこのコマンドレットを利用してみればわかりますが、対応するdockerコマンドについては100%の互換を持つわけではなく、微妙な違いがありますので注意してください。
コマンドレットで用を成せない場合はdockerコマンドを使うしかありません。

dockerコマンドとの使い分けについて

率直に言ってしまうと現時点ではPowerShell Module for Dockerはdockerコマンドの劣化版ですし、将来的にもdockerコマンドと完璧に対になるかといえばそうはならないと思います。
なのでPowerShell Module for Dockerとdockerコマンドは用途に応じて使い分けてやる必要があると思います。

あくまで私個人の想いですが、コンソール上で手早くDockerを操作する場合はdockerコマンドを、スクリプトによる自動化を行う場合はPowerShell Module for Dockerを使ってやるのが良いと思います。

例えば以下の様な処理をスクリプト化して定期的に実行してやれば消し忘れたコンテナを綺麗に消してやることができます。

# 停止してから24時間以上経ったコンテナを削除する処理
Get-Container `
    | Where-Object {
        if ( $_.State -ne "exited") {
            return $false
        }
        $detail = Get-ContainerDetail -Container $_
        if ( [Datetime]::Now.Subtract([Datetime]::Parse($detail.State.FinishedAt)).Hours -lt 24 ) {
            return $false
        } 
        return $true
     } `
    | Remove-Container

こういった処理をdockerコマンドで頑張るにはつらみがあると思いますし、PowerShellでやればかなり楽ができると思います。

あと完全に個人的な話ですが、私はDockerを検証用に使ってるのためコンソール上の操作でも、

# コンテナをまるっと全削除
Get-Container | Remove-Container -Force

といった感じでコンテナをまるっと消したりするのにコマンドレットを使っています。
何気にこの処理をdockerコマンドでやろうとするとめんどうくさいんですよね...

最後に

とりあえずこんな感じです。
PowerShellからDockerを扱う方法についてとりとめもなく書いていきました。

PowerShell Module for DockerについてはWindowsに限らず使えますので、非Windowsな環境でもDocker管理の自動化をしたい場合に使いどころがあるかと思います。
まだアルファ版ですが発展していくツールだとは思いますのでダメ元で使ってみるのも良いかもしれません。

*1:このライブラリのVer.1.0.0は2014年リリース

Docker for Windowsにdockerd.exeが同梱された話

Docker for Windows 1.12.2 Beta26以降の話です。
Stable Channelにはまだ入っていないと思われます。(未確認)

本エントリでは1.12.2 Beta28で動作確認をしています。

Docker for Windowsとdockerd.exeについて

stknohg.hatenablog.jp

でも触れた様にこれまでのDocker for WindowsはHyper-V上にLinux仮想マシン(Alpine Linux)を立て、その仮想マシン上にあるDocker Engine(Windowsから見えるサービスとしてはcom.docker.serviceなど)がLinuxイメージを扱うものでした。
Windowsイメージを扱うには別途dockerd.exeをダウンロードしてインストールする必要がありました。

Docker for Windowsに同梱されたdockerd.exe

Docker for Windows 1.12.2 Beta26からdockerd.exeが同梱され、タスクバーのアイコンから使用するDocker Engineを切り替えることができる様になりました。

dockerd.exeのインストール先はC:\Program Files\Docker\Docker\resourcesになり、現時点ではCS版の1.12.2-cs2-ws-betaがインストールされていました。
これはWindows Server 2016にインストール可能なものと同じですが、Windows Serverと同様にサポート対象になるのかは不明です。(多分サポート対象外な気がします...)

試してみる

というわけで、Docker Engineの切り替えを試してみます。
Docker for Windowsのインストール手順はインストーラーの指示に従うだけなので省略します。

インストール直後の状態ではdockerd.exeは起動しておらずLinuxイメージを扱う状態となっています。
docker versionの結果は以下の様になっています。

f:id:stknohg:20161018032143p:plain

画面右下のタスクバーのアイコンを右クリックすると「Switch to Windows Containers...」というメニューが増えているのでこれを選択するとDocker Engineの切り替えをすることができます。

f:id:stknohg:20161018032021p:plain

切り替え中は以下の様に通知されます。

f:id:stknohg:20161018032314p:plain

ここで過去に古いdockerd.exeをインストールしている場合は警告が表示されサービスのアンインストールを指示されます。

f:id:stknohg:20161018032433p:plain

このメッセージが出た場合はその内容に従いサービスをアンインストールしましょう。

# 要管理者権限
dockerd --unregister-service

また、初回切り替え時に時間がかかることがあるそうです。
私は以前から古いdockerd.exeを使っていたためか初回切り替え時も割とすぐに切り替えできましたが、もし時間がかかる場合はしばらく待つのが良いでしょう。

切り替えが終わるとWindowsイメージを扱うことができる様になります。
docker versionの結果は以下。

f:id:stknohg:20161018032822p:plain

あとは普通にdockerコマンドでいろいろすることができます。

ちなみに、この状態でProcess Explorerを使ってプロセスを見てみると以下の様になっており、com.docker.serviceの子プロセスとしてdockerd.exeが起動されていることがわかります。

f:id:stknohg:20161018032946p:plain

サービスとしてはcom.docker.service一つで管理しており、これまでの様にdockerd.exeをサービス登録する必要はありません。

元に戻す

Linuxイメージを扱う様に戻すには、タスクバーのアイコンを右クリックすると、メニューが「Switch to Linux Containers...」に変わっているのでこれを選択すればOKです。

f:id:stknohg:20161018033330p:plain

これで元に戻ります。
なお、一度起動したdockerd.exeはDocker for Windows自体を終了するまで残り続ける様です。

最後に

とりあえずこんな感じです。
クリック一つでDocker Engineの切り替えができるのは非常に楽なので早くStable Channelに来てほしいですね。