関数でインタラクティブ性を向上させる

Linux系のインタラクティブシェルには「エイリアス」と呼ばれる機能が用意されていることが多く、任意の文字列を任意のコマンドや関数に置き換えて実行することができる。このエイリアスは単純な文字列の置き換えになっていることが多く、かなり柔軟性が高い。

PowerShellにもエイリアスと呼ばれる機能が用意されているのだが、PowerShellのエイリアスは本当にコマンドレット名や関数名、コマンド名の部分だけを別の名前に置き換えるというかなり限定された機能で、Linux系インタラクティブシェルのようには利用することができない。

しかし、PowerShellでも、関数を使えばLinux系インタラクティブシェルと似たようなことは実現できる。前回はその例として関数を使った方法を紹介した。今回はもう一歩踏み込んだ使い方を取り上げよう。この辺りをマスターすることで、インタラクティブシェルとしてのPowerShellの使い勝手がかなり良くなってくるので、ぜひ自分の作業に取り入れていってもらいたい。

WindowsのパスをLinuxのパスに変換する

Microsoft StoreからLinuxディストリビューションをWindows 10に導入できるようになったことで、Windows 10側からLinuxコマンドを簡単に利用できるようになった。これは想像以上に便利なことで、もっとネイティブっぽくWindows 10側からLinuxのコマンドを利用したいという欲求が出てくる。

Linux側のコマンドはwslコマンドの引数として指定すればWindows 10側から実行することができる。前回はこれを関数を使って実現する方法を取り上げたわけだが、コマンドの引数にパスを渡すとなると勝手が違ってくる。WindowsとLinuxではパスの書き方が違うので、そのまま渡しただけではLinux側のコマンドがパスを処理できないのだ。

そこで、いったんWindowsのパスをLinuxのパスに変更して実行するようにする。将来的に、この辺りは自動的に処理されるようになると思うのだが、それまでは手動で対処する必要があるだろう。

基本的に、エイリアスは「自分が便利に使えればよい」というものなので、包括的な実装を行う必要はない。普段利用する範囲内で問題なく動作する程度の網羅感で実装すればよいだろう。例として、以下に「path_to_linux」というユーザー定義関数を掲載しておく。引数にWindowsのパスを与えると、それをLinuxのパスに変更するというものだ。

function path_to_linux {
    $winpath = $Args[0]

    if ($winpath -ne $null) {
        if ($winpath -match '^C:') {
            $linuxpath = "/mnt/c" + $winpath.Replace('C:','').Replace('\','/')
        }
        else {
            $linuxpath = $winpath.Replace('\','/')
        }
    }

    $linuxpath
}

この関数は次のような仕様となっている。

  • 引数は1つ。Windowsのパスを指定する
  • パスの先頭が「C:」で始まっていた場合には、その部分を「/mnt/c」に置き換える
  • パスの区切り文字であるバックスラッシュをスラッシュへ置き換える
  • 引数がない($null)場合には「$null」を返す


関数の実装そのものについては、特に説明の必要はないだろう。これまでの本連載の内容をこなしているレベルであれば、問題なく理解できるはずだ。上記のコードを関数として「$PROFILE」に書いておけば、次のような感じでPowerShellから実行できる。

path_to_linuxを直接実行しているところ

ユーザーが直接この関数を実行するのではなく、ほかの関数が内部のツールとしてこの関数を利用することになる。

treeコマンド - 引数がパス1つ

最も簡単な例として、コマンドの引数に1つのパスが指定される例を取り上げてみよう。次の関数はLinux側のtreeコマンドを呼び出すものだ。Linuxのtreeコマンドはかなり強力で便利だ。ディレクトリとファイルの配置状況を把握するのに使用できる。構造を把握したいディレクトリパスを引数として指定する。この場合、次のようにtree関数を作成すればよい。

function tree {
    wsl tree $(path_to_linux $Args[0])
}

実行結果は以下の通りだ。

tree関数の実行サンプル

Windows側のパスがLinux側で使用できるパスに変換され実行されていることがわかる。WSLで動作しているLinuxからWindows側のファイルシステムは/mnt/c以下にマウントされたファイルシステムのように見えるので、ここに対してパス変換をすれば使用できるわけだ。wslを経由した段階でカレントディレクトリや相対パスも機能しているので、上記のように問題なく動作する。

grepコマンド - 引数の最後がパス

コマンドは当然単一の引数をとるものだけではない。さらにパスの位置が必ず一番最初にあるとも限らない。よく使われるコマンドの一つにgrepコマンドがあるが、このコマンドに直接パスを指定する場合には引数の一番最後がパスになる。典型的には1つ目の引数がパターン、2つ目の引数がパスとなる。

こういったケースであれば、例えば次のように関数を作成する。path_to_linuxで処理する対象を一番後ろの引数に対してのみ実行し、変換後の引数をまるごとgrepコマンドに渡すわけだ。なお、PowerShellの配列はインデックスを「-1」にすると後ろからのインデックスとなる。

function grep {
    $Args[-1] = path_to_linux $Args[-1]

    $Input | wsl grep $Args
}

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

grep関数の実行サンプル

パイプラインから流れてくるデータに対しても、引数にファイルパスを指定したケースでも動作していることがわかる。

grep関数の実装は結構適当だ。引数にパターンしか指定されていない場合には、パターンに対してpath_to_linuxが処理されてしまう。これだと問題になるケースもあるだろう。パターンに指定する内容がパスと被るようになったら、この関数にちょっと手を加えて条件分けをするようにする。最初から完璧なものを仕上げようとするのではなく、シンプルにつくって後から変更できるようにしておき、必要に応じてささっと書き換えていく方法がお薦めだ。

nvimコマンド - 引数が複数のパス

Linuxで人気の高いエディタの一つに「Vim」がある。プラグインをよく使う場合には「Nvim」のほうが好まれるかもしれない。Windows 10でもVimやNvimが使えるというのであれば、使いたいと考えるのは当然だろう。

こうしたエディタでは引数として複数のファイルパスを取ることもある。まとめて編集できて便利だし、ソフトウエアの開発となると複数のファイルを同時に開いて作業するというのは日常茶飯事だ。逆に言えば、複数パス指定でエディタが起動できないと不便ですらある。

先ほどのpath_to_linuxはパスを1つしか変換できなかったので、これを拡張して複数のパスを一気に変換できるようにしてみよう。実装はそれほど難しくない。配列を作成して、そこに変換後のパスを順次追加していくだけだ。

function path_to_linux {
    $linuxpath = @()

    ForEach($winpath in $Args) {
        if ($winpath -ne $null) {
            if ($winpath -match '^C:') {
                $linuxpath += "/mnt/c" + $winpath.Replace('C:','').Replace('\','/')
            }
            else {
                $linuxpath += $winpath.Replace('\','/')
            }
        }
    }

    $linuxpath
}

拡張したpath_to_linux関数だけを使ってみると、次のように複数のパスの変換を確認できる。

複数のパス変換に対応したpath_to_linux関数の使用サンプル

それでは、この関数を使用するように関数を作成してみよう。以下に示すのは、Windows 10からLinuxのNvimを実行するnvim関数だ。

function nvim {
    wsl nvim $(path_to_linux $Args)
}

実行する際は、次のように複数のファイルを指定しする。

複数のファイルを指定してnvim関数を実行

そうすると次のようになる。このNvimはWindows 10 version 2004のWSL2で動作しているUbuntu 20.04 LTSのものだ。いくつかのプラグインをインストールしており、上部に現在開いているファイルがタブのように表示されている。

同時に複数のファイルをオープンしたNvim

バッファ一覧を表示させても、次のように同時に編集状態にあることがわかる。

バッファ一覧でも同時に編集していることを確認できる

先ほど取り上げたtreeコマンドだが、実はこのコマンドも複数のパスを引数に取ることができる。次のように書き換えるとtree関数で複数のパス指定に対応できるようになる。

function tree {
    wsl tree $(path_to_linux $Args)
}

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

複数のパス指定に対応したtree関数

tree関数が複数パスの指定に対して動作していることがおわかりいただけるだろう。

今年の秋に公開予定の「Windows 10, version 20H2」や、2021年5月に公開されると見られる「Windows 10, version 21H1」では、wslコマンドが拡張され、今回紹介したような変換は自動的に行われるようになることが予想される。PowerShell側を拡張してwslをかまさなくても、自動的にLinuxコマンドが実行できるようになる可能性もある。いずれは全て自動で解決されるようになる見込みだが、それまではここで取り上げたような方法を使うことで利便性を向上できるだろう。

関数のちょっとした使い方ではあるのだが、意外に使い勝手に影響するカスタマイズでもある。普段、LinuxやmacOSでターミナルをよく利用している方にとって、PowerShellのコマンド体系はやや覚えにくい。機能的にもLinux系コマンドは優秀なので、ある程度はLinux側のコマンドが使えたほうが便利だ。やってみると手放せなくなる便利さなので、ぜひ活用していただきたい。

今回使用した「$PROFILE」

function path_to_linux {
    $linuxpath = @()

    ForEach($winpath in $Args) {
        if ($winpath -ne $null) {
            if ($winpath -match '^C:') {
                $linuxpath += "/mnt/c" + $winpath.Replace('C:','').Replace('\','/')
            }
            else {
                $linuxpath += $winpath.Replace('\','/')
            }
        }
    }

    $linuxpath
}

function tree {
    wsl tree $(path_to_linux $Args)
}
function grep {
    $Args[-1] = path_to_linux $Args[-1]

    $Input | wsl grep $Args
}
function nvim {
    wsl nvim $(path_to_linux $Args)
}

Set-Alias -Name less -Value more
Set-Alias -Name open -Value explorer

function ll {   Get-ChildItem -Force    }
function la {   Get-ChildItem -Force    }

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"