前回は、ディレクトリに移動する処理(cd)をショートカット的に行う方法を取り上げた。ディレクトリ名が日付の場合、その日付で最も新しいものに移動する処理を「cd1」、その1つ前の日付のディレクトリに移動する処理を「cd2」に設定する、といった具合だ。今回は、これをもっとシンプル化したものを紹介する。自分でカスタマイズする際の参考にしていただきたい。

前回作った設定は次の通りだ。

function cd1 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-1] }
function cd2 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-2] }
function cd3 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-3] }
function cd4 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-4] }
function cd5 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-5] }
function cd6 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-6] }
function cd7 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-7] }
function cd8 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-8] }
function cd9 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-9] }
function cd10 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-10] }
function cd11 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-11] }
function cd12 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-12] }
function cd13 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-13] }
function cd14 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-14] }
function cd15 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-15] }
function cd16 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-16] }
function cd17 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-17] }
function cd18 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-18] }
function cd19 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-19] }
function cd20 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-20] }

今回は、もっと汎用的に使用できるものを作成していこう。

まず、次の例を見ていただきたい。「dir(Get-ChildItemコマンドレット)」を実行すると、ファイルとディレクトリが表示される。このディレクトリに対して、cd1やcd2で移動できるようにするというのが今回の取り組みだ。

dirでファイルとディレクトリを表示

前回作成したcd1やcd2といった関数は「日付」という名前のパターンだけで対象を絞り込んでおり、それがファイルだった場合にも一致してしまう。cdで移動する対象は基本的にディレクトリだ。つまり、まずdirで取得するデータをディレクトリに絞り込むのがよいことになる。Get-ChildItemコマンドレットでは、-Directoryパラメータを指定することでデータをディレクトリに絞り込むことができる。

「dir -Directory」で対象をディレクトリに絞り込んでおく

ディレクトリだけを取得できれば、後は簡単だ。前回と同じ要領でディレクトリの一番下に表示されているものを取り出してみよう。

dirが表示するディレクトリのうち、一番下だけを取り出した場合

次のようにcdで移動できることを確認する。

cdで取り出したディレクトリへ移動できることを確認

上記の処理をまとめれば、今回目指していた設定が出来上がることになる。

今回の設定

上記を踏まえて今回作成する設定をまとめると次のようになる。

function cd1 { cd (dir -Directory)[-1] }
function cd1 { cd (dir -Directory)[-1] }
function cd2 { cd (dir -Directory)[-2] }
function cd3 { cd (dir -Directory)[-3] }
function cd4 { cd (dir -Directory)[-4] }
function cd5 { cd (dir -Directory)[-5] }
function cd6 { cd (dir -Directory)[-6] }
function cd7 { cd (dir -Directory)[-7] }
function cd8 { cd (dir -Directory)[-8] }
function cd9 { cd (dir -Directory)[-9] }
function cd10 { cd (dir -Directory)[-10] }
function cd11 { cd (dir -Directory)[-11] }
function cd12 { cd (dir -Directory)[-12] }
function cd13 { cd (dir -Directory)[-13] }
function cd14 { cd (dir -Directory)[-14] }
function cd15 { cd (dir -Directory)[-15] }
function cd16 { cd (dir -Directory)[-16] }
function cd17 { cd (dir -Directory)[-17] }
function cd18 { cd (dir -Directory)[-18] }
function cd19 { cd (dir -Directory)[-19] }
function cd20 { cd (dir -Directory)[-20] }

動作を確認してみよう。次のように下に表示されるディレクトリから順に移動できることを確認できるはずだ。

cd1やcd2の実行サンプル

今回の設定は、カスタマイズのベースとして使用できる。移動するディレクトリを絞り込む場合は、「dir -Directory」となっている部分を「dir -Directory | Select-String -Pattern ‘正規表現’」のように書き換えればよい。いろいろなパターンに応用できるはずだ。

また、ここではcd1で下に表示されるディレクトリへと移動しているが、最初に表示されるディレクトリへ移動するほうが便利というケースもあるだろう。その場合、cd1のインデックスを「[-1]」ではなく「[0]」に変えればよい。そこから順に「[1]」「[2]」「[3]」とインクリメントするだけだ。

別のやり方としては、Sortコマンドレットを使って次のようにディレクトリを逆順で得るようにしてもよい。

Sortコマンドレットで順序を逆にする場合のサンプル

ユーザーによって使いやすい設定は違うと思うので、自分のよく使うケースに合わせてこの関数を書き換えていただきたい。こうしたショートカット設定はうまくハマると作業効率を著しく向上させてくれる。ぜひとも自分の普段のユースケースを整理して、効果的な設定を探してほしい。

付録: $PROFILE

本連載時点での$PROFILEを次に掲載しておく。

$PROFILE

#========================================================================
# Set encoding to UTF-8
#========================================================================
# [System.Console]::OutputEncoding is set to local encoding, so character
# corruption occurs when piped from WSL to WSL. Therefore, set
# [System.Console]::OutputEncoding and $OutputEncoding to UTF-8 to avoid
# the problem.
$OutputEncoding = [System.Console]::OutputEncoding =
    [System.Text.UTF8Encoding]::new()

#========================================================================
# Definition of Linux commands used via wsl
#========================================================================
# Linux pagers
$_linux_pagers = @("less", "lv")

# Linux PATH and commands
$_linux_path = (wsl echo '$PATH').Split(":") -NotMatch "/mnt"
$_linux_command_paths = (
    wsl ls -d ($_linux_path[($_linux_path.Length - 1)..0] -replace "$","/*")
) 2> $null

# Generate Linux commands functions
ForEach($n in $_linux_command_paths) {
    $_n = (Split-Path -Leaf $n)
    $_linux_functions += "
        function $_n {
            if (`$Input.Length) {
                `$Input.Reset()
                `$Input | wsl $n `$(_path_to_linux `$Args).Split(' ')
            }
            else {
                wsl $n `$(_path_to_linux `$Args).Split(' ')
            }
        }"
}

# Generate Linux pagers functions
ForEach($_n in $_linux_pagers) {
    $_linux_functions += "
        function $_n {
            if (`$Input.Length) {
                `$Input.Reset();

                # Prepare temporary file path
                `$_temp = New-TemporaryFile

                # Write data from pipeline to the temporary file
                `$Input | Out-File `$_temp

                # Do $_n
                wsl $_n `$(_path_to_linux `$Args).Split(' ') ``
                    `$(_path_to_linux `$_temp.ToString()).Split(' ')

                # Delete unnecessary temporary file and variable
                Remove-Item `$_temp
                Remove-Variable _temp
            }
            else {
                wsl $_n `$(_path_to_linux `$Args).Split(' ')
            }
        }"
}

# Function that converts Windows paths to Linux paths
$_linux_functions += @'
    function _path_to_linux {
        $linuxpath = @()

        # Convert arguments to Linux path style
        ForEach($winpath in $Args) {
            if ($winpath -eq $null) {
                Break
            }

            # Change drive path to mount path
            if ($winpath -match '^[A-Z]:') {
                $drive = $winpath.Substring(0,1).ToLower()
                $linuxpath += "/mnt/" + $drive + $winpath.Substring(2).Replace('\','/')
            }
            # Option is not converted
            elseif ($winpath -match '^[-+]') {
                $linuxpath += $winpath
            }
            # Other argument is converted
            else {
                $linuxpath += ([String]$winpath).Replace('\','/')
            }
        }

        $linuxpath
    }
'@

# Prepare temporary file path with extension .ps1
$_temp = New-TemporaryFile
$_temp_ps1 = $_temp.FullName + ".ps1"
Remove-Item $_temp

# Write function definition to temporary .ps1 file and parse
$_linux_functions | Out-File $_temp_ps1
. $_temp_ps1
Remove-Item $_temp_ps1

# Delete unnecessary variables
Remove-Variable n
Remove-Variable _n
Remove-Variable _temp
Remove-Variable _temp_ps1
Remove-Variable _linux_path
Remove-Variable _linux_command_paths
Remove-Variable _linux_functions

#========================================================================
# Individual Linux command function definitions
#========================================================================
# grep
function grep {
    $pattern_exists = $False
    $path_exists = $False
    $skip = $False
    $i = 0

    ForEach($a in $Args) {
        if ($skip) {
            $skip = $False
            $i++
            continue
        }

        # Options without argumetn
        if ($a -cmatch '^-[abcdDEFGHhIiJLlmnOopqRSsUVvwxZ]') {
        }
        # Options with argument
        elseif ($a -cmatch '^-[ABC]') {
            $skip = $True
        }
        # Pattern file specification option
        elseif ($a -ceq '-f') {
            $skip = $True
            $pattern_exists = $True
            $Args[$i+1] = _path_to_linux $Args[$i+1]
        }
        # Pattern specification option
        elseif ($a -ceq '-e') {
            $skip = $True
            $pattern_exists = $True
        }
        # Pattern or file path
        elseif ($a -cnotmatch '^-') {
            if ($pattern_exists) {
                $path_exists = $True
            }
            else {
                $pattern_exists = $True
            }
        }

        $i++
    }

    # Change file path
    if ($path_exists) {
        $Args[-1] = _path_to_linux $Args[-1]
    }

    $Input | wsl grep $Args
}

# ls
Get-Alias ls *> $null && Remove-Item alias:ls
function ls { wsl ls --color=auto $(_path_to_linux $Args).Split(' ') }
function ll { ls -l $(_path_to_linux $Args).Split(' ') }
function la { ls -a $(_path_to_linux $Args).Split(' ') }

#========================================================================
# Alias definition
#========================================================================
Set-Alias -Name open -Value explorer
Set-Alias -Name edge -Value "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
Set-Alias -Name chrome -Value "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"

Get-Alias man *> $null && Remove-Item alias:man

#========================================================================
# cd shortcuts
#========================================================================
function cd1 { cd (dir -Directory)[-1] }
function cd2 { cd (dir -Directory)[-2] }
function cd3 { cd (dir -Directory)[-3] }
function cd4 { cd (dir -Directory)[-4] }
function cd5 { cd (dir -Directory)[-5] }
function cd6 { cd (dir -Directory)[-6] }
function cd7 { cd (dir -Directory)[-7] }
function cd8 { cd (dir -Directory)[-8] }
function cd9 { cd (dir -Directory)[-9] }
function cd10 { cd (dir -Directory)[-10] }
function cd11 { cd (dir -Directory)[-11] }
function cd12 { cd (dir -Directory)[-12] }
function cd13 { cd (dir -Directory)[-13] }
function cd14 { cd (dir -Directory)[-14] }
function cd15 { cd (dir -Directory)[-15] }
function cd16 { cd (dir -Directory)[-16] }
function cd17 { cd (dir -Directory)[-17] }
function cd18 { cd (dir -Directory)[-18] }
function cd19 { cd (dir -Directory)[-19] }
function cd20 { cd (dir -Directory)[-20] }
#function cd1 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-1] }
#function cd2 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-2] }
#function cd3 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-3] }
#function cd4 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-4] }
#function cd5 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-5] }
#function cd6 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-6] }
#function cd7 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-7] }
#function cd8 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-8] }
#function cd9 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-9] }
#function cd10 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-10] }
#function cd11 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-11] }
#function cd12 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-12] }
#function cd13 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-13] }
#function cd14 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-14] }
#function cd15 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-15] }
#function cd16 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-16] }
#function cd17 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-17] }
#function cd18 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-18] }
#function cd19 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-19] }
#function cd20 { cd (dir | Select-String -Pattern '[0-9]{8}$')[-20] }