PowerShellでファイルの読み書き
WSH(Windows Host Script)やコマンドプロンプトは、Windowsを操作するコードとして長年機能してきましたが、近年それらがPowerShellに置きかえられつつあります。
すでに潮流に乗り遅れた感も強いですが、ここで今までVBSで書いていたコードをPowerShellに置き換えるための覚書のひとつを残しておきます。
今回は主にファイルの読み書きです。
基本的な使い方
PowerShellのスクリプトは、拡張子「.ps1」で保存されます。「.vbs」や「.bat」ファイルはダブルクリックでスクリプトを実行することができましたが、「.ps1」スクリプトはそれができません。ダブルクリックすると標準では「メモ帳」でスクリプトの中身を表示します。実行したい場合は右クリックから「PowerShellで実行」を選択します。また、右クリックメニューで「編集」を選択すると今度はメモ帳ではなく「PowerShell ISE」という開発環境でスクリプトが開きます。
コード内でコメントを書きたい場合は、vbsでは'(クォート)だったものが、ps1では#(シャープ)に代わっています。
変数は$(ドルマーク)を付けて定義します。
vbsではメッセージボックスを標準で使えましたが、ps1では「Add-Type」を使って、「.NET」のフォームを利用して表示します。
Add-TypeではほかにもC#のソースコードを読み込んだりもできます。それらは「[読み込んだクラス]::メソッド」で実行できます。たとえば、HelloWorldというメッセージボックスを表示するには次のように書きます。
スクリプトのある場所をPowerShellで取得するには「$MyInvocation.MyCommand.Path」や、「$PSScriptRoot」という変数があります。
ファイルの読み書き
カレントディレクトリにある、input.txtから1行ずつ読み込みoutput.txtに出力するコードは次のようになります。
ReadWriteFileSample1.ps1
# Forms読み込み
Add-Type -Assembly System.Windows.Forms
#スクリプトの親ディレクトリを取得
$strAppDir = Split-Path -Parent $MyInvocation.MyCommand.Path
#出力ファイルパス
$strOutputFilePath = $strAppDir+"\output.txt"
#入力ファイルパス
$strInputFilePath = $strAppDir+"\input.txt"
#入力ファイル存在確認
if (!(Test-Path $strInputFilePath)) {
[System.Windows.Forms.MessageBox]::Show("入力ファイル:"+$strInputFilePath+"が存在しません")
return
}
#ファイルオープン
$fileI = New-Object System.IO.StreamReader($strInputFilePath,[System.Text.Encoding]::GetEncoding("sjis"));
#エラークリア(エラーは$Errorに格納されています)
$Error.Clear()
#ファイル出力開始上書き(sjisをUTF-8に変換して保存します)
Out-File $strOutputFilePath -Encoding utf8
# $nullはnullを表す自動変数です。
if ($Error[0] -ne $null) {
#ファイルオープン失敗
[System.Windows.Forms.MessageBox]::Show($Error[0])
return
}
while(($strLine = $fileI.ReadLine()) -ne $null) {
#書き込み
echo "$strLine" | Out-File $strOutputFilePath -Encoding utf8 -Append
}
#ファイルクローズ
$fileI.Close()
[System.Windows.Forms.MessageBox]::Show("終了しました")
ファイル出力の方法はほかにもいろいろあって、リダイレクト>で指定する方法や「Start-TranScript」と「Stop-TranScript」を使う方法もあります。Stream-Readerで読み込んだのだから書き込みはStream-Writerだろう、と考える場合は次のようにします。Stream-WriterのコンストラクタはReaderと似ていますが、第二引数に自動変数の$trueを入れると追記、$falseを入れると上書きとなります。
ReadWriteFileSample2.ps1
Add-Type -Assembly System.Windows.Forms
$strAppDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$strOutputFilePath = $strAppDir+"\output.txt"
$strInputFilePath = $strAppDir+"\input.txt"
if (!(Test-Path $strInputFilePath)) {
[System.Windows.Forms.MessageBox]::Show("入力ファイル:"+$strInputFilePath+"が存在しません")
return
}
#入力ファイルオープン
$fileI = New-Object System.IO.StreamReader($strInputFilePath,[System.Text.Encoding]::GetEncoding("sjis"));
#出力ファイルオープン(ここでは上書きです)
$fileO = New-Object System.IO.StreamWriter($strOutputFilePath, $false,[System.Text.Encoding]::GetEncoding("utf-8"))
while(($strLine = $fileI.ReadLine()) -ne $null) {
#書き込み
$fileO.WriteLine($strLine)
}
#ファイルクローズ
$fileI.Close()
$fileO.Close()
[System.Windows.Forms.MessageBox]::Show("終了しました")
文字列の処理
このような処理の場合、途中読み込んだ文字列に対して何かしらの作業をすると思いますが、その際に使いそうなものをリストアップしておきます。
- 文字長(.Length)
文字長を取得したい場合は$strLine.Lengthを使います。
- 切り出し(.Substring)
文字列を字切り出したい場合は$strLine.Substring(n,m)を使います。引数は(開始位置、終了位置)で、一文字目は0となります。終了位置は省略すると残りの文字列すべてとなります。また存在しない位置を指定するとエラーとなります。
- 分割(.Split)
引数に指定した文字で分割します。戻り値は配列が返ります。たとえば、カンマで分割するなら次のようにします。$strLine.Split(',')
- 正規表現(-match)
正規表現による抽出は「$strLine -match "正規表現"」 とします。戻り値は一致すればTrueしなければFalseが返ります。一致した項目は$Matchesという変数に格納されます。
- 文字置換(.Replace)
文字列を置換するには$strLine.Replace(before,after)とします。
- 正規表現で文字置換(-replace)
$strLine -replace "正規表現","置換文字"とします。
ファイル取得
指定のフォルダ内のファイルをすべて取得して、順に処理したいという自動化のケースは多いと思います。その際はGet-ChildItemを用います。-Pathオプションの引数に取得したいディレクトリを指定します。
出力にフィルターをかけたい場合は-Filterオプションを使います。
Filterとは別に-Include、と-Excludeというオプションもあります。これらも対象の絞り込に使えますが、先に-Pathオプションで*(アスタリスク)を使って対象のディレクトリを指定しておく必要があります。たとえばd:¥test¥*とすると、dドライブのtestフォルダに対しての制限になります。-Pathオプションに*がない状態でこれらのオプションを使うと何も結果が返りません。
また公式のヘルプ(Get-Help Get-ChildItem -Online)によると、Filterオプションははオブジェクト取得時に機能するそうなので、ほかの絞り込みより効率的だということです。
他よく使いそうなオプションとしては、次のようなものがあります。
- -Recurse
サブフォルダを再帰的に検索します。
- -File
結果をファイルだけに絞り込みます。
- -Directory
結果をフォルダだけに絞り込みます。
- -Name
通常、結果にはファイルオブジェクトが戻りますが、-Nameオプションを使うことでファイル名だけを取得することも可能です。
取得した結果はforeachなどで処理します。
foreach($f in $files) {
#ファイルのフルパスを表示(オブジェクト時)
Host-Write($f.FullName)
}