今回は、HashTableや配列(一次、多次元)を、パイプラインでfunctionに渡してみます。
PowerShellでファイル名をソートして最後のアイテムを取得するなら
パイプラインで渡すとは
一応こういうイメージを意味しています。
HashTable
HashTable | function -param1 $_.PropertyX -param2 $_.PropertyY .....
配列
配列 | function -param1 $_[0] -param2 $_[1] .....
実行するfunction
前回のfunctionを実行してみましょう。
function Get-FirstItem { [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [string]$Path, [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [string]$Sortby, [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [int]$Count ) process{ Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -First $Count } }
HashTableをパイプラインでfunctionに渡す
HashTableの各Key毎にValueを渡すだけです。例えば拡張子を指定します。
@{
path=(Get-Location).Path;
ext="Extention";
count=1} | %{ Get-FirstItem -Path $_.path -Sortby $_.ext -Count $_.count }
ファイル名を指定してみると?(敢えて変な書き方をしてみました。)
@{
path=(Get-Location).Path;
key=((Get-ChildItem | Get-Member -MemberType Property).Name | ? {$_ -like "Na*"});
count=5} | %{ Get-FirstItem -Path $_.path -Sortby $_.key -Count $_.count }
HashTableなので見やすい感があります。 HashTableは1Keyに1Valueなので、複数条件をまとめて実行するにはForeach-Obejctで囲むなり等が必要になります。 例えば、Get-ChildItemで指定可能な各プロパティごとにソートキーを指定してみます。
(Get-ChildItem | Get-Member -MemberType Property).Name | %{ @{ path=(Get-Location).Path; key=$_; count=5} ` | %{"sort by : $($_.key)" Get-FirstItem -Path $_.path -Sortby $_.key -Count $_.count } }
各プロパティごとに取得できました。
PS C:\hoge\fuga> D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1 sort by : Attributes ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : CreationTime ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : CreationTimeUtc ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : Directory ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : DirectoryName ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : Exists ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : Extension ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : FullName ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 18 10.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt sort by : IsReadOnly ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : LastAccessTime ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : LastAccessTimeUtc ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : LastWriteTime ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : LastWriteTimeUtc ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 5.txt sort by : Length ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt sort by : Name ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 18 10.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 4.txt
一次配列をパイプラインでfunctionに渡す
一次配列をパイプを介して渡す際は少し注意です。
エラーが出るパターン
単純に渡そうとすると…配列が分解されてしまいエラーがでます。
# 配列が分解されるためErrorが出るパターン ((Get-Location).Path,"Name",1) | %{Get-FirstItem -Path $_[0] -Sortby $_[1] -Count $_[2]}
エラーが出ました。
Get-ChildItem : パス 'C:\hoge\fuga\C' が存在しないため検出できません。 発生場所 D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1:52 文字:9 + Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -Last $Count + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\hoge\fuga\C:String) [Get-ChildItem], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand Get-ChildItem : パス 'C:\hoge\fuga\C' が存在しないため検出できません。 発生場所 D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1:53 文字:9 + Get-ChildItem $Path | Sort-Object $SortBy -Descending | Select-Object -L ... + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\hoge\fuga\C:String) [Get-ChildItem], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand Get-ChildItem : パス 'C:\hoge\fuga\N' が存在しないため検出できません。 発生場所 D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1:52 文字:9 + Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -Last $Count + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\hoge\fuga\N:String) [Get-ChildItem], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand Get-ChildItem : パス 'C:\hoge\fuga\N' が存在しないため検出できません。 発生場所 D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1:53 文字:9 + Get-ChildItem $Path | Sort-Object $SortBy -Descending | Select-Object -L ... + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\hoge\fuga\N:String) [Get-ChildItem], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand Get-LastItem : 引数が空の文字列であるため、パラメーター 'Sortby' にバインドできません。 発生場所 D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1:207 文字:69 + ((Get-Location).Path,"Name",1) | %{Get-LastItem -Path $_[0] -Sortby $_[1] -Count ... + ~~~~~ + CategoryInfo : InvalidData: (:) [Get-LastItem]、ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Get-LastItem
どのような事でしょうか。 単純にForeach-Objectで見てみましょう。
((Get-Location).Path,((Get-ChildItem).Extension | sort -Unique),1) | %{$_}
ふむ。
C:\hoge\fuga
Name
1
では、インデックスを指定してみます。
((Get-Location).Path,"Extension",1) | %{$_[0]}
StringがCharに分解されています。これがエラーの原因ですね。
C
N
1
一次配列をパイプの先に配列として渡す
対処は、一次配列の前にカンマ","をつけます。
,((Get-Location).Path,"Name",1) | %{Get-LastItem -Path $_[0] -Sortby $_[1] -Count $_[2]}
上手く渡せました。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt
多次元配列をパイプラインでfunctionに渡す
多次元配列をパイプを介して渡す際はそのままですね!
@(
((Get-Location).Path,"Extention",1),
((Get-Location).Path,"Name",2)
) `
| %{Get-FirstItem -Path $_[0] -Sortby $_[1] -Count $_[2]}
中の配列毎にfunctionが実行されました。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 18 10.txt
多次元配列をHashTableで受けてからfunctionに渡す
この辺は好き好きなので。
配列ではインデックス指定でキー指定できません。
どうしてもキーで指定したい場合は、一度ハッシュテーブルで受けられそうですね。 先ほどの要領なので軽く。
@(
((Get-Location).Path,"Extention",1),
((Get-Location).Path,"Name",2)
) `
| %{
@{
path=$_[0];
ext=$_[1];
count=$_[2]}} `
| %{ Get-FirstItem -Path $_.path -Sortby $_.ext -Count $_.count }
キーで指定できています。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt -a--- 2013/02/19 20:06 18 10.txt
簡単に、多次元配列をそのまま渡す場合と、一度HashTableの実行時間を計測してみました。(単位:Milliseconds)
#多次元配列をそのままパイプで渡す 1..10000 | %{ Measure-Command{ @( ((Get-Location).Path,"Extention",1), ((Get-Location).Path,"Name",2) ) ` | %{Get-FirstItem -Path $_[0] -Sortby $_[1] -Count $_[2]} } } | measure -Average TotalMilliseconds #多次元配列をHashTableでうけてからパイプで渡す 1..10000 | %{ Measure-Command{ @( ((Get-Location).Path,"Extention",1), ((Get-Location).Path,"Name",2) ) ` | %{ @{ path=$_[0]; ext=$_[1]; count=$_[2]}} ` | %{ Get-FirstItem -Path $_.path -Sortby $_.ext -Count $_.count} } } | measure -Average TotalMilliseconds
この例では約0.11 msの差ですね。
Count : 10000 Average : 3.37438835999998 Sum : Maximum : Minimum : Property : TotalMilliseconds Count : 10000 Average : 3.48527187 Sum : Maximum : Minimum : Property : TotalMilliseconds
まとめ
PowerShellは、パイプでオブジェクトを渡すため、HashtTableの方が直感的に扱えます。 ただ、配列も渡せる方法を知っておくのもいいでしょう。
おまけ
またまた先生からアドバイスをいただきました。
ということでテスト。
function Get-FirstItem { [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string]$path, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string]$ext, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [int]$count ) begin{ } process{ Get-ChildItem $path | Sort-Object $ext | Select-Object -First $count } end{ } }
HashTableのパラメータ名も合わせてみると……
@{
path="C:\hoge\fuga";
ext="Extention";
count=1} | Get-FirstItem
HashTableのままじゃダメみたいですねー。
Get-FirstItem : 入力オブジェクトをバインドできません。次のすべての必須パラメーターをバインドするために必要な情報がありませんでした: path ext 発生場所 行:31 文字:16 + count=1} | Get-FirstItem + ~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (System.Collections.Hashtable:Hashtable) [Get-FirstItem]、ParameterBindingException + FullyQualifiedErrorId : InputObjectMissingMandatory,Get-FirstItem
HashTableをPSCustomObjectにキャストすればK,Vではなく、プロパティとして認識するので可能になります。
[PSCustomObject]@{ path="C:\hoge\fuga"; ext="Extention"; count=1} | Get-FirstItem
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 6.txt
この違いは、キャスト前とキャスト後を比べれば一目瞭然です。
@{
path="C:\hoge\fuga";
ext="Extention";
count=1}
[PSCustomObject]@{
path="C:\hoge\fuga";
ext="Extention";
count=1}
実行してみると
#HashTableのまま Key : path Value : C:\hoge\fuga Name : path Key : ext Value : Extention Name : ext Key : count Value : 1 Name : count #HashTableを[PSCustomObject]にキャスト path : C:\hoge\fuga ext : Extention count : 1
そりゃ駄目ですよね。勉強になりました。