前回は、TSVファイルをCSVファイルへ変換してからcsv2barchart.ps1を呼び出すというラッパスクリプト「tsv2barchart.ps1」を作成した。解説は大枠の考え方に留めて、tsv2barchart.ps1の内容については触れなかった。今回は、作成したラッパスクリプトについて説明する。

→連載「PowerShell Core入門 - 基本コマンドの使い方」の過去回はこちらを参照。

前回作成したラッパスクリプト

まず、前回作成したラッパスクリプト「tsv2barchart.ps1」は次の通りだ。

#!/usr/bin/env pwsh

#========================================================================
# 引数を処理
#   -TSVFile パス             グラフのTSVデータファイルパス
#   -PNGFile パス             生成するPNG画像のファイルパス
#   -OutFile パス             中間生成されるHTMLファイルパス
#   -GraphTitle タイトル        グラフのタイトル
#   -Width 幅                        生成するPNG画像の幅
#   -Height 高さ              生成するPNG画像の高さ
#========================================================================
Param(
    [Parameter(Mandatory=$true)][String]$TSVFile,
    [String]$PNGFile = (Get-Location).ToString() + '\out.png',
    [String]$OutFile = (Get-Location).ToString() + '\out.html',
    [String]$GraphTitle = 'グラフ',
    [Int]$Width = 2000,
    [Int]$Height = 1200
)

#========================================================================
# TSVファイルからCSVファイルを生成
#========================================================================
$CSVFile = $TSVFile + ".csv"
Import-Csv -Path $TSVFile -Delimiter `t | Export-Csv -Path $CSVFile

#========================================================================
# csv2barchart.ps1を使ってグラフを生成
#========================================================================
csv2barchart.ps1                                                    `
    -CSVFile        $CSVFile                                        `
    -PNGFile        $PNGFile                                        `
    -GraphTitle     $GraphTitle                                     `
    -Width          $Width                                          `
    -Height         $Height

#========================================================================
# 作業用の作成したCSVファイルを削除
#========================================================================
Remove-Item $CSVFile

短いスクリプトだが、PowerShellの便利な機能がギュッと凝縮している。覚えておくと便利なので、頭の片隅に入れておき、折りに触れ参考にしていただきたい。

tsv2barchart.ps1の内容を読む - パラメータ

PowerShellスクリプトで特に便利で強力なのがparam()だ。この機能は引数を定義するものだが、PowerShellはparam()の定義に従ってパラメータを処理し、入力補完のデータとしても利用してくれる。引数定義、デフォルト値、必須パラメータ指定、位置引数、キーワード引数などが使え、指定しない場合には可変長引数を使うことができる。

param()は、内容を記述していくことで引数を整理する用途でも使うことができる。コーディングしながら引数を整理することができるわけだ。記述方法がよく考えられており、シームレスにPowerShellスクリプトの書き方になじんでいる。

似たような機能はPythonやJavaScript、C#などにもある。比較的新しいプログラミング言語が備えていることが多い機能の一つだ。

今回は、tsv2barchart.ps1で次のようなparam()を書いている。

Param(
    [Parameter(Mandatory=$true)][String]$TSVFile,
    [String]$PNGFile = (Get-Location).ToString() + '\out.png',
    [String]$OutFile = (Get-Location).ToString() + '\out.html',
    [String]$GraphTitle = 'グラフ',
    [Int]$Width = 2000,
    [Int]$Height = 1200
)

この指定は、次のような内容になる。

パラメータ 指定 デフォルト値 内容
-TSVFile パス 指定必須 グラフのTSVデータファイルパス
-PNGFile パス out.png 生成するPNG画像のファイルパス
-OutFile パス out.html 中間生成されるHTMLファイルパス
-GraphTitle タイトル グラフ グラフのタイトル
-Width 2000 生成するPNG画像の幅
-Height 高さ 1200 生成するPNG画像の高さ

param()は、「パラメータ名と変数名が同一」という特徴があり、指定の必須・任意、デフォルト値、位置パラメータなどを指定できる。引数の指定を行う機能だが、PowerShellスクリプトで使用する変数をまとめるためにも使えるわけだ。ここで定義しておくとパラメータ経由で変数を変更することもでき、何かと便利だ。

TSVをCSVへ変換する

tsv2barchart.ps1では、次の処理でTSVファイルをCSVファイルへ変換している。

$CSVFile = $TSVFile + ".csv"
Import-Csv -Path $TSVFile -Delimiter `t | Export-Csv -Path $CSVFile

$CSVFileは、生成されるCSVファイルのパスを保持している。注目したいのは、その次の行の処理だ。この処理は、次の2つの処理をパイプで接続したものになっている。

◆TSVファイルをメモリへ読み込む

Import-Csv -Path $TSVFile -Delimiter `t

◆メモリからCSVファイルへ書き出す

Export-Csv -Path $CSVFile

Import-CsvはCSVファイルをメモリに読み込むためのコマンドレットだが、「-Delimiter」パラメータでデリミタ(区切り文字)を指定できるようになっている。簡単に言うとCSVはデリミタが「,」のファイルで、TSVはデリミタが「\t」のファイルだ。そして、PowerShellでは\tは「t」と記述するので、「-Delimitert」を指定すればTSVファイルを読み込めることになる。

読み込まれたデータはオブジェクトとしてメモリに保持されるので、それをパイプでExport-Csvへ渡している。Export-Csvはオブジェクト化されたデータをファイルに出力するコマンドレットで、デフォルトでCSV形式の出力を行う。つまり、こちらでは-Delimiterパラメータを指定する必要はない。そのため、先ほどの示した1行でTSVファイルをCSVファイルへ変換できるわけだ。

ラッパスクリプトとして本体を呼び出す

ここまで処理すれば事前準備は完了だ。次のようにcsv2barchart.ps1を読み出せば処理を行うことができる。

csv2barchart.ps1                                                    `
    -CSVFile        $CSVFile                                        `
    -PNGFile        $PNGFile                                        `
    -GraphTitle     $GraphTitle                                     `
    -Width          $Width                                          `
    -Height         $Height

このパラメータがPowerShellスクリプトのインタフェースになるので、ここだけ互換性を確保するようにしておけば、長期にわたって安定して使うことができるようになる。

一時ファイルを削除

最後は、csv2barchart.ps1を呼び出すために作成したCSVファイルを削除する。このファイルはあくまでも一時ファイルで不要だからだ。

Remove-Item $CSVFile

ここではかなり安易にCSVファイルパスを用意しているが、ファイルの上書きなどを回避するように実装するならNew-TemporaryFileコマンドレットを使うなり、「一時ファイルが存在する場合、何らかの処理を行う」といった実装を行うなりすればよい。その辺りは、必要に応じて実装すればよいだろう。

ただ、Windowsの場合はNew-TemporaryFileコマンドレットが作成する一時ファイルは普段はアクセスしないパスに存在している。そのため、削除できなかった一時ファイルが気が付かれずにどんどん溜まっていく、といった事態に陥りがちだったりする。一時ファイルをよく使う場合には、~\AppData\Local\Temp\を時々チェックして、不要なファイルは手動で削除するようにすると、ある程度クリーンナップできる。

実行サンプル

最後に、実行サンプルを示しておこう。

  • tsv2barchart.ps1の実行サンプル

    tsv2barchart.ps1の実行サンプル

  • 生成されたグラフの画像

    生成されたグラフの画像

今回の内容を参考に、ぜひお手元でも試してみていただきたい。

付録

csv2barchart.ps1

#!/usr/bin/env pwsh

#========================================================================
# 引数を処理
#   -CSVFile パス             グラフのCSVデータファイルパス
#   -PNGFile パス             生成するPNG画像のファイルパス
#   -OutFile パス             中間生成されるHTMLファイルパス
#   -GraphTitle タイトル        グラフのタイトル
#   -Width 幅                        生成するPNG画像の幅
#   -Height 高さ              生成するPNG画像の高さ
#========================================================================
Param(
    [Parameter(Mandatory=$true)][String]$CSVFile,
    [String]$PNGFile = (Get-Location).ToString() + '\out.png',
    [String]$OutFile = (Get-Location).ToString() + '\out.html',
    [String]$GraphTitle = 'グラフ',
    [Int]$Width = 2000,
    [Int]$Height = 1200
)

#========================================================================
# 気温データを取得
#========================================================================
$CsvData = Import-Csv $CSVFile -Header 1,2
# ヘッダを変数に格納
foreach     ($Row in $CsvData) {
    $CsvHeader1 = $Row.1
    $CsvHeader2 = $Row.2
    break
}

#========================================================================
# Google Charts用HTMLの用意
#========================================================================
$GoogleChartsHTML1 = @"
<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      // Google Visualization APIおよびコアチャートパッケージを読み込み
      google.charts.load('current', {'packages':['corechart']});

      // Google Visualization API読み込み完了後に実行
      google.charts.setOnLoadCallback(drawChart);
      function drawChart() {
        // データテーブルを作成
        var data = new google.visualization.DataTable();
        data.addColumn('string', '');
        data.addColumn('number', '$CsvHeader2');
        data.addRows([
"@

$GoogleChartsHTML2 = @"
        ]);

        // チャートオプションを設定
        var options = {'title':'$GraphTitle',
                       'width':'$Width',
                       'height':'$Height'};

        // 初期化およびチャートの生成
        var chart = new google.visualization.BarChart(document.getElementById('chart_div'));

        // チャートイメージをbase64エンコードされたPNG画像データとして出力
        google.visualization.events.addListener(chart, 'ready', function () {
          document.getElementById('chart_base64').innerHTML = chart.getImageURI();
        });

        // チャートを描画
        chart.draw(data, options);
      }
    </script>
  </head>
  <body>
    <div id="chart_div"></div>
    <div id="chart_base64"></div>
  </body>
</html>
"@

#========================================================================
# CSVデータをGoogle Chartsで使える形式へ加工
#========================================================================
$GraphData = ""
$FirstLineIs = $true
foreach     ($Row in $CsvData) {
    if ($FirstLineIs) {
            $FirstLineIs = $false
            continue
    }

    $GraphData = $GraphData + "['" + $Row.1 + "'," + $Row.2 + "],"
}
$GraphData = $GraphData.trim(",")

#========================================================================
# Google Charts用のHTMLを出力
#========================================================================
$GoogleChartsHTML1                                  > $OutFile
$GraphData                                          >>$OutFile
$GoogleChartsHTML2                                  >>$OutFile

#========================================================================
# WebDriver起動
#========================================================================
webdriver_edge_start.ps1

#========================================================================
# Microsoft EdgeでGoogle Charts用のHTMLをオープン
#========================================================================
$FileURL = "file:///" + $OutFile.Replace('\','/')

'Microsoft Edgeでグラフを描画します。'
Set-SeUrl -Url $FileURL

#========================================================================
# 描画されたグラフをBase64エンコードされたPNGデータとして取得
#========================================================================
'描画したグラフをBase64エンコードされたPNGデータとして取得します。'
$Element = Get-SeElement -By XPath -Value '//*[@id="chart_base64"]'
$Base64 = $Element.Text -replace 'data:image/png;base64,',''

#========================================================================
# Base64エンコードデータをデコードしてファイルへ保存
#========================================================================
'Base64エンコードされたPNGデータをデコードして保存します。'
$Bytes = [Convert]::FromBase64String($Base64)
[IO.File]::WriteAllBytes($PNGFile, $Bytes)

#========================================================================
# WebDriverを終了
#========================================================================
webdriver_edge_stop.ps1

#========================================================================
# 作業用の一時ファイルを削除
#========================================================================
Remove-Item $OutFile

webdriver_edge_start.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを起動する
#========================================================================

#========================================================================
# 動作しているMicrosoft Edge WebDriverをすべて終了
#========================================================================
webdriver_edge_stop.ps1

#========================================================================
# Seleniumモジュールがない場合にはインストール
#========================================================================
if (-Not (Get-InstalledModule -Name Selenium 2> $Null)) {
    'Seleniumモジュールをインストールします。'
    Install-Module -Name Selenium -AllowPrerelease -Force
    Get-InstalledModule -Name Selenium
}

#========================================================================
# Microsoft Edge WebDriverを起動
#========================================================================
'Microsoft Edge WebDriverを起動します。'
$Size = '1200,800'
if  (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null))
{
    #================================================================
    # Microsoft EdgeとMicrosoft Edge WebDriverのバージョンが一致して
    # いないためにドライバが動作しなかった可能性がある。
    #================================================================

    #================================================================
    # 不要なドライバプロセスを終了
    #================================================================
    webdriver_edge_stop.ps1

    #================================================================
    # Microsoft Edgeのバージョン番号
    #================================================================
    $EdgeDir='C:\Program Files (x86)\Microsoft\Edge\Application\'
    $EdgeVersion=(  Get-ChildItem -Name $EdgeDir                    | 
                    Where-Object { $_ -NotMatch "[a-zA-Z]+" }       |
                    Select-Object -First 1                          )
                    # ↑ 【Select-Object -First 1の理由】
                    # 更新前のバージョンと更新後のバージョンが同時に
                    # 存在するタイミングがあるので、更新後のバージョン
                    # のみを取得するためにSelect-Objectを実行している。

    #================================================================
    # Microsoft Edge WebDriverダウンロードURLとデプロイ先パス
    #================================================================
    $DriverURL="https://msedgedriver.azureedge.net/$EdgeVersion/edgedriver_win64.zip"

    $SeModVer=(Get-InstalledModule -Name Selenium).Version -replace "-.+$",""
    $DriverDir="$env:HOME\Documents\powershell\Modules\Selenium\$SeModVer\assemblies"
    $DriverDownloadDir="$DriverDir\_download"

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを作成
    #================================================================
    New-Item        $DriverDownloadDir -ItemType Directory -Force

    #================================================================
    # Microsoft Edgeと同じバージョンのMicrosoft Edge WebDriverを
    # ダウンロード
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をダウンロードします。"
    curl            -get                                            `
                    -o      $DriverDownloadDir\edgedriver_win64.zip `
                    $DriverURL

    #================================================================
    # Microsoft Edge WebDriverをデプロイ
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をインストールします。"
    Expand-Archive  -Path $DriverDownloadDir\edgedriver_win64.zip   `
                    -Destination $DriverDownloadDir                 `
                    -Force

    Copy-Item       -Path $DriverDownloadDir\msedgedriver.exe       `
                    -Destination $DriverDir\msedgedriver.exe        `
                    -Force

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを削除
    #================================================================
    Remove-Item     $DriverDownloadDir -Recurse -Force

    #================================================================
    # Microsoft Edge WebDriverを起動する
    #================================================================
    if      (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null)) 
    {
            #========================================================
            # 原因不明の起動不能
            #========================================================

            #========================================================
            # 不要なドライバプロセスを終了
            #========================================================
            webdriver_edge_stop.ps1

            Exit
    }
}
'Microsoft Edge WebDriverの起動処理完了。'

webdriver_edge_stop.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを終了する
#========================================================================

#========================================================================
# WebDriverプロセスを終了
#========================================================================
if  (Get-Process -Name msedgedriver 2> $Null) 
{
    '動作しているMicrosoft Edge WebDriverを終了します。'
    Get-Process -Name msedgedriver 2> $Null

    # Microsoft Edge WebDriverを終了
    Stop-SeDriver 2> $Null

    # まだ動作しているほかのMicrosoft Edge WebDriverを終了
    if      (Get-Process -Name msedgedriver 2> $Null) 
    {
            Get-Process -Name msedgedriver 2> $Null | Stop-Process
    }

    '動作しているMicrosoft Edge WebDriverの終了処理完了。'
}