PowerShellでファイル名をソートして最後のアイテムを取得するなら
パイプラインで渡すとは
一応こういうイメージを意味しています。 ・HashTableHashTable | 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 ) begin{ } process{ Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -First $Count } end{ } }
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に渡す
この辺は好き好きなので。 配列ではインデックス指定でキーで指定できません。(そりゃそうですし どうしてもキーで指定したい場合は、一度HashTableで受けれそうですね。 先ほどの要領なので軽く。@( ((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の方が直感的に扱えます。 ただ、配列も渡せる方法を知っておくのもいいかと思います。おまけ
またまた先生からアドバイスをいただきました。HashTable | function -PropertyX $_.PropertyX -PropertyY $_.PropertyY .....ならValueFromPipelineByPropertyNameにしておくと良いかもしれないですね(未確認)
— 牟田口大介さん (@mutaguchi) 2013年2月20日
そうしておけばHashTable | function だけで動くはずなので。ただHashtableでもいけたかな?もしかすると[pscustomobject]にしとかないといけないかもしれない。
— 牟田口大介さん (@mutaguchi) 2013年2月20日
これはもちろん関数側を自分で書き換えられるときだけのテクニックですが。関数側を変更できないならスプラッティング@で。
— 牟田口大介さん (@mutaguchi) 2013年2月20日
ということでテスト。
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-FirstItemHashTableのままじゃダメみたいですねー。
Get-FirstItem : 入力オブジェクトをバインドできません。次のすべての必須パラメーターをバインドするために必要な情報がありませんでした: path ext 発生場所 行:31 文字:16 + count=1} | Get-FirstItem + ~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (System.Collections.Hashtable:Hashtable) [Get-FirstItem]、ParameterBindingException + FullyQualifiedErrorId : InputObjectMissingMandatory,Get-FirstItemHashTableを[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そりゃ駄目ですよね。勉強になりました。