前々回の設定で、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",