本記事は PowerShell Advent Calendar 2019 の 10 日目です。
PowerShell で csv 形式のデータを取り扱うための基礎知識についてまとめてみました。
動作環境
Windows 10 にデフォルトで入っているバージョン(PowerShell v5.1)で動作確認してます。
PowerShell 6 だと一部の機能が動作しないですが、ほぼ同じように実行可能です。(たぶん、Out-GridView
が使えないくらい)
いずれ正式リリースされる PowerShell 7 ではきっと全て上手く動きます。
サンプルデータについて
こちらのサンプルデータを使います。
動作確認したい場合はこちらのファイルを UTF-8
で保存してください。
"product_code","product_name","price"
"POSH00001","鉛筆","100"
"POSH00002","消しゴム","50"
"POSH00003","ノート","110"
"POSH00004","定規","90"
"POSH00005","蛍光ペン","70"
csv 関連のコマンドレットの概要
まず、 csv ファイルや csv 形式の文字列と PowerShell オブジェクトとで変換を行うコマンドレットについて
主な機能を以下の表でまとめました。
PowerShell で csv データを操作する場合は、この 4 つのコマンドのどれかが入り口となります。
Cmdlets | Input | Output | Parameters | |||
---|---|---|---|---|---|---|
Delimiter | NoTypeInformation | Header | ||||
ConvertFrom-Csv | 文字列 | PsCustomObject | 各値を区切るための区切り文字 | - | ヘッダー文字列を配列で指定 | |
Import-Csv | ファイル | |||||
ConvertTo-Csv | PSCustomObject | 文字列 | 型情報を付加しない場合に指定 | - | ||
Export-Csv | ファイル |
簡潔にいうと、ファイル操作するときは、 Import-Csv
と Export-Csv
を使い、
csv 形式の文字列として扱いたいときは ConvertFrom-Csv
と ConvertTo-Csv
を使います。
各コマンドレットの使用例
Import-Csv
と Export-Csv
以下、 Import-Csv
で csv ファイルから PSCustomObject に変換するサンプルです。
# TypeInfomation
(後述)が記載されていない csv ファイルは標準で PSCustomObject に変換されます。
$products = Import-Csv .\products.csv -Encoding UTF8
$products | Format-Table
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00001 鉛筆 100
#=>POSH00002 消しゴム 50
#=>POSH00003 ノート 110
#=>POSH00004 定規 90
#=>POSH00005 蛍光ペン 70
$products[1].product_name
#=>消しゴム
今度はその逆で、 PSCustomObject から csv ファイルを出力するサンプルです。
$products = @(
[pscustomobject]@{
product_code = 'TEST00001'
product_name = 'ボールペン'
price = 100
},
[pscustomobject]@{
product_code = 'TEST00002'
product_name = '消しゴム'
price = 50
}
)
$products | Export-Csv -NoTypeInformation products.csv -Encoding UTF8
cat -Encoding UTF8 .\products.csv
#=>"product_code","product_name ","price "
#=>"TEST00001","ボールペン","100"
#=>"TEST00002","消しゴム","50"
ちなみにこのやり方だと PowerShell 独自の書き方になるため、
JSON みたいに共通のフォーマットから PSCustomObject を生成できた方が便利じゃね、という場合には以下のようなやり方もあります。
$products = @'
[
{
"product_code": "TEST00001",
"product_name ": "ボールペン",
"price ": 100
},
{
"product_code": "TEST00002",
"product_name ": "消しゴム",
"price ": 50
}
]
'@ | ConvertFrom-Json
$products | Export-Csv -NoTypeInformation products.csv -Encoding UTF8
cat -Encoding UTF8 .\products.csv
#=>"product_code","product_name ","price "
#=>"TEST00001","ボールペン","100"
#=>"TEST00002","消しゴム","50"
ConvertFrom-Csv
と ConvertTo-Csv
これまでファイルへの入出力を伴った csv 操作でしたが、
単純に文字列と PowerShell オブジェクトへの相互変換も同じような書き方で実現できます。
# csv 文字列から PowerShell オブジェクトへ変換
$products = @'
"product_code","product_name ","price "
"TEST00001","ボールペン","100"
"TEST00002","消しゴム","50"
'@ | ConvertFrom-Csv
$products
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00001 鉛筆 100
#=>POSH00002 消しゴム 50
# PowerShell オブジェクトから csv 文字列に変換
$products | ConvertTo-Csv -NoTypeInformation
#=>"product_code","product_name ","price "
#=>"TEST00001","ボールペン","100"
#=>"TEST00002","消しゴム","50"
各オプションについて
NoTypeInformation
オプション
ConvertTo-Csv
や Export-Csv
で PowerShell オブジェクトから csv 形式の文字列に変換する場合、
デフォルトでは先頭行に csv データの元となったクラス情報が #TYPE
にコメントアウトされる形で記載されるようになります。
ただ、PowerShell 6 以降は指定してもしなくても #TYPE
が付かないようになったみたいです。
(もう NoTypeInformation
オプションの意味がないような。。)
そのため、 PowerShell 6 以降では気にする必要はないですが、 PowerShell 5 だと先頭行に付加された #TYPE
によって、
PowerShell 以外からパースして csv ファイルを読み込むときに無効なフォーマットとしてエラーになるケースがあるため、
常に指定使った方が無難です。1
この #TYPE
という記載ルール、てっきり型情報を書いておけば、再度 Import-Csv
などで読み込んだ際の
デシリアライズ用にあるものだと思ってたのですが、ちょっと違うみたいです。
cd ~\AppData
ls | Export-Csv appdata.csv -Encoding UTF8
cat .\appdata.csv
#=>#TYPE System.IO.DirectoryInfo
#=>"PSPath","PSParentPath","PSChildName","PSDrive","PSProvider",...
#=>...
$appdata = Import-Csv .\appdata.csv -Encoding UTF8
$appdata | Get-Member
#=>TypeName: CSV:System.IO.DirectoryInfo
#=>Name MemberType Definition
#=>---- ---------- ----------
#=>CreationTime NoteProperty string CreationTime=2017/10/30 8:01:14
#=>Exists string Exists=True
このように、確かに Get-Member
すると CSV:System.IO.DirectoryInfo
となりますが、
フィールドは全て string 型となってしまっています。
ネット上にも有用に使っているサンプルコード見当たらないですし、
シンプルに PowerShell 5 では -NoTypeInformation
を付けて使用し、
PowerShell 6 以降では無視して使っていけば特に支障はなさそうです。
Delimiter
オプション
区切り文字を変更したい場合は、 -Delimiter <char>
オプションで変更できます。
デフォルトはもちろん、 カンマ(,
)になっていますが、例えばタブ区切りにしたい場合は以下のように使います。
$products = Import-Csv .\products.csv -Encoding UTF8
$products | ConvertTo-Csv -Delimiter "`t"
#=>"product_code" "product_name" "price"
#=>"POSH00001" "鉛筆" "100"
#=>"POSH00002" "消しゴム
#=>...
注意点:
・ PowerShell のエスケープ文字は バッククォート(`)で、バックスラッシュ(\)ではない。
・ エスケープシーケンスを区切り文字にしたい場合は ダブルクォート(""
)で囲むこと。
Header
オプション
読み込み対象となる csv 形式のデータにヘッダー行がない場合は PowerShell 側で指定する必要があります。
やり方は単純で、カラム順に合わせて配列を -Header <string[]>
に渡すだけです。
$products_csv = @'
"POSH00001","鉛筆","100"
"POSH00002","消しゴム","50"
"POSH00003","ノート","110"
"POSH00004","定規","90"
"POSH00005","蛍光ペン","70"
'@
$products_csv | ConvertFrom-Csv -Header @('product_code', 'product_name', 'price')
ちなみに単純に 1, 2, 3, ... のような列番号を連番で付けたのでよい場合は以下のように書けます。
$products_csv | ConvertFrom-Csv -Header @(1..3)
読み込んだ csv データの確認方法
csv ファイルから生成した PowerShell オブジェクトの値を確認したい場合、
だいたいは表形式「Format-Table
(Alias: ft
)」かリスト形式「Format-List
(Alias: fl
)」、
または、GUI 画面「Out-GridView
(Alias: ogv
)」を使うと便利です。
表形式「Format-Table
(Alias: ft
)」:csv 全体のデータを確認したい場合
カラム数がそこまで多すぎない場合は有用です。
カラムが多すぎると省略されてしまうため、全カラム確認したい場合は不向きかと思います。
$products | ft
リスト形式「Format-List
(Alias: fl
)」:1 レコードの詳細を確認したい場合
カラム数が多いときに便利です。
表示対象となるレコード数が多いと見づらくなります。
$products | fl
GridView(GUI) 表示「Out-GridView
(Alias: ogv
)」:レコード数、カラム数が多い場合
※ GridView は PowerShell 5 、または、 PowerShell 7 以降で使用可能です。
読み込んだ csv ファイルの内容を GUI で表示させることができます。
ただ、横スクロールができないなど、そこまで作りこまれた Viewer ではないので常にこれ使うべきという品物でもないです。
$products | ogv
ちなみに、GridView を使うと GUI 操作による行の絞り込みも可能です。
$low_price_products = $products | ogv -OutputMode Multiple
Out-GridView
コマンドレットに -OutputMode Multiple
を指定することで、
表示された GridView のレコードを選択し、「OK」ボタンを押すことで行を抽出することができます。
$low_price_products | ft
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00002 消しゴム 50
#=>POSH00004 定規 90
#=>POSH00005 蛍光ペン 70
あまり使ったことないですが、一応紹介まで。
メタ情報を得る
カラムごとの型情報を取得する
ファイル・文字列から読み込んだ csv データのカラムを確認したい場合は Get-Member
コマンドレットで取得可能です。
PowerShell オブジェクトに変換されたそれぞれのカラムは全て string 型で保持されます。
$products | Get-Member -MemberType NoteProperty | select Name, Definition
#=>Name Definition
#=>---- ----------
#=>price string price=100
#=>product_code string product_code=POSH00001
#=>product_name string product_name=鉛筆
型変換
全てのカラムが string のままだと不便なので、適切な型へ変換したいケースもあります。
その場合は以下のように書くと変換可能です。
$products = $products | % {$_.price = [int]($_.price); $_}
$products | Get-Member -MemberType NoteProperty | select Name, Definition
#=>Name Definition
#=>---- ----------
#=>price System.Int32 price=100
#=>product_code string product_code=POSH00001
#=>product_name string product_name=鉛筆
よく遭遇するトラブルへの対処法
文字化けする
文字コードについて、ここまで共通して -Encoding UTF8
をつけてサンプルコード書いてきましたが、
実はこの文字コードの挙動について PowerShell 5 とそれ以降のバージョンで差異が生じています。
・ PowerShell 5 の場合
→ 指定しない場合は(たぶん)ASCII で出力される。 SJIS を使いたい場合は Default を指定する。(日本語OSのみらしい)
・ PowerShell 6 の場合
→ 未指定の場合は UTF-8 となる。 SJIS を使いたい場合は oem を指定する。(日本語OSのみらしい)
以下、 products.csv ファイルが SJIS で保存されていた場合のファイルの読み込み方法です。
Import-Csv .\products.csv
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00001 ���M 100
#=>POSH00002 �����S�� 50
#=>POSH00003 �m�[�g 110
#=>POSH00004 ��K 90
#=>POSH00005 �u���y�� 70
# PowerShell 5 だと Default で読み込めます。
Import-Csv -Encoding Default .\products.csv
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00001 鉛筆 100
#=>POSH00002 消しゴム 50
#=>POSH00003 ノート 110
#=>POSH00004 定規 90
#=>POSH00005 蛍光ペン 70
# PowerShell 6 以降だと oem で読み込みます。
Import-Csv -Encoding oem .\products.csv
#=>product_code product_name price
#=>------------ ------------ -----
#=>POSH00001 鉛筆 100
#=>POSH00002 消しゴム 50
#=>POSH00003 ノート 110
#=>POSH00004 定規 90
#=>POSH00005 蛍光ペン 70
The member "xxxx" is already present.
読み込んだ csv ファイルのヘッダー行に同じカラム名を持つものが存在していた場合に生じるエラーです。
カラム名を直すか、 -Header
オプションで無理やり読み込むかで解消できます。
ipcsv : The member "xxxx" is already present.
At line:1 char:1
+ ipcsv '.\duplicated_headers.csv'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Import-Csv], ExtendedTypeSystemException
+ FullyQualifiedErrorId : AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.PowerShell.Commands.ImportCsvCommand
-
むしろ、デフォルトでは
WithTypeInformation
オプションとかにしないと型情報記載されないようにしてほしかったり。 ↩