PowerShellを使ってCSVを編集する(実践編)
2023/10 MSの発表により将来的にWindowsからVBScriptが廃止になるとのことで
その後継はPowerShellということになるそうです。
PowerShellがVBScriptより使いやすいのかはどうかは思案の余地があるとしても、
PowerShellのCSV加工は凄く便利だったのでゼロから勉強しながら使ってみることにします。
この記事は、それについてほうぼうで調べてきてものを纏めたもので、
他のプログラミング言語には触れている人レベルを想定します。
個々のコマンドの使い方や注意点は理論編へどうぞ
- Windows 11 & PowerShell 5.1で検証しています。
PowerShell起動バッチ
とりま、PowerShellを手で呼び出すのは面倒なので、起動バッチファイルを作ります。
名前はとりあえずCsvConvert.batとしておきます。batなのでSJISで保存します。
引数でファイル名を渡しておきましょう。
@ECHO OFF
SETLOCAL
SET BASE_DIR=%~dp0
CD /d "%BASE_DIR%"
SET IN_FILE=source.csv
SET OUT_FILE=destination.csv
powershell -NoProfile -ExecutionPolicy Unrestricted .\CsvConvert.ps1 "%IN_FILE%" "%OUT_FILE%"
TIMEOUT 5 > nul 2>&1
ENDLOCAL
EXIT /b 0
私の過去記事にあるテンプレートを使用しています。
サンプルCSVファイル
本記事のsource.csvの内容がこちらになります。
"ID","名前","年齢","コメント"
"0001","アリス",18,"がんばります"
"0002","ベティ",15,
"0003","クリス",17,""
"0004","ディック",21,"コメントには
力を込めんと"
"0005","エリン",22,"""エリ""と呼んでください"
以降このサンプルを使います。こいつの文字コードはUTF-8です。
PowerShellスクリプト・ベース
呼び出されるCsvConvert.ps1を作成していきます。
文字コードはUTF-8で保存してください。
Set-PSDebug -Strict
# 引数取得とチェック
$srcFile = $args[0]
$destFile = $args[1]
Write-Host "[Info] Input :" $srcFile
Write-Host "[Info] Output:" $destFile
if(([string]::IsNullOrEmpty($srcFile)) -or ([string]::IsNullOrEmpty($destFile)))
{
Write-Host '[Error] Filename is empty.'
exit
}
## 開始
Write-Host '[Info] Execute Start'
## 前回出力時の変換後のファイルがあれば削除する
if(Test-Path $destFile){
Remove-Item -Path $destFile
}
## CSV取込
$srcs = Import-Csv -Encoding utf8 -Path $srcFile
## #############################################################################
$dests = $srcs
## #############################################################################
## CSV保存
$dests | Export-Csv -Encoding utf8 -NoTypeInformation -Path $destFile
$dests | ft
Write-Host '[Info] Execute End'
実用ということで、引数が空だったら抜ける処理と、失敗時に前のファイルが残らないようにしておきます。
本体は## CSV取込
と## CSV保存
の部分です。
ID 名前 年齢 コメント
-- ---- ---- --------
0001 アリス 18 がんばります
0002 ベティ 15
0003 クリス 17
0004 ディック 21 コメントには...
0005 エリン 22 "エリ"と呼んでください
これは標準出力(ft)側。別にdestination.csvが生成されます。
destination.csv
"ID","名前","年齢","コメント"
"0001","アリス","18","がんばります"
"0002","ベティ","15",""
"0003","クリス","17",""
"0004","ディック","21","コメントには
力を込めんと"
"0005","エリン","22","""エリ""と呼んでください"
ファイル側では各項目がダブルクォートされます。
ポイントは4行目のコメントで、出力では省略されているものの、ファイル取込・保存ではちゃんと処理されていることが分かります。
一部の項目を加工する
※上のPowerShellスクリプト・ベース内の$dests = $srcs
の置換する形で実装します。
$dests = $srcs
foreach ($dest in $dests){
$dest."年齢" = $dest."年齢" + "歳";
}
ID 名前 年齢 コメント
-- ---- ---- --------
0001 アリス 18歳 がんばります
0002 ベティ 15歳
0003 クリス 17歳
0004 ディック 21歳 コメントには...
0005 エリン 22歳 "エリ"と呼んでください
destination.csv
"ID","名前","年齢","コメント"
"0001","アリス","18歳","がんばります"
"0002","ベティ","15歳",""
"0003","クリス","17歳",""
"0004","ディック","21歳","コメントには
力を込めんと"
"0005","エリン","22歳","""エリ""と呼んでください"
ループを回しながら処理を行います。
改行を取り除く
※上のPowerShellスクリプト・ベース内の$dests = $srcs
の置換する形で実装します。
改行文字を取り除きます。
## そのまま複製
$dests = $srcs
## 改行を取り除く処理
foreach ($dest in $dests){
foreach ($col in @("名前","コメント")){
if($dest.$col){
$dest.$col = ($dest.$col).Replace("`r`n", " ")
$dest.$col = ($dest.$col).Replace("`n", " ")
$dest.$col = ($dest.$col).Replace("`r", " ")
}
}
}
レコードごとに、各ヘッダ項目ごとにReplaceを掛けます。
(PowerShellでの改行コードは `n
と表現されます)
ID 名前 年齢 コメント
-- ---- ---- --------
0001 アリス 18 がんばります
0002 ベティ 15
0003 クリス 17
0004 ディック 21 コメントには 力を込めんと
0005 エリン 22 "エリ"と呼んでください
destination.csv
"ID","名前","年齢","コメント"
"0001","アリス","18","がんばります"
"0002","ベティ","15",""
"0003","クリス","17",""
"0004","ディック","21","コメントには 力を込めんと"
"0005","エリン","22","""エリ""と呼んでください"
改行コードが無くなれば、他の言語でも安全に取り込めますね。
新しいCSVを作り直す
※上のPowerShellスクリプト・ベース内の$dests = $srcs
の置換する形で実装します。
元々のCSVデータから各レコードを取り出し、
空っぽのレコードを作成してデータを設定して登録していく処理です。
## 出力結果の項目名を配列で定義
$destHeaders = @("ID","名前","年齢","出身")
## 出力結果を配列で準備
$dests = @()
## 変換元のデータを1レコードごとに処理する
foreach ($src in $srcs){
if($src."コメント" -cne ""){
# 空のレコードを作成
$dest = New-Object PSObject | Select-Object $destHeaders
# 値をセットしていく
$dest."ID" = $src."ID"
$dest."名前" = $src."名前"
$dest."年齢" = [int]($src."年齢") + 5
if($dest."名前" -ceq "アリス"){
$dest."出身" = "オーサカ"
}else{
$dest."出身" = "トーキョー"
}
# レコードを結果データに登録
$dests += $dest
}
}
コメントのある人のみ「年齢」に+5して「出身」を作成したものを登録するという出力。
ID 名前 年齢 出身
-- ---- ---- ----
0001 アリス 23 オーサカ
0004 ディック 26 トーキョー
0005 エリン 27 トーキョー
destination.csv
"ID","名前","年齢","出身"
"0001","アリス","23","オーサカ"
"0004","ディック","26","トーキョー"
"0005","エリン","27","トーキョー"
変換後が変換前とかなり異なる場合は、こうした方がスッキリするかもしれません。
新しいCSVを作り直す2
似たようなことがforeach文ひとつで出来きます。
速度はこちらが速いのでこちらが推奨であると思う。
## 変換元のデータを1レコードごとに処理したものを配列に保存
$dests = foreach ($src in $srcs) {
$area = "トーキョー"
[PSCustomObject]@{
"ID" = $src."ID"
"名前" = $src."名前"
"年齢" = [int]($src."年齢") + 1
"出身" = $area
}
}
マスタデータ参照
source.csvに、master.csv の内容をくっつけたいとします。
ID,出身
0001,"オーサカ"
0003,"ナゴヤ"
0003,"サッポロ"
※上のPowerShellスクリプト・ベース内の$dests = $srcs
の置換する形で実装します。
## マスタ持込
$masters = Import-Csv -Encoding utf8 -Path "master.csv"
## 出力結果の項目名を配列で定義
$destHeaders = @("ID","名前","出身")
## 項目の抽出
$dests = $srcs | Select-Object $destHeaders
## データを1レコードごとに処理する
foreach ($dest in $dests){
# マスタ取得
$master = $masters | Where-Object {$_."ID" -ceq $dest."ID" } | Select-Object -First 1
if($master){
$dest."出身" = $master."出身"
}
}
ID 名前 出身
-- ---- ----
0001 アリス オーサカ
0002 ベティ
0003 クリス ナゴヤ
0004 ディック
0005 エリン
destination.csv
"ID","名前","出身"
"0001","アリス","オーサカ"
"0002","ベティ",
"0003","クリス","ナゴヤ"
"0004","ディック",
"0005","エリン",
マスタに0003が2つありますが、Select-Object -First 1
で先頭1行に絞っています。
このSelectがないと0003 クリス {ナゴヤ, サッポロ}
という配列で表示され、
出力ファイルでは"0003","クリス","System.Object[]"
と出てきてしまいます。
というわけで理論編と実践編でまとめてみました。
基本的にはこれの亜種で大抵のCSVファイルの加工がBAT+PowerShellで変換ができると思います。
PorwerShellのメンターの方々、ここをこうした方がいいというのがありましたらご教授ください。
あと必要なときに加筆予定
独学のため正確でない可能性があります。
(っ・x・)っ きゅ