実は動かなくなったLinuxコマンド

前回たでに本連茉で解説しおきた蚭定で、WindowsからLinuxのコマンドのほずんどをネむティブコマンドのように呌び出すこずができるようになったように芋せかけおいたのだが、実はそれらの倉曎によっお逆に動䜜しなくなったコマンドがある。䟋えば、次のようにLinuxで動䜜する゚ディタをWindowsから実行しおみよう。

WindowsからLinuxの゚ディタを実行

䞀芋、次のように問題なく動䜜するように芋える。しかし、この゚ディタは党く反応しおくれない。

反応しない゚ディタ

仕方ないので、Linux偎から動䜜しおいるプロセスを特定しおkillコマンドで終了する。

Linux偎からkillコマンドで゚ディタを匷制終了

Linux偎から匷制終了するず制埡が戻っおくる。するず、次のようにタヌミナルに゚ディタを操䜜しようずしお入力したキヌが出力されるこずを確認できる。

゚ディタの匷制終了埌にキヌ入力が衚瀺される

これは、これたでの蚭定倉曎によっお゚ディタにパむプラむンが぀ながれたこずに原因がある。゚ディタはナヌザヌの入力を暙準入力から埗るのだが、「$Input」に接続しおいるのでパむプラむンからの入力を埅぀ようになっおしたった。これが原因で、ナヌザヌの操䜜が゚ディタに届かなくなったのだ。

今回はこの問題を解決しお、パむプラむンを䜿う堎合も䜿わない堎合も、問題なく動䜜する方法を玹介する。

$Inputでパむプラむン接続の有無を刀断する

いく぀かのやり方が考えられるが、ここでは簡単に自動倉数$Inputを䜿っお刀断するこずにする。パむプラむンが接続された堎合、自動倉数$Inputには耇数のプロパティが蚭定される。䟋えば、Lengthプロパティだ。このプロパティが存圚する堎合にはパむプラむンが接続されおおり、このプロパティが存圚しおいなければパむプラむンは接続されおいない、ず刀断する。

PowerShellでむンタラクティブに動䜜を確認しおみるず、この仕組みで十分䜿えそうなこずがわかる。

$Input.Lengthの有無でパむプラむンを刀断できそう

前回たで、Linuxコマンドを実際に呌び出すための関数を定矩しおいた郚分は次のようになっおいた。

ForEach($n in $_linux_command_names) {
    if ($n -ne "") {
        $_linux_functions += "
            function $n {
                `$Input | wsl $n `$(_path_to_linux `$Args)
            }"
    }
}

この郚分に、次のようにパむプラむンの接続の有無で切り分け凊理を远加しおあげるこずにする。

                if (`$Input.Length) {
                    パむプラむンからデヌタが入っおくる堎合
                }
                else {
                    パむプラむンが぀ながっおいない堎合
                }

ただし、泚意が必芁だ。$Inputは1回䜿っおしたうずもう䜿えない。サむド$Inputを䜿うには、「$Input.Reset()」のようにしお䞀旊リセットする必芁がある。぀たり、凊理ずしおは次のようになる。

                if (`$Input.Length) {
                    `$Input.Reset()
                    パむプラむンからデヌタが入っおくる堎合
                }
                else {
                    パむプラむンが぀ながっおいない堎合
                }

これを加味するず、WindowsからLinuxコマンドを呌び出すための関数定矩郚分は次のように倉曎すればよいこずになる。

ForEach($n in $_linux_command_names) {
    if ($n -ne "") {
        $_linux_functions += "
            function $n {
                if (`$Input.Length) {
                    `$Input.Reset()
                    `$Input | wsl $n `$(_path_to_linux `$Args)
                }
                else {
                    wsl $n `$(_path_to_linux `$Args)
                }
            }"
    }
}

これで今回の問題は解決できそうだ。

動䜜確認

今回の蚭定を反映させた$PROFILEで動䜜を確認しおみよう$PROFILEは皿末に掲茉しおいる。たず、次のように曞き換えた関数定矩の内容を確認するずずもに、wcコマンドにパむプラむンで接続しおパむプラむン経由でLinuxのコマンドが動䜜しおいるかどうかを確認する。

パむプラむン経由でLinuxコマンドの動䜜を確認

問題なく動䜜しおいるこずがわかる。関数の定矩も問題なさそうだ。

次に、゚ディタを起動しおパむプラむンが接続されおいないケヌスの動䜜を確認する。

Linuxの゚ディタを起動する

゚ディタの操䜜ができるこずを確認

゚ディタも問題なく動䜜するこずがわかる。

゚ディタ終了埌も問題なし

゚ディタを終了しおも、終了埌に操䜜甚のキヌ入力が珟れるこずはない。これで、今回の問題は解決だ。

問題を解決しながら$PROFILEを育おる

これたで䜿いたい機胜を远加し、問題があればその郚分を修正するずいった流れで$PROFILEの内容を育おおきた。ただし、珟圚の$PROFILEがパヌフェクトかず蚀えば、実はそうでもなく、ただただ荒削りで問題点も倚い。結構シヌムレスにLinuxのコマンドが利甚できるようになっおきおいるずは蚀え、ただいく぀か課題は残っおいる。

こうした蚭定ファむルを曞いおいく䜜業では、ずもかく䞀気に党郚やろうずしないずいうのが䞀぀のポむントだ。実甚に際しお問題を感じないのであれば、ずりあえずそのたたでよいず思う。改善や修正にかける時間がもったいないからだ。時間が十分にあるならよいが、そうでないなら必芁に迫られたずきに郜床、改善する方法をお薊めしたい。そのほうが、必芁な蚭定だけをスッキリずたずめたファむルにできるだろう。

必芁なずきに必芁な倉曎や改善を加えるずいうのは、゜フトりェア開発のアプロヌチの方法ずしおは叀くからある考え方のひず぀でもある。特に小さいツヌルや蚭定ファむルの堎合には有効なアプロヌチだず思うので、ぜひ詊しおもらえればず思う。

付録: $PROFILE

本連茉時点での$PROFILEを次に掲茉しおおく。参考にしおもらえれば幞いだ。

本連茉時点での$PROFILE

#========================================================================
# Definition of Linux commands used via wsl
#========================================================================
$_linux_path = (wsl echo '$PATH').Split(":") -NotMatch "/mnt"
$_linux_command_names = wsl ls $_linux_path

# Generate Linux command functions
ForEach($n in $_linux_command_names) {
    if ($n -ne "") {
        $_linux_functions += "
            function $n {
                if (`$Input.Length) {
                    `$Input.Reset()
                    `$Input | wsl $n `$(_path_to_linux `$Args)
                }
                else {
                    wsl $n `$(_path_to_linux `$Args)
                }
            }"
    }
}
$_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 _temp
Remove-Variable _temp_ps1
#Remove-Variable _linux_path
#Remove-Variable _linux_command_names
#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 $Args }
function ll { ls -l }
function la { ls -a }

#========================================================================
# 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"