このような記事があったのですが……ふむ…。 少し冗長なのでコードを自分なりに修正すると…。
ファイル名でソートしたときに一番最後にくるファイルを開く PowerShell スクリプト
元コード
元はこうです。
Get-ChildItem C:\hoge\fuga | Sort-Object -Descending | Select-Object -First 1 | Invoke-Item
準備
状況を考えるため、準備しましょう。 フォルダを作成します。
New-Item -Path C:\hoge\fuga\ -ItemType Directory
フォルダができました。
ディレクトリ: C:\hoge Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2013/02/19 19:59 fuga
適当にファイルも作成します。
1..10 | %{$num=$_;"hoge" + $_ | Out-File C:\hoge\fuga\$num.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 -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 -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 8.txt -a--- 2013/02/19 20:06 16 9.txt
元コードの実施
実行してみると?
Get-ChildItem C:\hoge\fuga | Sort-Object -Descending | Select-Object -First 1 | Invoke-Item
確かにファイル名ソートで最後のファイルが開きましたね。
元コードの改善
実際に開く直前でパイプを止めます。
Get-ChildItem C:\hoge\fuga | Sort-Object -Descending | Select-Object -First 1 #| Invoke-Item
最後を取っていますが…。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 9.txt
さて改善できないでしょうか。 勿論できます。サンプルは「名前でソートを明示しつつ最後を取得」するのに、sort -Descending
とselect -First 1
がどうにも謎指定ですね。 よって、これで十分です。
Get-ChildItem C:\hoge\fuga | Sort-Object name | Select-Object -Last 1
同じ結果を取得できています。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 9.txt
最後のアイテムを実行したいなら、例の通りInvoke-Itemを付けるだけですね。
Get-ChildItem C:\hoge\fuga | Sort-Object name | Select-Object -Last 1 | Invoke-Item
Function化を意識する
また、Sort-Objectへのプロパティ指定もできているので、これならfunction化も容易です。 例えば、指定したパス($Path)を、指定したプロパティでソート($Sortby)して、指定個数取得($Count)というファンクションが思いつきます。 最後のアイテムを取得するGet-LastItem
ファンクション例
function Get-LastItem { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$true)] [string]$Sortby, [Parameter(Mandatory=$true)] [int]$Count ) begin{ } process{ Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -Last $Count } end{ } }
そう、これなら最初のアイテムを取得するGet-FirstItem
ファンクション例も簡単ですね。
function Get-LastItem { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$true)] [string]$Sortby, [Parameter(Mandatory=$true)] [int]$Count ) begin{ } process{ Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -Last $Count } end{ } }
例と同様の利用する時は以下のようにします。
Get-LastItem -Path C:\hoge\fuga -Sortby Name -Count 1
あるいは、取得するアイテム数を変えることもできます。
Get-LastItem -Path C:\hoge\fuga -Sortby Name -Count 3
あるいは-Sortbyを指定すれば、取得するアイテムソート対象をNameからCreationTimeやLastAccessTimeに変えることもできます。
Get-LastItem -Path C:\hoge\fuga -Sortby CreationTime -Count 1
Get-FirstItemファンクションは、Get-LastItemファンクションとは逆の動作ができますね。
Get-FirstItem -Path C:\hoge\fuga -Sortby Name -Count 1
1.txtが取れました。
ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 1.txt
まとめ
この例で示したかったのは、以下の3点です。
- Sort-Objectでのプロパティ指定は大事です。
- Sort-Objectの並び順制御とSelect-Objectでの制御には無駄が出ないように気を付けて
- 常にFunctionにすることを考えて書くといいかも
さぁ、あなたもオレオレFunctionを作ろう! (
補足
牟田口先生からの指摘をいただきました。
並べた時の最後の一つを取るのに、昇順で並べて最後の1個を取るのと、降順で並べて最初の1個を取るのって別に違いないような気がするなあ。意味的に素直なのは前者だけど、後者が冗長というほどでもないような。
— 牟田口大介さん (@mutaguchi) 2013年2月20日
それにPowerShell3.0でのパイプライン処理最適化を考えると、select -first 1のほうがselect -last 1より効率いいと思う。この例ではsortの段階でどうせ全processが実行されるので同じことかもしれないけど…
— 牟田口大介さん (@mutaguchi) 2013年2月20日
@guitarrapc なるほど複数なら結果が昇順になってるほうがいいかもしれませんね。
— 牟田口大介さん (@mutaguchi) 2013年2月20日
記事タイトルから少しずれるのですが、本記事の内容は出力結果が一つではなく複数になった時に意識するべきだと思います。 例えば以下のテスト関数を書きます。
function Get-ItemTest { [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 | Format-Table -AutoSize Get-ChildItem $Path | Sort-Object $SortBy -Descending | Select-Object -First $Count | Format-Table -AutoSize Get-ChildItem $Path | Sort-Object $SortBy | Select-Object -Last $Count | Format-Table -AutoSize Get-ChildItem $Path | Sort-Object $SortBy -Descending | Select-Object -Last $Count | Format-Table -AutoSize } end{ } }
実行してみましょう。
Get-ItemTest -Path C:\hoge\fuga -Sortby Name -Count 5
実行結果です。
PS C:\hoge\fuga> D:\Document\Program\Powershell\Get-FirstItem\Get-FirstItem.ps1 ディレクトリ: 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 ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 9.txt -a--- 2013/02/19 20:06 16 8.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 5.txt ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 5.txt -a--- 2013/02/19 20:06 16 6.txt -a--- 2013/02/19 20:06 16 7.txt -a--- 2013/02/19 20:06 16 8.txt -a--- 2013/02/19 20:06 16 9.txt ディレクトリ: C:\hoge\fuga Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2013/02/19 20:06 16 4.txt -a--- 2013/02/19 20:06 16 3.txt -a--- 2013/02/19 20:06 16 2.txt -a--- 2013/02/19 20:06 18 10.txt -a--- 2013/02/19 20:06 16 1.txt
どうでしょうか? これが、この記事の意義だと考えています。 次回は、以下のようなfunctionへの値の渡し方について考えてみます。
@{ path=(Get-Location).Path; key=((Get-ChildItem | Get-Member -MemberType Property).Name | ? {$_ -like "Na*"}); count=5} ` | %{ Get-FirstItem -Path $_.path -Sortby $_.key -Count $_.count }
こんな風にでます。
ディレクトリ: 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