PowerShell 7をインタラクティブシェルとして使う - コマンドパスもLinuxから自動設定

【連載】

PowerShell Core入門 - 基本コマンドの使い方

【第114回】PowerShell 7をインタラクティブシェルとして使う - コマンドパスもLinuxから自動設定

[2020/09/04 09:00]後藤大地 ブックマーク ブックマーク

前々回の設定で、Linuxのディレクトリパスを配列に設定し、そのディレクトリパスにデプロイされているコマンドをWindowsのネイティブコマンドのように呼び出せるようにした。そのときに設定したディレクトリパスは次の通りだ。

$_linux_path = @('/usr/local/sbin', '/usr/local/bin', '/usr/sbin', 
                 '/usr/bin', '/sbin', '/bin')

Linuxでは、環境変数PATHにコロン区切りでコマンドがデプロイされているディレクトリパスを設定する慣例になっている。ということは、上記配列のように手動で設定しなくても、Linuxの環境変数PATHの内容から自動的に上記ディレクトリパスの配列を設定すればよいのではないか、という発想にも行き着く。将来に渡ってさらに楽をしたいという建設的な改善だ。今回はこの部分の書き換えをやってみよう。

環境変数PATHから配列を作る

PowerShellからは、次のようにwslコマンドを介してLinuxのPATH環境変数の内容を取得することができる。

PS C:\Users\daich> wsl echo '$PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Program Files/PowerShell/7:/mnt/c/WINDOWS/system32:/mnt/c/WINDOWS:/mnt/c/WINDOWS/System32/Wbem:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/mnt/c/Program Files/PowerShell/6/:/mnt/c/Program Files/PowerShell/7/:/mnt/c/Program Files/PowerShell/7-preview/preview:/mnt/c/Users/daich/.cargo/bin:/mnt/c/Users/daich/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/daich/AppData/Local/Programs/Microsoft VS Code/bin
PS C:\Users\daich>

PowerShellでは「.Split(“文字”)」で指定した文字で文字列を分割することができる。Linuxの環境変数PATHは「:(コロン)」で区切られているので、コロンを指定すれば次のように環境変数PATHの内容をそれぞれのディレクトリパスへ分解することができる。

PS C:\Users\daich> (wsl echo '$PATH').Split(":")
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/mnt/c/Program Files/PowerShell/7
/mnt/c/WINDOWS/system32
/mnt/c/WINDOWS
/mnt/c/WINDOWS/System32/Wbem
/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/
/mnt/c/Program Files/PowerShell/6/
/mnt/c/Program Files/PowerShell/7/
/mnt/c/Program Files/PowerShell/7-preview/preview
/mnt/c/Users/daich/.cargo/bin
/mnt/c/Users/daich/AppData/Local/Microsoft/WindowsApps
/mnt/c/Users/daich/AppData/Local/Programs/Microsoft VS Code/bin
PS C:\Users\daich>

分割してみるとわかるが、WSLで動作するLinux環境では/mnt/c/以下もコマンドパスに含まれている。Linux環境からWindows側のコマンドを実行したりアプリケーションを起動したりできるようにするためだ。しかし、ここではPowerShell側からWSLで動作するLinuxのコマンドを実行できるようにすることが目的なので、/mnt/c/以下のパスは必要ない。

そこで、「-NotMatch(“/mnt”)」を使って不要なディレクトリパスを次のように対象から外す。配列に収めるのはこれで十分だろう。

PS C:\Users\daich> (wsl echo '$PATH').Split(":") -NotMatch "/mnt"
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
PS C:\Users\daich>

ここまでの説明をまとめよう。以下に示すのが、元の設定だ。

$_linux_path = @('/usr/local/sbin', '/usr/local/bin', '/usr/sbin', 
                 '/usr/bin', '/sbin', '/bin')

上記を、次のように書き換えればよいことになる。

$_linux_path = (wsl echo '$PATH').Split(":") -NotMatch "/mnt"

これでLinux環境の環境変数PATHに設定されているディレクトリパス以下のコマンドが、PowerShellから利用できるようになる。Linux側でいろいろと作業して環境変数PATHの書き換えが行われたとしても、その変更は自動的にPowerShell側にも反映される(当然$PROFILEの読み直しか、PowerShellの再起動は必要だ)。

今回の設定の使用例

細かい話のように思うかもしれないが、こうした部分がシームレスに連携してくれると思った以上に楽だ。Linux側を設定して、PowerShell側を設定して、といったように同じことを繰り返すのは意外とストレスが溜まる。今回の設定のように、少しずつ、建設的に手を抜ける状況を整えていくことが大切だ。

付録: $PROFILE

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

#========================================================================
# Linux commands definition 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 {
                `$Input | 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",

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

一覧はこちら

連載目次

もっと知りたい!こちらもオススメ

【連載】RPA入門 - ツールで学ぶ活用シーン

【連載】RPA入門 - ツールで学ぶ活用シーン

AIには、ルールベース、機械学習、深層学習(ディープラーニング)の3つのレベルがあり、レベルが上がるに連れてより高度な人工知能を実現しますが、AIのスピンオフという位置付けで、Digital Labor(仮想知的労働者)によるホワイトカラー業務の自動化を実現するRPAが注目されています。

関連リンク

この記事に興味を持ったら"いいね!"を Click
Facebook で IT Search+ の人気記事をお届けします
注目の特集/連載
[解説動画] Googleアナリティクス分析&活用講座 - Webサイト改善の正しい考え方
[解説動画] 個人の業務効率化術 - 短時間集中はこうして作る
ミッションステートメント
教えてカナコさん! これならわかるAI入門
AWSではじめる機械学習 ~サービスを知り、実装を学ぶ~
対話システムをつくろう! Python超入門
Kubernetes入門
SAFeでつくる「DXに強い組織」~企業の課題を解決する13のアプローチ~
PowerShell Core入門
AWSで作るマイクロサービス
マイナビニュース スペシャルセミナー 講演レポート/当日講演資料 まとめ
セキュリティアワード特設ページ

一覧はこちら

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

一覧はこちら

会員登録(無料)

ページの先頭に戻る