tech.guitarrapc.cóm

Technical updates

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

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

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

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

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

前提

前回同様の縛りプレイです。

なるべく1ライナーで……敢えて、変数に収めるべきところすら、そのまま利用できるところは読みやすさを犠牲にパイプで繋ぐという制約で (おい

出題内容は、UPS友の会様をご覧下さい。 あと、繰り返しですが…一応。

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

  以下のフォルダ、ファイルl構成でファイルを作って、ファイル一覧を表示(ls)、ファイル内容を表示(cat)。 ※注意 : #bin/bashのおまじないは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
        }
    }

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

  1. 対象の文字列が含まれるファイルをSelect-Stringで調べて
  2. 対象のファイルの対象文字列置換を実行
  3. ファイルを出力

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

問題4: 集計

やることの意味が分からず…すいません><…省略 ( ※[Jan/25/2013 追記] 牟田口先生が書かれていました!

問題5: Fizz Buzz

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

PowerShellでFizzBuzzしてみる

問題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があることを知らず無駄な事をしてました。 牟田口先生のお陰で勉強になります。

なるほどー…ふむふむ。

TechNet - New-Timespan コマンドレットの使用

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

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

  前提の「1から10の数字がかいてあり、そのうちの一つの数がかけているファイルを作りましょう」には、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 Parameterが有ることを牟田口先生の例で学びました。これでこんな悩まなくて済むw

ほむ…

TechNet - The Get-Random Cmdlet

追加→ 手法3. ということで、そもそも-Count Parameterを使えばいいんでは

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. 含まれないと以降結果0になることを利用して真偽値で判定※牟田口先生がFizzBuzzで示されていた方法の応用です。

(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: 横にならんだ数字のソート

飽きた前回の第二回でやったことと同様なので…省略 ( ※[Jan/25/2013 追記] 牟田口先生が書かれていたので参考に。

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

飽きた…省略 ( ※[Jan/25/2013 追記] 牟田口先生が書かれていたので参考に。

まとめ

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