今回は、本連載でこれまで作ってきたPowerShellスクリプトにパイプラインの処理を加えてみよう。スクリプトはパイプラインで使えるようにすることで、より便利なものになる。サンプルで作っているスクリプトの場合にはあまり恩恵はないのだが、作り方の例としてご覧いただければと思う。

パイプラインに対応させる

前回の成果物「test-d_14.ps1」は次の通りだ。PowerShellスクリプトとして、かなりそれらしい体裁になってきた。「Get-Help」に対応したヘルプメッセージも追加されている。

#!/usr/bin/env pwsh

<#
.SYNOPSIS
ディレクトリパスが存在するかどうかを判定。

.DESCRIPTION
指定されたパスがディレクトリパスであり、かつ、存在するかどうかを判定する。

.PARAMETER DirectoryPath
ディレクトリパスを指定。

.PARAMETER Help
ヘルプメッセージを表示。

.INPUTS
なし。パイプには対応していない。

.OUTPUTS
System.Boolean。ディレクトリパスが存在する場合にTrue、そうでない場合にFalse。

.EXAMPLE
PS> .\test-d .\Documents\
True

.EXAMPLE
PS> .\test-d .\Documents-non-exists\
False

.LINK
Test-Path
#>

#====================================================================
# 引数
#====================================================================
Param(
    [string]$DirectoryPath,     # ディレクトリを指定
    [switch]$Help           # ヘルプを表示
)

if ($Help -Or -Not $DirectoryPath) {
    Get-Help $PSCommandPath
    exit 1
}

#====================================================================
# ディレクトリパスの存在確認
#====================================================================
if (Test-Path -PathType Container -Path $DirectoryPath) {
    # 指定されたパスはディレクトリであり、存在している。
    $True
} else {
    # 指定されたパスはディレクトリではない。
    if (Test-Path -PathType Leaf -Path $DirectoryPath) {
        # 指定されたパスはファイル。
        $infomsg = "$DirectoryPath : " `
            + "ファイルです。"
    } else {
        # 指定されたパスは存在しない。
        $infomsg = "$DirectoryPath : " `
            + "そのようなディレクトリは存在しません。"
    }
    Write-Host $infomsg
    $False
}

ヘルプメッセージにも書いてあるが、このスクリプトはパイプラインからの入力に対応していない。引数にディレクトリパスを指定して利用することを想定している。試しにパイプライン経由でディレクトリパスを渡すと、次のようにヘルプが表示される。これは引数が何も指定されていないため、動作としてヘルプを表示する状態になっているからだ。

test-d_14.ps1はパイプラインからの入力には対応していない

これまで連載で取り上げてきたように、PowerShellでは「$input」がパイプラインの入力にアクセスするものになる。「$input」を使ってパイプライン経由でのディレクトリパスも指定できるようにしてみよう。

パイプラインからディレクトリパスを取得して動作

先に今回の成果物「test-d_15.ps1」を掲載しておく。次のように書き換えを行った。

#!/usr/bin/env pwsh

<#
.SYNOPSIS
ディレクトリパスが存在するかどうかを判定。

.DESCRIPTION
指定されたパスがディレクトリパスであり、かつ、存在するかどうかを判定する。

.PARAMETER DirectoryPath
ディレクトリパスを指定。

.PARAMETER Help
ヘルプメッセージを表示。

.INPUTS
ディレクトリパス。引数でディレクトリパスが指定されていない場合に使われる。

.OUTPUTS
System.Boolean。ディレクトリパスが存在する場合にTrue、そうでない場合にFalse。

.EXAMPLE
PS> .\test-d .\Documents\
True

.EXAMPLE
PS> .\test-d .\Documents-non-exists\
False

.EXAMPLE
PS> dir | .\test-d
True

.LINK
Test-Path
#>

#====================================================================
# 引数
#====================================================================
Param(
    [string]$DirectoryPath,  # ディレクトリを指定
    [switch]$Help          # ヘルプを表示
)

if ($Help) {
    Get-Help $PSCommandPath
    exit 1
}
# 引数でディレクトリが指定されていない場合、パイプからディレクトリ
# パスの取得を試みる。
if (-Not $DirectoryPath) {
    $DirectoryPath = @($input)[0]

    if (-Not $DirectoryPath) {
        Get-Help $PSCommandPath
        exit 1
    }
}

#====================================================================
# ディレクトリパスの存在確認
#====================================================================
if (Test-Path -PathType Container -Path $DirectoryPath) {
    # 指定されたパスはディレクトリであり、存在している。
    $True
} else {
    # 指定されたパスはディレクトリではない。
    if (Test-Path -PathType Leaf -Path $DirectoryPath) {
        # 指定されたパスはファイル。
        $infomsg = "$DirectoryPath : " `
            + "ファイルです。"
    } else {
        # 指定されたパスは存在しない。
        $infomsg = "$DirectoryPath : " `
            + "そのようなディレクトリは存在しません。"
    }
    Write-Host $infomsg
    $False
}

書き換えたスクリプトの動作を見てみよう。標準入力から渡したデータの1つ目をパスとして取り出し、それがディレクトリパスであり、存在するかどうかを判定していることがわかる。

test-d_15.ps1の実行サンプル - パイプラインからディレクトリパスを読み取って動作している

スクリプトの挙動が変わったので、ヘルプメッセージも書き換えてある。次のようにして確認できる。

更新されたヘルプメッセージを確認

更新されたヘルプメッセージを確認

では、どのように書き換えたのかを見ていこう。前回のスクリプトでは、引数に-Helpパラメータが指定されていた場合か、ディレクトリパスが指定されていなかった場合に、次のようにヘルプメッセージを表示してプログラムを終了するように処理を組んでいた。

if ($Help -Or -Not $DirectoryPath) {
    Get-Help $PSCommandPath
    exit 1
}

今回はここを、「ディレクトリパスが指定されていない場合には、パイプラインからパスを読み込む」という処理に変更する。まず、-Helpパラメータが指定されていたときの動作を次のように変更する。

if ($Help) {
    Get-Help $PSCommandPath
    exit 1
}

次に、「引数にディレクトリパスが指定されていなかった場合に、パイプラインからディレクトリパスを取得して処理する」という処理を追加する。ここでは次のように追加した。

if (-Not $DirectoryPath) {
    $DirectoryPath = @($input)[0]

    if (-Not $DirectoryPath) {
        Get-Help $PSCommandPath
        exit 1
    }
}

パイプラインから何も入力が得られなかった場合には、これまでと同じようにヘルプメッセージを表示して処理を終了させている。

動作は書いてある通りだが、「@($input)[0]」という書き方がポイントだ。こう書くと「『$input』を配列として扱い、その1つ目の要素を持ってくる」という処理にすることができる。ここでは処理を簡素化したいので、このようにパイプラインの1つ目のデータのみを取り出している。

動作が変わっているのでヘルプも書き換えが必要だ。前回のスクリプトでは、パイプラインの入力データについては次のように説明を行っていた。

.INPUTS
なし。パイプには対応していない。

今回パイプラインからの入力に対応したので、この部分を次のように書き換える。

.INPUTS
ディレクトリパス。引数でディレクトリパスが指定されていない場合に使われる。

さらに、実行サンプルとして次のような動作も追加した。

.EXAMPLE
PS> dir | .\test-d
True

この書き換えで、今回目的としたディレクトリパスをパイプラインから持ってくる処理の完了だ。

パイプラインも使っていこう

これまでWindows系のコマンドばかり扱っていたのであれば、パイプラインでコマンドをつなげて処理するといったことはあまり行うことがなかったかもしれない。パイプラインはUNIX系のOSでよく使われる方法だ。慣れるととても便利なものなので、ぜひとも使えるようにしていただきたい。

ただし、UNIX系OSのパイプラインと、PowerShellのパイプラインは根本的に異なるメカニズムで動作するものである、ということは覚えておいたほうがよいと思う。UNIX系OSのパイプラインは動作が高速でマルチコアを有効に使えるが、PowerShellはあくまでも順番に処理するための方法にすぎない。PowerShellでパイプラインをたくさん使ったからとして、パフォーマンスが向上するわけではない点には注意が必要だ。