tech.guitarrapc.cóm

Technical updates

PowerShellでファイル名をソートして最後のアイテムを取得するなら

このような記事があったのですが……ふむ…。 少し冗長なのでコードを自分なりに修正すると…。

ファイル名でソートしたときに一番最後にくるファイルを開く 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 -Descendingselect -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点です。

  1. Sort-Objectでのプロパティ指定は大事です。
  2. Sort-Objectの並び順制御とSelect-Objectでの制御には無駄が出ないように気を付けて
  3. 常にFunctionにすることを考えて書くといいかも

さぁ、あなたもオレオレFunctionを作ろう! (

補足

牟田口先生からの指摘をいただきました。

記事タイトルから少しずれるのですが、本記事の内容は出力結果が一つではなく複数になった時に意識するべきだと思います。 例えば以下のテスト関数を書きます。

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