マイナビニュースマイナビ

MSYS2で行く - WSL2との違い「WSL2の親和性を引き上げる設定」

【連載】

にわか管理者のためのLinux運用入門

【第307回】MSYS2で行く - WSL2との違い「WSL2の親和性を引き上げる設定」

[2021/11/16 08:00]後藤大地 ブックマーク ブックマーク

WSL LinuxをWindowsとなじませる設定

Windowsとの親和性という面ではWSL2よりもMSYS2に軍配が上がる。Linuxを日常使いしているほど感じやすいのだが、ちょっとした部分でWSL2はイラッとすることがある。ただし、インタラクティブシェルとしてPowerShell 7を使っているのであれば、設定である程度WSL2の親和性を引き上げることができる。

前回はその方法として次の設定を紹介した。

#========================================================================
# Linux commands integration mode
#========================================================================
function linuxcmds {

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

    # Linux PATH and commands
    Write-Host "checking linux 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 global:$_n {
                if (`$Input.Length) {
                    `$Input.Reset()
                    `$Input | wsl $n ([String]`$(_path_to_linux 
                        `$Args)).Split(' ')
                }
                else {
                    wsl $n ([String]`$(_path_to_linux `$Args)).Split(' ')
                }
            }
            Write-Host -NoNewline .
        "
        Write-Host -NoNewline '_'
    }

    # Generate Linux pagers functions
    ForEach($_n in $_linux_pagers) {
        $_linux_functions += "
            function global:$_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(' ')
                }
            }
            Write-Host -NoNewline .
        "
        Write-Host -NoNewline '_'
    }

    # Function that converts Windows paths to Linux paths
    $_linux_functions += @'
        function global:_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
        }
        Write-Host .
'@
    Write-Host -NoNewline '_'

    # 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
    Write-Host
    Write-Host "functionizing linux commands:"
    . $_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_pagers
    Remove-Variable _linux_path
    Remove-Variable _linux_command_paths
    Remove-Variable _linux_functions

    #----------------------------------------------------------------
    # Individual Linux command function definitions
    #----------------------------------------------------------------
    # grep
    function global: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 global:ls { wsl ls --color=auto $(_path_to_linux 
        $Args).Split(' ') }
    function global:ll { ls -l $(_path_to_linux $Args).Split(' ') }
    function global:la { ls -a $(_path_to_linux $Args).Split(' ') }
}

具体的には、この設定を$PROFILEで設定されているパスのファイルへ書き込む。パスはWindows Terminalなどのターミナルアプリケーションで「echo $PROFILE」を実行すれば確認できるほか、「notepad $PROFILE」のようにして直接編集してもよい。

$PROFILEにこの設定を書き込んだら新しいPowerShel 7を起動し、「linuxcmds」を実行する。これでWSLのUbuntuに含まれているコマンドがwslコマンドの指定なしでもWindows側から実行できるようになる。毎回実行するのが面倒な場合は、$PROFILEの設定の後に、次の設定を追加しておけばよい。

linuxcmds

これで、WSLで動作するLinuxのコマンドをMSYS2のようにネイティブっぽいコマンドとしてWindowsからも利用できるようになる。

何を行っているのか

先ほどの「linuxcmds」を実行すると、次のようになる。

linuxcmdsの実行サンプル

この関数ではまず、WSL Linuxで定義されている環境変数PATHの値を取得し、そのパスに収められているコマンドの一覧を取得している。そして、そのコマンドと同じ名前の関数を文字列として作成し、作成した文字列状態の関数を一旦ファイルに書き出して、それを現在のセッションのPowerShellに読み込ませて本来の関数として機能させている。

また、WindowsのパスとWSL Linuxのパスは記述方法やトップディレクトリが異なるので、そのパスを自動変換する関数も作成し、先ほど作成したコマンド名の関数のなかで利用している。こうすることで、実際にはWSL Linuxのコマンドが実行されているのではなく、PowerShellで同名の関数が実行され、その中で「wsl コマンド」のようにしてWSL側のコマンドが実行される、という仕組みを実現している。ほかにも細々としたことを行っているが、大枠としてはこのようなところだ。

例えば、先ほどのスクリーンショットでは、「screenfetch」をPowerShell 7で実行している。そんなコマンドは存在しないので失敗しているわけだが、linuxcmdsを実行した後は、次のように「screenfetch」という処理が機能していることがわかる。

linuxcmds実行後はWSL Linuxのコマンドがwslなしで実行できている

上記のように、PowerShellで実行する「screenfetch」も「wsl screenfech」も同じ処理になっている。このようにラッパー関数というべきものをたくさん作ることで、あたかもWindowsでWSL Linuxのコマンドがネイティブに動作しているかのように振る舞わせている。

冒頭に掲載したlinuxcmdsでは、ほかにも動作しないページャーを動作する体にしている仕組みや、いわゆるLinuxにおけるエイリアスと同じような短縮名コマンドの実装などが行われている。PowerShell 7からWSLを活用する場合には参考にしていただきたい。

完璧ではないが必要十分

今回紹介した方法は完璧というわけではないのだが、WSLをWindowsからもネイティブ風に使いたいという場合にはそれなりに使える設定だと思う。WSLで動作するLinuxは、何といっても動作が高速だ。こういったギミックで十分利用に耐えるのであれば、かなり効果的な方法ではないかと思う。

また、ここで行っている方法はあくまでもPowerShell 7に限定した方法だ。Windowsでほかのシェルを使っているのであれば、そのシェルの書き方でここで説明したのと同じような方法を実現する必要がある。

※ 本記事は掲載時点の情報であり、最新のものとは異なる場合がございます。予めご了承ください。

一覧はこちら

連載目次

関連リンク

この記事に興味を持ったら"いいね!"を Click
Facebook で TECH+ の人気記事をお届けします
注目の特集/連載
[解説動画] Googleアナリティクス分析&活用講座 - Webサイト改善の正しい考え方
Slackで始める新しいオフィス様式
Google Workspaceをビジネスで活用する
人生を豊かにするパラレルキャリア~VUCA時代に新しい生き方を選ぶ理由とは~
ニューノーマル時代のオウンドメディア戦略
ミッションステートメント
次世代YouTubeクリエイターの成長戦略
IoTでできることを見つけるための発想トレーニング
教えてカナコさん! これならわかるAI入門
AWSではじめる機械学習 ~サービスを知り、実装を学ぶ~
Kubernetes入門
SAFeでつくる「DXに強い組織」~企業の課題を解決する13のアプローチ~
マイクロサービス時代に活きるフレームワーク Spring WebFlux入門
AWSで作るマイクロサービス
マイナビニュース スペシャルセミナー 講演レポート/当日講演資料 まとめ
セキュリティアワード特設ページ

一覧はこちら

今注目のIT用語の意味を事典でチェック!

一覧はこちら

会員登録(無料)

ページの先頭に戻る