テキストファイルの処理

これまで、コマンドプロンプトやUNIX系OSと異なり、PowerShellではオブジェクトをパイプラインで処理できることを何度も説明しました。一方で、スクリプトでテキスト処理をしたいという要望もあるでしょう。心配いりません。PowerShellではテキスト処理も可能です。

今回は、テキストファイルをCSV形式に加工する例を紹介します。ユーザーアカウントや社員情報(OUデータ)、メールアドレスなど、サーバー管理の上ではこうした個人データのテキスト加工はよくあることです。

元のテキスト。1ユーザーあたり5行ずつ、名前や所属、メールアドレスが記述されています。

加工後のCSV形式。スクリプトでこのような書式に整形します。

元データのファイル名はaddress.txtとします。

このテキストをGet-Content(省略形gc)コマンドレットで読み出してパイプラインに流し、tocsv.ps1スクリプトでCSV形式に加工します。PowerShellコマンドラインで実行するとき、tocsv.ps1をカレントディレクトリに作成すれば、以下のように実行します。

gc address.txt  |  .\tocsv

この出力結果をaddress.csvとして保存するのであれば、さらにファイル保存用のコマンドレットOut-Fileを使って、以下のように実行します。

gc address.txt  |  .\tocsv  | Out-File -Encoding default -FilePath  address.csv

これは、やや面倒な記述です。

tocsvがDOSやUNIX系OSのフィルタコマンドであれば、「tocsv < address.txt > address.csv」などのように「<」を使用してテキストファイルを直接フィルタに流し込み、「>」を使ってテキストファイルにリダイレクトできます。しかし、PowerShellでは「<」を使用できません。「.\tocsv < address.txt 」と実行することはできないのです。

※ 「<」はPowerShellで予約されていますので、今後のバージョンアップでこうした使い方ができるようになるかもしれません。

一方、リダイレクト「>」はPowerShellでも使えますが...やや問題があります。PowerShellでリダイレクトしてファイル保存した場合、文字コードがUnicodeになってしまいます。しかし、Windowsで扱うテキストファイルはまだまだShift JISコードが主流であるため、汎用的なテキストファイルを作成するためには、Shift JISコードで保存しなければなりません。そのためにOut-Fileを使用します。Out-Fileの使い方は以下の通りです。

Out-File  -Encoding  文字コード種別 -FilePath  保存するファイルパス

※ 文字コード種別にdefaultを指定するとShift JISコードで保存するのですが、Shift JISコードがdefault(既定)なのにリダイレクトするとUnicodeで保存されるのは、釈然としない仕様です。

読み出した行をとりまとめて出力するプログラム

tocsv.ps1 のスクリプトプログラムは以下の通りです。このプログラムは、元のファイルの5行をカンマで接続して1レコード(1行)にします。基本的なプログラム構造としては、5行読み込むたびに整形して1行を出力する繰り返しで、5行を1行にまとめるためには、配列変数を利用します。以下の内容をメモ帳などで作成し、tocsv.ps1として保存します。

#5行をまとめて1行のCSV形式レコードにする
begin {
    $i = 0
    $r = "", "", "", "", ""
}
process {
    if ($i -lt 5) {
        $r[$i] = $_
        $i++
    }
    if ($i -eq 5) {
        $r[0] + "," + $r[1] + "," + $r[2] + "," + $r[3] + "," + $r[4]
        $i = 0
        $r = "", "", "", "", ""
    }
}

メモ帳でスクリプトプログラムを作成し、ファイル名をtocsv.ps1として保存。

行頭に#を記述した行はコメント行です。プログラムとして実行しませんので、プログラムのメモ書きなどに利用できます。

※ フィルタを作成するbegin{} process{}ブロックの詳細については、当連載の第9回を参照して下さい。

実行結果。gc(Get-Content)でaddress.txtを読み出して、tocsv1.ps1で加工。

以下、プログラムの詳細について説明します。

配列変数

begin{}ブロックで、行数を数えるために使用する変数$iと、5行分のデータを保存する配列変数$rを初期化します。配列とは同じ名前を持つ変数の集合で、その集合のうち何番目の変数かをインデックス(索引)で指定します。たとえば、$r[3]というのは、$rという配列変数の4番目の変数を意味します。

※ インデックスは0から始まります。つまり$r[0]がこの配列の先頭の変数です。「○番目」という考え方をすると0から始まるのは不自然に思えますが、先頭からのオフセット(差分)で表現するためにこうなります。先頭の変数は、先頭からの差が0なので$r[0]、5番目のデータであれば先頭からの差が4なので$r[4]となります。

配列には以下のように個々の変数に、1行1変数で記述して値を代入することもできます。

$r[0] = "abc"
$r[1] = "def"
$r[2] = "ghi"

しかし、以下のように列挙して1行の記述で代入することもできます。

$r = "abc", "def", "ghi"

また、@()内にセミコロンで列挙して1行の記述にすることもできます。

$r = @("abd"; "def"; "ghi")

※ @()を使った方法では、@()にコマンドライン相当の記述をして、コマンドレットの出力オブジェクトを配列化することもできます。

process{}ブロックによる繰り返し処理

process {}ブロックは、address.txtからの行ごとの処理です。読み込んだオブジェクト(この場合はaddress.txtの各行)は「$_」で参照できます。

$iは、何行読み込んだかを数えるために使う変数です。$iが5未満であれば($i -lt 5)、$iをインデックスとして$r配列変数に読み込んだ1行($_)を$r[$i]に代入します。そして、次の行に備えて$iを1つ増やします。$i++は$iの値を1増やす処理で、$i = $i + 1あるいは$i += 1と記述しても同じ結果になります。

$iが5になれば($i -eq 5)、$r[0]~$[4]の5行のデータをカンマで結んで出力し、$iや$rの値を再初期化します。

プログラム中に代入先のない式を記述したとき、その式の値を表示します。

たとえば、

$str = "abc"

と記述すれば、$strという変数に"abc"という値が代入されるだけで、何も表示されません。しかし、

"abc"

と記述すれば、"abc"という値が表示されます。言ってみれば、代入先を省略したときの代入先が画面と考えてもいいでしょう。

なお、注意が必要なのは、returnです。

return "abc"

と記述しても、画面に表示します。この違いは何か...returnの場合は、値を表示するだけでなく、その位置で処理を抜け出し、終了することを意味します。

たとえば、以下のような関数を作成してみます。

function  test {
    "abc"
    return "def"
    "hij"
}

このtestを実行すると、abcとdefは表示しますが、hijを表示せずにプログラムが終了してしまいます。

関数testを定義して実行した結果。returnより後ろの行は実行しません。

サンプルスクリプトプログラムtocsv.ps1の実行

address.txtをGet-Content(gc)で読み出してtocsv.ps1で加工し、address.csvとして保存するには、以下のように実行します。

gc address.txt | .\tocsv | Out-File -Encoding default -FilePath address.csv

実行結果。加工結果をリダイレクトしてaddress.csvに保存しています。

※ 当記事では何度か「表示」という表現を使っていますが、この「表示」という処理は、厳密に言えばパイプライン(ストリーム)への出力です。リダイレクト先がなければ画面に表示しますが、リダイレクトされれば画面へは表示せず、ファイルとして保存したり、次のフィルタに送ったりします。

address.csvをExcelで開いたところ。

このプログラムでは、単純に5行ずつ読み込んで1行に連結しているだけです。プログラミングになれてきたら、process {} ブロック内で、読み込んだ行が適切な内容かどうか...「ふりがな」がひらがなかどうか、メールアドレスが正しいメールアドレス形式になっているかどうか...などをチェックするプログラムを組み込んでいけば、完成度と実用度が格段に高くなるでしょう。

次回は、CSVファイルを読み込んで加工する例を紹介します。