tech.guitarrapc.cóm

Technical updates

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

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

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

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

条件

簡単にエラーを出す方法にGet-ChildItemを使います。 Get-ChildItemコマンドレットは「対象ファイルが存在すれば標準出力」「対象ファイルが存在しなければ標準エラー出力」となります。

Bashに近くなるようにエイリアスのlsと今回は表記しましょう。

まず、D:\bcdフォルダを用意しました。 eee.txtはありません。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

テスト4とは、エラー出力と、標準出力の順序を変えています。 まずは、存在するD:\bcdです。

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

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

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

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

テスト4が、&2>と1>のどちらを先に判断しているのか見てみましょう。 まずは、存在するD:\bcdです。

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

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

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

まとめ

以上で、リダイレクト演算子の動作がちょっとでもイメージできれば幸いです。 以前指摘していますが、もう一度言います。

| Out-Null

はやめましょう。インアクションでは >$nullと同一と記載していますが、比較にならないオーバーヘッドが存在します。 最後に、今回のテストコードを掲載しておきます。

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に渡したパスの親フォルダが存在していれば開きます。