tech.guitarrapc.cóm

Technical updates

PowerShellのリダイレクト演算子を纏めておく

リダイレクト演算子……PowerShellではあまり使ってません。 せいぜい$null破棄時の >$null でしょうか……

気になるアレがありました。

シェルコマンドの 2>&1 とはどういう意味でしょうか?

Bashは弱い子なので簡潔な説明にへぇっと納得してたのですが……PowerShellでの動作を確認してみましょう。 

 

条件

簡単にエラーを出す方法ということで Get-ChildItem (Alias = dir, ls)で試します。

Get-ChildItemコマンドレットでは、対象ファイルが存在すれば標準出力、対象ファイルが存在しなければ標準エラー出力となります。

Bashに近くなるように、Get-ChildItemは lsと今回は表記しましょう。

 

今回、D:\にはbcdフォルダを用意しました。 eee.txtはありません。 つまりこの状況です。

f:id:guitarrapc_tech:20190109035753p:plain



この状況下で、bcdでlsしたら標準出力、eee.txtで検索したら標準エラー出力させてリダイレクト演算子の動きを見ます。

テスト1. 標準出力、エラー出力とも出力ファイルを指定

まずは、存在する D:\bcd です。

# 標準出力はD:\test.txtに出力 エラーがd:\test2.txtに0KBで出力。
ls d:\bcd >d:\test.txt 2>D:\test2.txt

f:id:guitarrapc_tech:20140625164440p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力はD:\test.txtに0KBで出力 エラーがd:\test2.txtに出力。
ls d:\eee.txt >d:\test.txt 2>D:\test2.txt

f:id:guitarrapc_tech:20140625164452p:plain

 

テスト2. 標準出力に$null指定、エラー出力とも出力ファイルを指定

まずは、存在する D:\bcd です。

# 標準出力が$null破棄。エラーは存在しないので出ない(text2.txtファイルが0KBで生成)
ls d:\bcd >$null 2>D:\test2.txt

f:id:guitarrapc_tech:20140625164500p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力は$null破棄 エラーがd:\test2.txtに出力。
ls d:\eee.txt >$null 2>D:\test2.txt

f:id:guitarrapc_tech:20140625164509p:plain

 

テスト3. 標準出力にファイルを指定、エラー出力に2>&1を指定

まずは、存在する D:\bcd です。

# 標準出力がd:\test.txtに出力。エラーは存在しないので出ない(ファイルもない)
ls d:\bcd >d:\test.txt 2>&1

f:id:guitarrapc_tech:20140625164517p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力は存在しないので出ない。(ファイルもない) エラーがd:\test.txtに出力。
ls d:\eee.txt >d:\test.txt 2>&1

f:id:guitarrapc_tech:20140625164525p:plain

テスト4. 標準出力に$nullを指定、エラー出力に2>&1を指定

まずは、存在する D:\bcd です。

# 標準出力が$nullに出力。エラーは存在しないので出ない(ファイルもない)
ls d:\bcd >$null 2>&1

f:id:guitarrapc_tech:20140625164535p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力は存在しないので出ない。(ファイルもない) エラーは1にリダイレクト = $nullに破棄
ls d:\eee.txt >$null 2>&1

f:id:guitarrapc_tech:20140625164535p:plain

テスト5. エラー出力に2>&1を指定、標準出力に$nullを指定

テスト4とは、エラー出力と、標準出力の順序を変えています。

まずは、存在する D:\bcd です。

# 標準出力が$nullに出力。エラーは存在しないので出ない(ファイルもない)
ls d:\bcd 2>&1 1>$null

f:id:guitarrapc_tech:20140625164535p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力は存在しないので出ない。(ファイルもない) エラーは1にリダイレクト = $nullに破棄
ls d:\eee.txt 2>&1 1>$null

f:id:guitarrapc_tech:20140625164535p:plain

テスト6. エラー出力に2>&1を指定、標準出力にファイルを指定

テスト4が、&2>と1>のどちらを先に判断しているのか見てみましょう。

まずは、存在する D:\bcd です。

# 標準出力がd:\test.txtに出力。エラーは存在しないので出ない(ファイルもない)
ls d:\bcd 2>&1 1>D:\test.txt

f:id:guitarrapc_tech:20140625164702p:plain

次に、存在しない D:\eee.txt で試しましょう。

# 標準出力は存在しないので出ない。(ファイルもない) エラーは、1にリダイレクトされてD:\test.txtに出力
ls d:\eee.txt 2>&1 1>D:\test.txt

f:id:guitarrapc_tech:20140625164709p:plain

まとめ

以上で、リダイレクト演算子の動作がちょっとでもイメージできれば幸いです。

以前指摘していますが、もう一度言います。

| Out-Null

はやめましょう。インアクションでは >$null と同一と記載していますが、比較にならないオーバーヘッドが存在します。

最後に、今回のテストコードを掲載しておきます。 敢えて大袈裟にしてみました (( 本体よりparam()の方が長い

function Get-RedirectOperatorTest{

    [CmdletBinding(  
        SupportsShouldProcess = $false,
        ConfirmImpact = "none",
        DefaultParameterSetName = "do11"
    )]
    param(
        [Parameter(
        HelpMessage = "Input path of existing file",
        Position = 0,
        Mandatory = $false,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({Test-Path $_.FullName})]
        [IO.FileInfo[]]
        [string]$existFile,
        
        [Parameter(
        HelpMessage = "Input path of not existring file",
        Position = 0,
        Mandatory = $false,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({!(Test-Path $_.FullName)})]
        [IO.FileInfo[]]
        [string]$notexistFile,

        [Parameter(
        HelpMessage = "Input path of output file for write log success",
        Position = 0,
        Mandatory = $false,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [IO.FileInfo[]]
        [string]$outputSuccess,

        [Parameter(
        HelpMessage = "Input path of output file for write log failed",
        Position = 0,
        Mandatory = $false,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [IO.FileInfo[]]
        [string]$outputFail,

        [Parameter(ParameterSetName="do11")] 
        [switch]$do11,
        [Parameter(ParameterSetName="do12")] 
        [switch]$do12,
        [Parameter(ParameterSetName="do21")] 
        [switch]$do21,
        [Parameter(ParameterSetName="do22")] 
        [switch]$do22,
        [Parameter(ParameterSetName="do31")] 
        [switch]$do31,
        [Parameter(ParameterSetName="do32")] 
        [switch]$do32,
        [Parameter(ParameterSetName="do41")] 
        [switch]$do41,
        [Parameter(ParameterSetName="do42")] 
        [switch]$do42,
        [Parameter(ParameterSetName="do51")] 
        [switch]$do51,
        [Parameter(ParameterSetName="do52")] 
        [switch]$do52,
        [Parameter(ParameterSetName="do61")] 
        [switch]$do61,
        [Parameter(ParameterSetName="do62")] 
        [switch]$do62
    )

    switch ($true) {
        # 標準出力はD:\test.txtに出力 エラーがd:\test2.txtに0KBで出力。
        $do11 {
            "running test 1-1"
            ls $existFile >$outputSuccess 2>$outputFail
        }

        # 標準出力はD:\test.txtに0KBで出力 エラーがd:\test2.txtに出力。
        $do12 {
            "running test 1-2"
            ls $notexistFile >$outputSuccess 2>$outputFail
        }

        # 標準出力が$null破棄。エラーは存在しないので出ない(text2.txtファイルが0KBで生成)
        $do21 {
            "running test 2-1"
            ls $existFile >$null 2>$outputFail
        }
    
        # 標準出力は$null破棄 エラーがd:\test2.txtに出力。
        $do22 {
            "running test 2-1"
            ls $notexistFile >$null 2>$outputFail
        }

        # 標準出力がd:\test.txtに出力。エラーは存在しないので出ない(ファイルもない)
        $do31 {
            "running test 3-1"
            ls $existFile >$outputSuccess 2>&1
        }
    
        # 標準出力は存在しないので出ない。(ファイルもない) エラーがd:\test.txtに出力。
        $do32 {
            "running test 3-2"
            ls $notexistFile >$outputSuccess 2>&1
        }
    
        # 標準出力が$nullに出力。エラーは存在しないので出ない(ファイルもない)
        $do41 {
            "running test 4-1"
            ls $existFile >$null 2>&1
        }
    
        # 標準出力は存在しないので出ない。(ファイルもない) エラーは1にリダイレクト = $nullに破棄
        $do42 {
            "running test 4-2"
            ls $notexistFile >$null 2>&1
        }
    
        # 標準出力が$nullに出力。エラーは存在しないので出ない(ファイルもない)
        $do51 {
            "running test 5-1"
            ls $existFile 2>&1 1>$null
        }
    
        # 標準出力は存在しないので出ない。(ファイルもない) エラーは1にリダイレクト = $nullに破棄
        $do52 {
            "running test 5-2"
            ls $notexistFile 2>&1 1>$null
        }
    
        # 標準出力がd:\test.txtに出力。エラーは存在しないので出ない(ファイルもない)
        $do61 {
            "running test 6-1"
            ls $existFile 2>&1 1>$outputSuccess
        }
    
        # 標準出力は存在しないので出ない。(ファイルもない) エラーは、1にリダイレクトされてD:\test.txtに出力
        $do62 {
            "running test 6-2"
            ls $notexistFile 2>&1 1>$outputSuccess
        }
    }

    if (Test-Path (Split-Path $outputSuccess -Parent))
    {
        $openfolder = Split-Path $outputSuccess -Parent
        Invoke-Item $openfolder
    }
    else
    {
        $openfolder = $null
    }
}

利用したい時は、そのコードをスイッチ選択し、パスを渡してください。

例: 例1-1のテストを実行するとき

Get-RedirectOperatorTest -existFile d:\bcd -notexistFile d:\eee.txt -outputSuccess D:\test.txt -outputFail D:\test2.txt -do11

DefaultParameterSetName を利用しているので、switchは一つだけ選べるようにしてあります。*1

実行後に -outputSuccessに渡したパスの親フォルダが(存在していれば)開きます。

*1:-do11 を選ぶと -do12などは出なくなる