この記事は以下のシリーズの17番目の記事です。
この記事でやること
PowerShellを使い始めると、次のような書き方をよく見かけます。
Get-ChildItem |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, LastWriteTime
最初は、記号の | や $_ が分かりにくいかもしれません。
しかし、この書き方が理解できると、PowerShellでできることが一気に広がります。
この記事では、PowerShellのパイプラインを次の流れで整理します。
| コマンド | 役割 |
|---|---|
| Get-ChildItem | 対象を集める |
| Where-Object | 条件で絞る |
| Select-Object | 必要な項目だけ取り出す |
| Export-Csv | 結果をCSVに出力する |
VBS/BAT利用者向けに言うと、PowerShellのパイプラインは、処理対象を次の処理へ順番に渡していく仕組みです。
まず結論
PowerShellの基本的な考え方は、次の形です。
集める | 絞る | 整える | 出力する
ファイル一覧を例にすると、次のようになります。
Get-ChildItem -File |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, LastWriteTime |
Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding UTF8
これは次の意味です。
| 処理 | 内容 |
|---|---|
| Get-ChildItem -File | ファイルを集める |
| Where-Object |
.txt だけに絞る |
| Select-Object | 必要な項目だけ選ぶ |
| Export-Csv | CSVに出力する |
PowerShellでは、このように処理をつなげて書くことができます。
検証用フォルダを作る
まず、検証用フォルダを作成します。
New-Item -ItemType Directory -Path "C:\work\ps-pipeline-test" -Force
Set-Location "C:\work\ps-pipeline-test"
検証用ファイルを作成します。
Set-Content -Path ".\sample1.txt" -Value "text file 1"
Set-Content -Path ".\sample2.txt" -Value "text file 2"
Set-Content -Path ".\memo.log" -Value "log file"
Set-Content -Path ".\data.csv" -Value "id,name"
サブフォルダも作ります。
New-Item -ItemType Directory -Path ".\sub" -Force
Set-Content -Path ".\sub\subsample.txt" -Value "sub folder text"
確認します。
Get-ChildItem -Recurse
パイプラインとは何か
パイプラインは、コマンドの結果を次のコマンドへ渡す仕組みです。
記号は | です。
コマンドA | コマンドB | コマンドC
これは、次のような意味です。
コマンドAの結果を、コマンドBに渡す。
コマンドBの結果を、コマンドCに渡す。
たとえば、次のコマンドを見ます。
Get-ChildItem | Select-Object Name
これは次の意味です。
Get-ChildItemでファイルやフォルダを取得する。
その結果からNameだけを取り出す。
PowerShellのパイプラインは、単なる文字列ではなく、オブジェクトを渡している点が重要です。
BATのパイプとの違い
BATでも | は使えます。
たとえば、次のような書き方です。
dir | find "txt"
BATの場合、前のコマンドの文字列出力を、次のコマンドへ渡すイメージです。
一方、PowerShellでは、文字列だけでなく、ファイル情報などのオブジェクトを渡します。
たとえば、Get-ChildItem の結果には、次のような情報が含まれています。
| プロパティ | 内容 |
|---|---|
| Name | 名前 |
| FullName | フルパス |
| Extension | 拡張子 |
| Length | サイズ |
| CreationTime | 作成日時 |
| LastWriteTime | 更新日時 |
そのため、PowerShellでは次のように書けます。
Get-ChildItem -File |
Where-Object { $_.Extension -eq ".txt" }
文字列に "txt" が含まれるかを見るのではなく、ファイルの Extension プロパティが .txt かどうかを見ています。
ここがPowerShellらしい考え方です。
Get-ChildItemで対象を集める
まずは、ファイルやフォルダの一覧を取得します。
Get-ChildItem
ファイルだけにしたい場合は、-File を付けます。
Get-ChildItem -File
サブフォルダも含めたい場合は、-Recurse を付けます。
Get-ChildItem -File -Recurse
ここでは、Get-ChildItem を「処理対象を集めるコマンド」と考えると分かりやすいです。
まず対象を集める。
そのあとに、絞る・整える・出力する。
Where-Objectで条件に合うものだけ絞る
Where-Object は、条件に合うものだけを残すコマンドです。
.txt ファイルだけに絞る例です。
Get-ChildItem -File |
Where-Object { $_.Extension -eq ".txt" }
ここで出てくる $_ は、パイプラインで渡されてきた現在の1件を表します。
つまり、次のような意味です。
現在処理中のファイルのExtensionが.txtなら残す
$_.Extension は、現在のファイルの拡張子です。
| 式 | 意味 |
|---|---|
$_ |
現在処理中の1件 |
$_.Name |
現在のファイル名 |
$_.Extension |
現在の拡張子 |
$_.Length |
現在のファイルサイズ |
$_.LastWriteTime |
現在の更新日時 |
条件式の基本
PowerShellでは、比較に次のような演算子を使います。
| 演算子 | 意味 | 例 |
|---|---|---|
-eq |
等しい | $_.Extension -eq ".txt" |
-ne |
等しくない | $_.Extension -ne ".log" |
-gt |
より大きい | $_.Length -gt 1MB |
-lt |
より小さい | $_.Length -lt 1MB |
-ge |
以上 | $_.Length -ge 1MB |
-le |
以下 | $_.Length -le 1MB |
-like |
ワイルドカード一致 | $_.Name -like "*.txt" |
-match |
正規表現一致 | $_.Name -match "sample" |
たとえば、1KBより大きいファイルを抽出する場合です。
Get-ChildItem -File |
Where-Object { $_.Length -gt 1KB }
名前に sample を含むファイルを抽出する場合です。
Get-ChildItem -File |
Where-Object { $_.Name -like "*sample*" }
30日以上更新されていないファイルを抽出する場合です。
Get-ChildItem -File -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) }
Select-Objectで必要な項目だけ取り出す
Select-Object は、必要なプロパティだけを取り出すコマンドです。
Get-ChildItem -File |
Select-Object Name, FullName, Length, LastWriteTime
これにより、ファイル情報のうち、必要な項目だけを表示できます。
Where-Object と組み合わせると、次のようになります。
Get-ChildItem -File |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, Length, LastWriteTime
意味は次の通りです。
ファイルを集める。
.txtだけに絞る。
Name, FullName, Length, LastWriteTimeだけ表示する。
計算した列を追加する
Select-Object では、計算した列を追加できます。
ファイルサイズをMBで表示する例です。
Get-ChildItem -File -Recurse |
Select-Object Name,
FullName,
@{Name="SizeMB"; Expression={[math]::Round($_.Length / 1MB, 2)}},
LastWriteTime
この部分で、SizeMB という列を作っています。
@{Name="SizeMB"; Expression={[math]::Round($_.Length / 1MB, 2)}}
Length はバイト単位なので、1MB で割ってMB単位にしています。
Export-CsvでCSVに出力する
パイプラインの最後に Export-Csv をつなげると、結果をCSVに出力できます。
Get-ChildItem -File -Recurse |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, Length, LastWriteTime |
Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding UTF8
これで、.txt ファイルだけをCSVに出力できます。
Excelで開く前提なら、環境によっては utf8BOM を検討します。
Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding utf8BOM
Windows PowerShell 5.1で古いExcel向けなら、Default を使うこともあります。
Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding Default
Sort-Objectで並べ替える
パイプラインの途中に Sort-Object を入れると、並べ替えができます。
更新日時の新しい順に並べる例です。
Get-ChildItem -File -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object Name, FullName, LastWriteTime
ファイルサイズの大きい順に並べる例です。
Get-ChildItem -File -Recurse |
Sort-Object Length -Descending |
Select-Object Name, FullName, Length
このように、PowerShellでは処理を追加したい場所にコマンドを差し込めます。
集める | 絞る | 並べる | 整える | 出力する
パイプラインを読みやすく書く
PowerShellのコマンドは、1行で書くこともできます。
Get-ChildItem -File -Recurse | Where-Object { $_.Extension -eq ".txt" } | Select-Object Name, FullName, LastWriteTime | Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding UTF8
ただし、長くなると読みにくくなります。
実務では、次のように改行して書くと読みやすいです。
Get-ChildItem -File -Recurse |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, LastWriteTime |
Export-Csv -Path ".\txt_files.csv" -NoTypeInformation -Encoding UTF8
パイプ | の位置で改行すると、処理の流れが見えやすくなります。
foreachとの違い
PowerShellでは、foreach を使って1件ずつ処理することもできます。
$files = Get-ChildItem -File
foreach ($file in $files) {
Write-Host $file.Name
}
一方、パイプラインでは次のように書けます。
Get-ChildItem -File |
ForEach-Object {
Write-Host $_.Name
}
どちらも使えます。
簡単に考えるなら、次のように使い分けるとよいです。
| 書き方 | 向いている用途 |
|---|---|
| パイプライン | 絞る、並べる、項目を選ぶ、CSV出力 |
| foreach | 複数行の複雑な処理、件数管理、try/catch |
たとえば、CSV出力だけならパイプラインが簡潔です。
Get-ChildItem -File |
Select-Object Name, FullName |
Export-Csv ".\files.csv" -NoTypeInformation -Encoding UTF8
一方、1件ずつコピーして成功件数・失敗件数を数えるなら、foreach の方が分かりやすい場合があります。
よくあるパイプラインの型
実務でよく使う型をいくつか整理します。
ファイル一覧をCSV出力する
Get-ChildItem -Path ".\" -File -Recurse |
Select-Object Name, FullName, Length, LastWriteTime |
Export-Csv -Path ".\file_list.csv" -NoTypeInformation -Encoding UTF8
特定拡張子だけ出力する
Get-ChildItem -Path ".\" -File -Recurse |
Where-Object { $_.Extension -eq ".log" } |
Select-Object Name, FullName, LastWriteTime |
Export-Csv -Path ".\log_files.csv" -NoTypeInformation -Encoding UTF8
古いファイルを抽出する
Get-ChildItem -Path ".\" -File -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Select-Object Name, FullName, LastWriteTime |
Export-Csv -Path ".\old_files.csv" -NoTypeInformation -Encoding UTF8
大きいファイル順に出力する
Get-ChildItem -Path ".\" -File -Recurse |
Sort-Object Length -Descending |
Select-Object Name,
FullName,
@{Name="SizeMB"; Expression={[math]::Round($_.Length / 1MB, 2)}} |
Export-Csv -Path ".\large_files.csv" -NoTypeInformation -Encoding UTF8
やってはいけない例
パイプラインの途中で何を渡しているか分からない
長い処理を書くと、途中で何が渡っているか分からなくなることがあります。
その場合は、途中まで実行して確認します。
Get-ChildItem -File -Recurse
次に、絞り込みまで確認します。
Get-ChildItem -File -Recurse |
Where-Object { $_.Extension -eq ".txt" }
最後に、項目選択やCSV出力を追加します。
Get-ChildItem -File -Recurse |
Where-Object { $_.Extension -eq ".txt" } |
Select-Object Name, FullName, LastWriteTime
一気に書かず、左から順番に確認すると分かりやすいです。
何でも1行で書く
次のような1行コマンドは、動きますが読みにくいです。
Get-ChildItem -File -Recurse | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } | Select-Object Name, FullName, LastWriteTime | Export-Csv ".\old.csv" -NoTypeInformation -Encoding UTF8
実務では、次のように改行した方が読みやすいです。
Get-ChildItem -File -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Select-Object Name, FullName, LastWriteTime |
Export-Csv ".\old.csv" -NoTypeInformation -Encoding UTF8
削除処理をいきなりつなげる
危険なのは、抽出した結果をそのまま削除する場合です。
Get-ChildItem -File -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item
このような処理は、まず -WhatIf を付けます。
Get-ChildItem -File -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -WhatIf
削除対象を確認してから、-WhatIf を外します。
PowerShellのパイプラインは強力なので、削除や移動の前には必ず確認する習慣をつけた方が安全です。
実務で使いやすいテンプレート
ファイル一覧を条件で絞り、CSVに出力するテンプレートです。
$targetDir = Join-Path $PSScriptRoot "target"
$outputDir = Join-Path $PSScriptRoot "output"
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
$today = Get-Date -Format "yyyyMMdd"
$outputPath = Join-Path $outputDir "file_list_$today.csv"
Get-ChildItem -Path $targetDir -File -Recurse |
Where-Object { $_.Extension -eq ".txt" } |
Sort-Object LastWriteTime -Descending |
Select-Object Name,
FullName,
Extension,
@{Name="SizeKB"; Expression={[math]::Round($_.Length / 1KB, 2)}},
LastWriteTime |
Export-Csv -Path $outputPath -NoTypeInformation -Encoding UTF8
このテンプレートは、次の流れになっています。
target配下のファイルを集める
.txtだけに絞る
更新日時の新しい順に並べる
必要な項目だけ選ぶ
CSVに出力する
まとめ
この記事では、PowerShellのパイプラインについて整理しました。
パイプラインは、コマンドの結果を次のコマンドへ渡す仕組みです。
基本形は次の通りです。
Get-ChildItem |
Where-Object { 条件 } |
Select-Object 項目 |
Export-Csv 出力先
実務では、次のように考えると分かりやすいです。
集める | 絞る | 並べる | 整える | 出力する
代表的なコマンドは次の通りです。
| コマンド | 役割 |
|---|---|
| Get-ChildItem | 対象を集める |
| Where-Object | 条件で絞る |
| Sort-Object | 並べる |
| Select-Object | 必要な項目を選ぶ |
| Export-Csv | CSVに出力する |
| ForEach-Object | 1件ずつ処理する |
VBS/BATからPowerShellに移るときに重要なのは、PowerShellでは文字列だけでなく、オブジェクトを次の処理へ渡しているという点です。
そのため、ファイル名の文字列を無理に解析するのではなく、Name、FullName、Extension、Length、LastWriteTime などのプロパティを使って処理できます。
PowerShellのパイプラインを理解すると、ファイル一覧作成、CSV出力、ログ調査、古いファイル抽出、バックアップ対象確認などの処理がかなり書きやすくなります。