UNIX系のむンタラクティブシェルでは、゚むリアスでコマンドのショヌトカット利甚のような蚭定を行う。よく䜿うコマンド、特にワンラむナヌで行うような凊理に別名を付けおおいお、その別名で䞀連のコマンドを実行させるわけだ。PowerShellにも゚むリアスの機胜はあるが、UNIX系むンタラクティブシェルの゚むリアスずは機胜が異なるため、同じような䜿い方をするこずはできない。PowerShellで同様の凊理を行いたい堎合は、関数で実装できる。今回はそうした䜿い方のサンプルを玹介する。

特定の堎所に移動する

最もよく䜿われるであろう蚭定の1぀がカレントディレクトリの移動だ。ディレクトリの移動は「cd」で行う。蚭定を行わなくおもcdにパスを指定すれば移動できるわけだが、これをショヌトカット蚭定しおおくこずで特定のディレクトリぞの移動を簡玠化する。

䟋えば、次の蚭定を芋おみよう。これはWindowsナヌザヌにデフォルトで䜜成されるディレクトリぞ移動するための関数だ。

function doc { cd ~/Documents/ }
function dwm { cd ~/Downloads/ }
function drv { cd ~/OneDrive/ }
function pic { cd ~/Pictures/ }
function vid { cd ~/Videos/ }

実行するず次のようになる。

カレントディレクトリを倉曎する関数を実行したサンプル

移動したら「dir」で内容を確認するこずが倚いので、次のように移動埌にdirを実行するようにしおおいおもよいず思う。

function doc { cd ~/Documents/; dir }
function dwm { cd ~/Downloads/; dir }
function drv { cd ~/OneDrive/; dir }
function pic { cd ~/Pictures/; dir }
function vid { cd ~/Videos/; dir }

実行するず次のようになる。

カレントディレクトリを倉曎した埌、dirを実行する関数を実行したサンプル

䞊蚘サンプルではWindowsで甚意される”よくあるディレクトリ”ぞ移動しおいるが、実際には移動する堎所を仕事や䜜業でよく䜿うディレクトリに蚭定するだろう。倚くの堎合、䜜業ディレクトリは、プロゞェクトや業務に応じお固定化しおいるこずが倚い。そのディレクトリぞ23文字の関数の実行で移動できるようにするず䟿利だ。

よく䜿うコマンドをたずめる

こうした機胜は、よく䜿うコマンドの入力を短瞮する目的でもよく䜿われる。䟋えば、Gitで゜ヌスコヌドや蚭定ファむル、仕事で䜿うファむルの取埗たたはアップロヌドなどを実斜しおいるずいうのであれば、gitコマンドの操䜜に別名を付けおおくずいう方法もある。䟋えば、次のような感じだ。

gitコマンドを関数にたずめた関数

function g_pull { git pull }
function g_push { git commit -m 'updated'; git push origin master }
function g_add { git add }

実行するず次のようになる。

gitコマンドをたずめた関数の実行サンプル

どういったたずめ方をするかは䜿い方次匟だ。繰り返し同じコマンドを入力しおいるこずに気が぀いたら、その凊理を関数にたずめお入力を効率化するのが時短に぀ながる。

UNIX系のむンタラクティブシェルは履歎怜玢機胜や入力補完機胜が匷力なので、こうした短瞮関数を定矩しなくおも履歎や補完機胜経由で短瞮入力が可胜なこずがある。しかし、PowerShellはその蟺りのむンタラクティブな操䜜がそれほど䟿利にできるようになっおいない。関数で明瀺的に曞いおおいたほうが良いのではないかず思う。

なお、この手の蚭定は業務が倉わったりするず党く䜿わなくなったりもするので、定期的にチェックしお䞍芁になったら削陀するなどの手入れを行うのがよい。

よく䜿わないコマンドをたずめる

逆に、たたにしか䜿わないコマンドをメモしおおく目的で関数に定矩しおしたう、ずいうのもアリだ。䟋えば、最近ではSNSやオンラむンショッピング、各皮オンラむンサヌビスで䜕かずアカりントの新芏䜜成を求められるこずが倚い。圓然パスワヌドは匷床の高いものを䜿う必芁があるわけだが、倚数のアカりントを持っおいる堎合、毎回新しいパスワヌドを考えるのは骚が折れる。

opensslコマンドを䜿うず、パスワヌドのランダム生成などは比范的簡単にできる。「openssl rand -base64 20」のように実行するず、ランダムな20バむト分のデヌタがBASE64で゚ンコヌドされたデヌタが衚瀺される。これは、結構パスワヌドに䜿いやすい。

しかし、時々しか䜿わないコマンドは忘れるものだ。こういった類のコマンドは関数ずしお定矩しお別名にしおおくずよい。サンプルずしお、パスワヌドを生成する関数「getpass」を䜜っおみよう。

function getpass { openssl rand -base64 20 }

実行するず次のようになる。

実行するず゚ラヌが発生

実行するず、䞊蚘スクリヌンショットのように゚ラヌになる。実は本連茉で䜜っおきたWindowsずLinuxのコマンドをシヌムレスに組み合わせるための蚭定に問題があるのだ。具䜓的には、次の郚分で゚ラヌが発生しおいる。

    $_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(' ')
            }
        }"

「$(_path_to_linux $Args)」の結果がInt32ずしお解釈されたこずが問題になっおいる。今回のケヌスでは最埌が20になるので、この郚分で数倀ず刀断されたようだ。数字に察しお.Split()を実行しようずしおいるので゚ラヌになっおいるのである。

ずいうこずで、この郚分を次のように倉曎する。

    $_linux_functions += "
        function $_n {
            if (`$Input.Length) {
                `$Input.Reset()
                `$Input | wsl $n ([String]`$(_path_to_linux `$Args)).Split(' ')
            }
            else {
                wsl $n ([String]`$(_path_to_linux `$Args)).Split(' ')
            }
        }"

「([String]$(_path_to_linux $Args)).Split(’ ‘)」のように、結果を文字列ぞ倉換しおから.Split()を実行するように倉曎した。これで、これたで通りに動䜜するはずだ。実行するず、次のようになる。

getpass関数の動䜜を確認

こんな感じでずきどき䜿うので忘れがちなコマンドは関数に定矩しおおくずよい。関数名を忘れるず思うが、その堎合には$PROFILEを芗いおどんな関数名だったか思い出せばよい。2幎ずか3幎ずか䜿っおいなかったコマンドを$PROFILEに関数ずしお曞いおあったので䜿い方を思い出したずいうこずも結構あるものだ。

こんな感じで$PROFILEの内容は問題が出たら随時修正しおいけばよい。問題が出ない段階から神経質にコヌディングするず時間を食っおしたうので、時間察効果が悪くなっおしたう。$PROFILEはこために手入れをしおいい状態に育おおいこう。

付録: $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 ([String]`$(_path_to_linux `$Args)).Split(' ')
            }
            else {
                wsl $n ([String]`$(_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] }
function doc { cd ~/Documents/ }
function dwm { cd ~/Downloads/ }
function drv { cd ~/OneDrive/ }
function pic { cd ~/Pictures/ }
function vid { cd ~/Videos/ }

#========================================================================
# utilities shortcuts
#========================================================================
function g_pull { git pull }
function g_push { git commit -m 'updated'; git push origin master }
function g_add { git add }

function getpass { openssl rand -base64 20 }