PowerShell 7をインタラクティブシェルとして使う - Linuxのlsを使うように変更

【連載】

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

【第113回】PowerShell 7をインタラクティブシェルとして使う - Linuxのlsを使うように変更

[2020/08/28 08:00]後藤大地 ブックマーク ブックマーク

新しい設定に移る前に、前回の処理をもう少し改善しよう。前回、Linuxコマンドの大半をそのままWindows 10から実行できるようにしたわけだが、実はそのアプローチには少し問題がある。例えば、次のように定義したLinuxコマンド対応関数の数を調べようとしてみよう。期待としては、wcコマンドによる行数が表示されてほしいところなのだが、実際にはそうはならない。

wcコマンドが行数を表示してくれない

「Ctrl」+「C」で停止すると戻ってくる

パイプラインで「wc -l」に接続しているので、標準出力の行数がカウントされて行数が出力されることを期待するのだが、実際には上記スクリーンショットのようにwcコマンドは停止しているように見える。

これは前回定義した関数がパイプラインを考慮していないことに原因がある。例えば、1つだけ別に定義したgrepで同じことをすると、次のように期待通りに動作することを確認できる。

grepはパイプラインに対して期待通りに動作している

これはgrepに対応する関数ではパイプラインを考慮した処理が記述されているためだ。grepの該当する部分は次のようになっていた。

function grep {

...略...

    $Input | wsl grep $Args
}

ポイントは「wsl grep」の処理に対して「$Input |」というパイプが接続されている点にある。この処理によって、前の処理のデータがパイプラインを介してgrepコマンドへ接続されている。

同じようにwslでLinuxのコマンドを実行している前回定義した関数部分を見ると、次のようになっている。

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

wslはそれ単体で実行されており、「$Input |」は接続されていない。これが原因だ。つまり、wcコマンドの動作が停止したように見えたのは、wcコマンドがその場でパイプラインからの入力ではなく、標準入力からのデータ入力を待っている状態になっているためということになる。

この部分をパイプとの接続を考慮するように変更すると、問題を解決することができる。

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

なお、この部分は一旦テキストファイル(.ps拡張子の一時ファイル)に書き出してから読み込むという処理をするため、「$(ドル記号)」をそのままドル記号として保持するためにバッククォートでエスケープ処理されている点に注意しておこう。「$Input |」だと「$Input」が変数として解釈され、「|」だけが使われてしまう。バッククォートでエスケープすることで「$Input |」をそのままテキストファイルへ書き出している。詳しい説明は前回をされたい。

最初に実行したコマンドをこの新しい定義でもう1回実行すると、次のようにwcコマンドが期待通りの動作をすることが確認できる。

改良後の動作

上記スクリーンショットからわかるように、大体2770個くらいのコマンドが定義されていることになるのだが、もしこれを全部関数として事前に用意しておいた場合、「$PROFILE」の中身は1万行くらい増え、今回の書き換えも2770回くらい行う必要があったことになる。$PROFILE読み込み時にダイナミックに関数を生成して利用できるようにしたことで、1カ所変更するだけで全ての関数に処理を反映することができたのだ。

Linuxのlsコマンドを使う

前述した処理でlsコマンドに関してもLinuxのlsコマンドが関数として定義されているのだが、「ls」を実行すると次のようにGet-ChildItemコマンドレットの実行結果が出力される。コマンドプロンプトでのdirコマンドの出力に似ている。

PowerShell 7では、「ls」はGet-ChildItemコマンドレットへのエイリアス

なぜこのようなことが起こるかと言うと、PowerShell 7ではlsはGet-ChildItemコマンドレットへのエイリアスになっているためだ。関数として「ls」が定義されていても、エイリアスで定義されているとエイリアスのほうが優先されるので、結果的にGet-ChildItemコマンドレットが実行される。

ただし、次のように処理すれば定義されているエイリアスは削除することができる。

Remove-Item alias:ls

ここでもうひと工夫しておこう。$PROFILEの編集を行っている場合、「. $PROFILE」のようにすることで書き換えた$PROFILEの内容を現在のPowerShellへ適用することができる。この方法で書き換えと動作確認を行っていった場合、上記の書き方だと2回目以降はエラーが発生する。それは1回目の処理で「ls」のエイリアスを削除してしまったので、2回目には「そのようなエイリアスはない」というエラーが出てしまうのだ。

いくつかやり方があるが、順当に考えると「lsがエイリアスとして定義されている場合には、lsエイリアスを削除」といった処理にすればよい。エイリアスが定義されているかどうかは「Get-Alias ls」を実行すれば調べることができる。ただし、こちらでもエイリアスが存在していないと赤いエラーメッセージが出力されてしまう。ここでリダイレクトを使い、エラーメッセージもろとも「$null」へ流し込んでやるようにする。こうすれば、Get-Aliasがエラーメッセージを発生させても出力としては表示されない。つまり、次のように記述しておけばよいことになる。

Get-Alias ls *> $null && Remove-Item alias:ls

Linuxのエイリアスコマンドは最近では「ls —color=auto」へのエイリアス(Linuxのインタラクティブシェルのエイリアス)になっていることが多い。PowerShellでも同じように動作させるなら、次のようにPowerShellの関数として定義し、そこで「—color=auto」を指定すればよい。

function ls { wsl ls --color=auto $Args }

連載では、これまで「ll」と「la」を次のようにGet-ChildItemコマンドレットとして設定していた。

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

「ls」でLinuxコマンドの「ls」が使われるように書き換えたので、この部分もLinuxシェルにおけるエイリアスのように、次のように書き換えてみる。これで動作がLinuxへの設定に近づく。

function ll { ls -l }
function la { ls -a }

この状態で「ls」を実行すると次のようになる。「Get-ChildItem」ではなくLinuxのlsコマンドが実行されていることがわかる。

lsでLinuxのlsコマンドが実行される

「ll」では「ls -l」が実行され、やはりLinuxと同じ出力を得ることができる。

「ll」でもLinuxコマンドのlsコマンドが実行される

普段LinuxやmacOSといったUNIX系の環境を使っている場合、PowerShellのGet-ChildItemコマンドレットよりもlsコマンドの出力のほうがなじみがあるだろう。最初からエイリアスが設定されているため一旦エイリアスを解除する必要があるが、今回のように設定しておけば、エイリアスを解除して新しい設定を適用することができる。Linux側のコマンドに慣れている場合、今回取り上げた方法でカスタマイズしていただきたい。

付録: $PROFILE

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

#========================================================================
# Linux commands definition used via wsl
#========================================================================
$_linux_path = @('/usr/local/sbin', '/usr/local/bin', '/usr/sbin', 
                 '/usr/bin', '/sbin', '/bin')
$_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用語の意味を事典でチェック!

一覧はこちら

会員登録(無料)

ページの先頭に戻る