tech.guitarrapc.cóm

Technical updates

Hbstudy#38シェルスクリプトでいろいろやってみよう!をPowerShellでやってみた

いつもUSP友の会様を拝見してます。 さて、前回のシェル芸が第2回だったこともあり第1回を触ってみました。 触るのはおもしろそうな3,5,6,7問だけです。(問題4は意図が不明でした)

hbstudy #38で講師してきました

ちなみに、前回の挑戦はこんな感じでした。

第2回チキチキ!シェル芸人養成勉強会をPowerShellでやってみた

前提

前回同様の縛りプレイです。 なるべく1ライナーで……敢えて、変数に収めるべきところすら、そのまま利用できるところはパイプで繋ぐという制約です。 出題内容は、UPS友の会様をご覧下さい。

※ シェル環境前提なので、なるべくAliasを利用しているのはご了承ください。 ※ 私はAlias余り好きじゃない派です。 ※ PowerShellとBashの大きな違いは|(パイプ)で渡されるのが文字列ではなくオブジェクトということを念頭に…

Alias一覧

Get-ChildItem  #ls
Get-Content #cat #gc
Get-Random #random
Foreach-Object #%
Where-Object #?
Measure-Object #measure
Compare-Object #diff
Format-Table #ft
Format-List #fl

問題1: ユーザの抽出

そもそもpasswdはWindowsにないので省略。

問題2: ユーザの抽出2

同上、省略。

問題3: ファイルの一括変換

以下のフォルダ・ファイル構成でファイルを作って、ファイル一覧を表示(ls)、ファイル内容を表示(cat)。

※ 注意: #bin/bashのshebangはPowerShellにはないため置換対象を変更しています。

  • 置換操作前: #requires -Version 2.0
  • 置換操作後: #requires -Version 3.0

これで一撃です。

Select-String -Path ".\etc\*.*" -Pattern "#requires -Version 2.0" -Encoding default | %{ $filename = $_.Filename; cat "$($_.Path)" | %{$_ -replace "#requires -Version 2.0","#requires -Version 3.0" | Out-File ".\hoge\$filename" -Encoding default -Force -Append } }

例のごとく改行します。

Select-String -Path ".\etc\*.*" -Pattern "#requires -Version 2.0" -Encoding default `
    | %{ $filename = $_.Filename;
        cat "$($_.Path)" -Encoding default  `
        | %{$_ -replace "#requires -Version 2.0","#requires -Version 3.0" `
            | Out-File ".\hoge\$filename" -Encoding default -Force -Append
        }
    }

やっていることは簡単です。

  • 対象の文字列が含まれるファイルをSelect-Stringで調べる
  • 対象ファイルで文字列置換
  • ファイルを出力

ポイントは、Out-Fileでの出力が一行ごとのため-Appendが必要なぐらいでしょうか?

問題4: 集計

やることの意味が分からず…省略。

※ Jan/25/2013追記: 牟田口先生が書かれていました!

問題5: Fizz Buzz

これは先行して別記事で解いていますので、こちらを参照してください。

問題6: 日付の計算

1978年2月16日は2012年10月27日の何日前か……簡単です。

解法1.

まずは、Foreach-Objectを使った手法です。

1978..2011 | %{ (Get-Date "$_/12/31").DayOfYear} | measure -Sum | %{$_.Sum - (Get-Date "1978/02/16").DayOfYear + (Get-Date "2012/10/27").DayOfYear }

改行します。

1978..2011 `
    | %{ (Get-Date "$_/12/31").DayOfYear} `
    | measure -Sum `
    | %{ $_.Sum - (Get-Date "1978/02/16").DayOfYear + (Get-Date "2012/10/27").DayOfYear }

解法2.

この考えはそのままScriptBlockの手法にも使えます。

1978..2011 | %{ (Get-Date "$_/12/31").DayOfYear} | measure -Sum | select @{label="DIFF";expression={$_.Sum - (Get-Date "1978/02/16").DayOfYear + (Get-Date "2012/10/27").DayOfYear}} | fl

改行します。

1978..2011 `
    | %{ (Get-Date "$_/12/31").DayOfYear} `
    | measure -Sum `
    | select @{
        label="DIFF";
            expression={$_.Sum - (Get-Date "1978/02/16").DayOfYear + (Get-Date "2012/10/27").DayOfYear}
        } `
    | fl

解法1の変則

Get-DateにはAliasがないので、短くするのに冒頭でdというAliasを当ててみました。あまり変わらなかった。

begin{Set-Alias d "Get-Date"}process{1978..2011 | %{ (d "$_/12/31").DayOfYear} | measure -Sum | %{$_.Sum - (d "1978/02/16").DayOfYear + (d "2012/10/27").DayOfYear }}

改行します。

begin{
    Set-Alias d "Get-Date"
}
process{
    1978..2011 `
        | %{ (d "$_/12/31").DayOfYear} `
        | measure -Sum `
        | %{$_.Sum - (d "1978/02/16").DayOfYear + (d "2012/10/27").DayOfYear }
}

※ 答えは、12672日

Jan/25/2013追記

New-Timespanがあることを知らず無駄な事をしてました。牟田口先生のお陰で勉強になります。

Select-Object-ExpandPropertyパラメータを付けると結果だけになるのですね。いつもForeach-Objectしてましたが、このパラメータをつければ省ける状況も生まれますね。

問題7: リストにないものを探す

前提の「1から10の数字がかいてあり、そのうちの1つの数がかけているファイルを作りましょう」には、2つの手法があります。

手法1

9つそろったことを確認する手法で、条件達成が保証されます。

begin{$b=$c=@()}process{while ($c.count -lt 9){$a = Get-Random -Minimum 1 -Maximum 11;$b += $a;$c = $b | select -Unique | select -First 9}}end{$c | select -Unique | select -First 9}

改行します。

begin{$b=$c=@()}
process{
    while ($c.count -lt 9)
    {
        $a = Get-Random -Minimum 1 -Maximum 11
        $b += $a
        $c = $b | select -Unique | select -First 9
    }
}
end{$c | select -Unique | select -First 9}

手法2

十分な数を回す手法で、必ずしも条件達成が保証はされないので母数を大きくします。

1..100 | %{Get-Random -Minimum 1 -Maximum 11}  | select -Unique | select -First 9

Jan/25/2013追記

Get-Random-Countが有ることを牟田口先生の例で学びました。これでこんな悩まなくて済みます。

TechNet - The Get-Random Cmdlet

手法3

そもそも-Countを使えばいいのでは。

1..10 | Get-Random -Count 10 | select -First 9

さてファイルの生成後は、いよいよお題です。今回、3つの解法を考えました。

解法1

合計値との差異から判定は、正直卑怯というか目的が。

(cat .\num.txt) | measure -Sum | %{[int](1..10 | measure -sum).Sum - [int]$_.Sum }

解法2

比較で含まれないモノを判定する方法です。

1..10 | %{diff $_ ([int[]](cat .\num.txt))} | ?{$_.SideIndicator -eq "<="} | fl

Jan/25/2013追記

これも牟田口先生の指摘で勉強しました。

なるほど、diffに与えた-PassThruパラメーターですか。このパラメーターを渡すと、パイプラインに渡す出力が変化します。

この辺で勉強したり。

Advanced Compare-Object: Working with Results

あとは、1..10ではなく(1..10)(cat num.txt)で比較しているのがいいです。(..)とすることで、直接比較しているので明らかに私のは無駄で。

解法3

※ 牟田口先生がFizzBuzzで示されていた方法の応用です。

含まれないと以降結果0になることを利用して真偽値で判定

(0..9 | %{($_+1)*!(($_+1) - ([int[]](cat .\num.txt) | sort)[$_])} | measure -Maximum).Maximum + 1

手法2のファイル生成と、解法1をワンライナーで行う例です。

※ ファイル生成がないので意味がない?

1..100 | %{Get-Random -Minimum 1 -Maximum 11}  | select -Unique | select -First 9 | measure -Sum | %{[int](1..10 | measure -sum).Sum - [int]$_.Sum }

Jan/25/2013追記

少し改良するとこうでしょうか。

1..10 | Get-Random -count 10 | select -First 9 | measure -Sum | %{[int](1..10 | measure -sum).Sum - [int]$_.Sum }

diffにパイプで渡せず…これではダメなんですね…・

1..10 | Get-Random -count 10 | select -First 9 | diff $a (1..10) -PassThru

問題8: CPU使用率

Get-Processの記事で分かるとおり、そもそもCPU %をWindowsで取得は省略。

PowerShellを使ってサーバで動いているプロセスを知りたい

Jan/25/2013追記

牟田口先生もこのように……むむむ…ワンライナーは難しいかー

問題9: 横にならんだ数字のソート

前回の第2回でやったことと同様なので…省略

Jan/25/2013追記

牟田口先生が書かれていたので参考に。

問題10: 横にならんだ数字のソート

牟田口先生が書かれていたので参考に。

まとめ

実は、社内のBash使いと「せーの」で開始して問3の完成速度で負けたので…ぐぬぬ…。 是非、USP友の会様には第3回の公開を期待しています。