2011年4月26日火曜日

◆CSVファイルをExcelで開く2

PowerShell: ◆CSVファイルをExcelで開くを少しだけ変更してみた。
CSVファイルは文字列項目をダブルコーテーションで囲んでいるケースが多々ある。
そんな場合はその項目だけに文字列属性を指定してあげることで、前回不満だった数値項目が左寄せされてしまう(計算とかも出来なくなる)のを回避できそうだ。

私のあやふやな正規表現での判定文を追加した。
$_ -match '".*"'

001
002
003
004
005
006
007
008
009
010
011

$path = $args[0]
$item = (Get-Content $path -TotalCount 1) -split ','
$item | %{$atts = @()}{if($_ -match '".*"'){$atts += 2}else{$atts += 1}}
$excel = New-Object -Com Excel.Application
$excelBook = $excel.Workbooks.Add()
$excel.Visible = 
$true
$query
 = $excel.ActiveSheet.QueryTables.Add(
"TEXT;$path",$excel.Application.Range('$A$1'))
$query.TextFileCommaDelimiter = 
$true
$query
.TextFileColumnDataTypes = 
$atts
$query
.Refresh($false) | Out-Null

2011年4月25日月曜日

◆CSVファイルをExcelで開く

拡張子CSVはデフォルトではExcelに関連付けられている。(当然Excelが入っていればだが)
カラム毎に表示されるのでメモ帳で開くよりは見やすい。
ただし、CSVファイルをダブルクリックして開いた場合はデータの内容に応じてExcelが自動的にフォーマットしてくれるのだが、これが大きなお世話になることもある。

例えば、以下のようなCSVファイルを開くと、こんな感じになってしまう。

"00001","1-1",1,-66
"00003","2-1",3,4


image

これを回避するにはテキストとしてインポートして、その時に表示されるウィザードにてカラムの属性をそれぞれ指定してあげれば良い。
毎回同じフォーマットのCSVを開くのであればこれをマクロ登録しておけば良いのだが、フォーマットの違うCSVを開く場合はその都度カラム属性の指定が必要になる。

そこで、とりあえずフォーマット変更されないようにすべての項目に対して文字列属性を指定してExcelで開くスクリプトを作ってみた。

001
002
003
004
005
006
007
008
009
010
011
012

$path = $args[0]
$itemCount = ((Get-Content $path -TotalCount 1) -split ",").Count
$excel = New-Object -Com Excel.Application
$excelBook = $excel.Workbooks.Add()
$excel.Visible = 
$true
$query
 = $excel.ActiveSheet.QueryTables.Add(
"TEXT;$path",$excel.Application.Range('$A$1'))
$query.TextFileCommaDelimiter = 
$true
$arr
 = [char[]]("2" * $itemCount) | %{[int]::Parse($_)}

$query.TextFileColumnDataTypes = 
$arr
$query
.Refresh($false) | Out-Null

結果はこんな感じになる。
image

純粋な数値項目も左寄せになってしまい今一歩感もあるが、とりあえずさっきよりはましではなかろうか。

ちなみに、属性の指定はカラムの数分だけ配列で指定すれば良さそうだ。
ここでは「文字列属性」ということで2を指定している。

またCSVの拡張子に関連付けるには、このスクリプトを呼び出すバッチファイルを作る必要がありそうだ。(ps1ファイルは実行形式として扱われていないため)

2011年4月21日木曜日

◆Excelの実行パスを取得する(Get-ItemProperty)

「ファイル名を指定して実行」にてExcelと打つとExcelが起動する。
同様にNotepadと打つとメモ帳が起動する。

コマンドプロンプトからNotepadと打つとメモ帳が起動する。
同様にExcelと打つとExcelが起動!しない。

というのは以前から知っていたのだが必要に迫られなかったので特に理由は調べていなかった。
「ファイル名を指定して実行」はPath環境変数以外にも何かを参照しているのだろう、そして何かと言えばレジストリだろうとは思っていたが。

今回、必要に迫られてレジストリキーを調べてみた。
以下のキーに値(名前)Pathの値データとして持っているようだ。
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\excel.exe"

なのでそいつをGet-ItemPropertyで取ってくれば良い。

$xlsPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\excel.exe"

Get-ItemProperty $xlsPath -Name Path | select path

(Get-ItemProperty $xlsPath -Name Path).Path

Pathパラメータを指定すると値が文字列で取れるのかと思ったが、返ってくるのはPsObjectなので結果のPathプロパティを参照する必要がある。

◆フォルダーに特定の種類のファイルが含まれるか調べる(Test-Path)

色々なやり方が考えられ、どれをとっても大した処理ではないのであまり気にする必要は無いのかとも思うのだが、一応考えてみた。

素直に考えるとこんな感じ?
(dir | ?{$_.extension -eq ".ps1"}).count -gt  0

試してみると一見良さそうに見えるが、よくある間違いのパターンでps1ファイルが一つしか無いとうまくいかない。
一つでもうまくいくように先頭に@を付けておく。

こんなのでも良いか。
@(dir .\* -inc *.ps1).count -gt 0

これはいまいちか。
@((dir) -match ".*\.ps1").count -gt 0
ん~、これじゃ途中にps1が有っても引っかかるか?
こうかな?
@((dir) -match '\.ps1$').count -gt 0
(相変わらず私の正規表現は怪しい)

他にも色々とあるのかもしれないが、一応以下のTest-Pathを使うのが簡単そう。

PS>Test-Path .\*.ps1
True

2011年4月20日水曜日

◆配列をフィルターする

PS>$a -eq $a
True

この式は必ずTrueが返ってくるだろうか。
実は、Trueが返ってこない$aの値がある。
さて、何でしょう・・・。

 

本題に戻って配列からある特定の値を抽出して結果をカウントしてみる。

原始的にやるとこんな感じ。

PS>$arr = 1,2,1,3,4,2,1
PS>$arr | %{$c=0}{if($_ -eq 1){$c++}}{$c}
3
PS>

普通は抽出なのでWhere-Objectを使うのかな。

PS>$arr = 1,2,1,3,4,2,1
PS>($arr | ?{$_ -eq 1} | Measure).count
3
PS>

意外とすっきりしない・・・。

match演算子を使うともう少しすっきりする。

PS>$arr = 1,2,1,3,4,2,1
PS>($arr -match 1).Count
3
PS>

だいぶすっきりした。

もう少し短く書ける。(他の方法もあるかもしれませんが)

PS>$arr = 1,2,1,3,4,2,1
PS>($arr -eq 1).Count
3
PS>

配列に対する比較演算子はその条件を満たす要素を返すようにオーバーロードされているようだ。
なので、この様に簡単に抽出が可能になっている。

PS>$arr = 1,2,1,3,4,2,1
PS>$arr -ge 2
2
3
4
2
PS>

なので、一番最初の話に戻って$aに配列を入れるとTrueが返ってこなくなる。

PS>$arr = 1,2,1,3,4,2,1
PS>$a = $arr
PS>$a -eq $a
PS>

2011年4月19日火曜日

◆九九の計算

LINQを見ていて九九の計算のサンプルがあったのでPowershellだとどうなるかやってみた。

2重ループの力技になってしまうのは仕方が無いのだろうか。

PS>($x=1..9) | %{"九九の計算"}{$t=$_;$x | %{"$($t)X$_ = {0,2}" -f ($t * $_)}}
九九の計算
1X1 =  1
1X2 =  2
1X3 =  3
1X4 =  4
1X5 =  5
1X6 =  6
1X7 =  7
1X8 =  8
1X9 =  9
2X1 =  2
2X2 =  4
2X3 =  6
2X4 =  8
2X5 = 10
2X6 = 12
2X7 = 14
2X8 = 16
2X9 = 18
3X1 =  3
3X2 =  6
3X3 =  9
3X4 = 12
 ・
 ・
 ・

2011年4月15日金曜日

◆自動で閉じるメッセージボックス

これまで、メッセージボックスを表示するには当たり前のように.NETのMessageBoxクラスを使ってきた。
しかし、こいつは事前にアセンブリーのロードが必要だったりして必ずしもPowershellから使いやすいとは言えない。

MessageBoxクラスの代わりにWSHを使うともう少し簡単に表示する事が可能だ。(WSHユーザーには当たり前すぎることでしょうが私は使っていなかったので)

しかも、定義をみるとこいつは指定時間後に自動で閉じるなんて機能もあるらしい。

こんな感じだ。

001
002
003
004
005

$message = "C:\の中身を表示しました。"
dir c:\
      
(
New-Object -ComObject wscript.shell).
popup(
   
$message,1,"TestTitle") | 
out-null
Write-Host
 $message -fore Yellow
<!--EndFragment--

自動で閉じる機能が使えるかどうかは別として、1行でメッセージボックスを表示できるのは嬉しい。

今後はこいつを使おう。

2011年4月13日水曜日

◆フィルター時のNULL参照エラー対策

nullオブジェクトのメソッドなどを実行しようとしてエラーが発生することはよくある。

image

これを回避する方法をいくつか考えてみた。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016

$array = ("a","b","c","B",$null)
$array | ?{$_.ToUpper() -eq "B"}

"対策1"
$ErrorActionPreference = "SilentlyContinue"
$array | ?{$_.ToUpper() -eq "B"}
$ErrorActionPreference = 
"continue"

"対策2"

$array | ?{try{$_.ToUpper() -eq "B"}catch{}}

"対策3"
$array | ?{$_} | ?{$_.ToUpper() -eq "B"}

"対策4"
$array | ?{$_ -and $_.ToUpper() -eq "B" }
<!--EndFragment

結果。

image

結果としてはどの対策も良さそうだ。
さてどれにしたものか。

対策1はちょっと無理やり的だしコーディング量も多いので却下。
対策2は悪くは無いと思うが、ブロックが増えるので若干書きづらい。
対策3は考え方が素直だしコーディング量的にも多くない。しかし、データ量が多い場合はパイプが増える分不利かもしれない。

対策4がコーディング量的にも性能的にもベターかとも思うが、幾分トリッキー?

これは、$_ がnullの時はand条件のそれ以降の判定が実行されないという動作を利用している。C#で言うところの&&演算子みたいな動作。しかし、 &&演算子と全く同じかというとそうでもなくて、nullでの除算などを行うとエラーになってしまうので使用に当たっては注意が必要。

結局、自分だけで使うスクリプトなら対策4、そうでなければ対策3、性能まで求められる処理ならば対策2といったところかな。

当然、素直にIFで判定しても良いけど。

2011年4月11日月曜日

◆日付の和暦表示

今日回ってきた社内のアンケート文書を見ていたら日付が平成22年となっていた。
あれ、今って23年じゃ無かったっけ?と思い、PowershellでGet-Date

えー、和暦で表示するにはっと・・・。あれ、表示できないの?
ヘルプを見てもネットで調べてもなんとなく無さげ。

ん~、管理ツールだから必要ないといえば必要ないとは思うのだが・・・。

とりあえず、.NETと同じ方法で表示すると以下の通り。
<

001
002
003
004
005

$cult = New-Object system.Globalization.CultureInfo("ja-JP");
$cult.DateTimeFormat.Calendar =
          New-Object System.Globalization.JapaneseCalendar
$date = Get-Date
$date.ToString("ggyy年MM月dd日",$cult)
<!--EndFragme

2011年4月5日火曜日

◆スコープ3(Set-Variable)

またまたスコープ。
以前の、PowerShell: ◆スコープPowerShell: ◆スコープ2の補足。

PowerShell: ◆スコープ2ではfunction内からスクリプトスコープの変数に値を設定するのに$script:変数名を使ったがSet-Variableコマンドレットを使っても可能なようだ。

001
002
003
004
005
006
007
008
009

$a = 1
function fncA{
$a
Set-Variable -Name a -Value ($a+2) -Scope 1
$script:a = $a + 3
$a
}
fncA
$a

-Scopeパラメータに0を指定すると現在のスコープ、1を指定すると一つ上(親)のスコープに値が設定できる。

image

ちなみに、このスコープを2にしてこのまま実行すると2つ上の親は居ないのでエラーになるが、コマンドラインからこのスクリプトを呼び出すとこうなる。

image

この時コマンドラインから$aを表示するとグローバルスコープの値3が表示される。

最初の例に戻って3行目の$a を $a = 1 に変えてみる。
結果は想像つくだろうか。

試してみるとこうなる。

image

なんとなくPowershellばかり普段使っていると若干違和感のある結果だが、1行目と3行目はC#で書けば、 int a = 1;  だと再認識すれば当たり前の結果だと判る。

2011年4月4日月曜日

◆例外処理(Trap)

以前もPowerShell: ◆例外処理でちょっと調べたが、今回はTrapについてもう少し細かく試してみた。

まずはこんなスクリプトでエラーを拾ってみる。

001
002
003
004
005
006

trap{
 
Write-Host "エラーが発生しました"
}

dir c:\nonexist -ea stop
Write-Host "スクリプトの最終に到達しました"

結果は、
image

エラーを直接表示させたくないときはcontinueを追加してあげる。

image

エラー発生時点で処理を止めたい時は、continueの代わりにbreak

image

不思議な(素晴らしい)事にTrapは途中に書いてもちゃんと有効になる。
image

この様に比較的簡単にエラー処理は行えるのだが、関数とかが入ってきてスコープが絡むと若干面倒になる。

image
上記の例ではtrap処理のcontinueに続く処理は、実際にエラーが発生した9行目の後(10行目)ではなく、エラーが発生した関数を呼び出した13行目の後からということになる。

これを10行目から続けるためにはtrap処理を関数の中に書いてあげれば良い。

image

また、関数の中でtrapしつつ、後続処理を関数呼び出しの後から続けたい場合、関数内のtrap処理ではbreakし、親スコープのtrap処理でcontinueしてあげれば良い。
以下のような感じだ。

image

2011年4月1日金曜日

◆パラメータとパイプライン両方の入力に対応した関数を作る2

PowerShell: ◆パラメータとパイプライン両方の入力に対応した関数を作るでは[Parameter(ValueFromPipeline=$True)]属性を使ったが、プロパティ値でマッピングするときはこれを[Parameter(ValueFromPipelineByPropertyName=$True)]に変更してあげれば良い。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027

function ftest{
 
#[CmdletBinding()]
  param
(
   
[Parameter(ValueFromPipelineByPropertyName=$True)]
    $name
  )
 
 
process
{
 
   
function proc($name
) {
     
Write-Host "arg1 = $name"
    }
   
   
if($name.
count){
     
$name | %{proc $_
}
    }
else
{
     
proc $name
    }
   
  }
}


"■ Param Input"
ftest (dir)

"■ PipeLine Input"
dir | ftest