11
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

【PowerShell】PsReadLine 設定のススメ

PowerShell ユーザーの人権と言っても過言ではない存在、それが PsReadLine 。PowerShell 5.0 から標準で同梱されるようになり、コマンドの着色やキーバインド、補完の表示形式など諸々を設定できるツールです。

「 PowerShell 使いにくい!!」という怨嗟の声もチラホラ耳にしますが、これを活用することで解決できる問題もあるのではないでしょうか。

以下、大半は 公式のサンプル をもとに個人的にカスタムしているものです。

ポイント

  1. コマンドラインの状態を取得する:[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState()

    • 下記のように参照渡しすると、 $line でその時点で表示されているコマンド文字列、 $cursor でカーソル位置を取得できるようになります。
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
    
  2. 選択状態を取得する: [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState()

    • 下記の参照渡しで選択範囲の開始位置と終了位置を取得できます。何も選択していない状態では $selectionStart は -1 になります。
    $selectionStart = $null
    $selectionLength = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
    

なお、 -key の指定で Shift キーによる修飾は、対象文字を大文字にすることで表現します。 ctrl+v は Ctrl と v ですが ctrl+V は Ctrl と Shift と v に相当する形です。

用途別コード

履歴の管理

重複した履歴を保存しないようにするには下記のようにします。

Set-PSReadlineOption -HistoryNoDuplicates

その他、履歴に残さない条件も指定することができます。下記では SKIPHISTORY の文字が含まれるコマンド(用途は後述)、アルファベット1文字だけのコマンド、終了コマンドを保存しないようにしています。

Set-PSReadlineOption -AddToHistoryHandler {
    param ($command)
    switch -regex ($command) {
        "SKIPHISTORY" {return $false}
        "^[a-z]$" {return $false}
        "exit" {return $false}
    }
    return $true
}

単語区切り

Ctrl + 矢印でカーソル移動するときの単語区切りです。デフォルトでは ;:,.[]{}()/\|^&*-=+'" + "\u2013\u2014\u2015 とのこと。日本語に対応させて業務上よく使う約物を追加しています。

Set-PSReadLineOption -WordDelimiters ";:,.[]{}()/\|^&*-=+'`" !?@#$%&_<>「」()『』『』[]、,。:;/"

括弧/引用符の入力補完

括弧

  • 文字列選択時:
    選択範囲を括弧で囲みカーソルを閉じ括弧の後ろに移動させる
  • 非選択時:
    • 開き括弧に対応した閉じ括弧を入力して間にカーソルを移動させる
    • 一方の括弧が先に入力されていた場合は対応する括弧のみ入力する
Set-PSReadLineKeyHandler -Key "(","{","[" -BriefDescription "InsertPairedBraces" -LongDescription "Insert matching braces or wrap selection by matching braces" -ScriptBlock {
    param($key, $arg)
    $openChar = $key.KeyChar
    $closeChar = switch ($openChar) {
        <#case#> "(" { [char]")"; break }
        <#case#> "{" { [char]"}"; break }
        <#case#> "[" { [char]"]"; break }
    }

    $selectionStart = $null
    $selectionLength = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($selectionStart -ne -1) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $openChar + $line.SubString($selectionStart, $selectionLength) + $closeChar)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
        return
    }
    $nOpen = [regex]::Matches($line, [regex]::Escape($openChar)).Count
    $nClose = [regex]::Matches($line, [regex]::Escape($closeChar)).Count
    if ($nOpen -ne $nClose) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($openChar)
    }
    else {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($openChar + $closeChar)
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
    }
}

余計な閉じ括弧を入力しない

下記のようにすることで、既に閉じ括弧が入力されている状態では代わりにカーソルが動くのみになります。
上記のように自動で閉じ括弧を補完すると、手癖で入力したときに余計な閉じ括弧を入力してしまうことがあるので、安全のための設定です。

Set-PSReadLineKeyHandler -Key ")","]","}" -BriefDescription "SmartCloseBraces" -LongDescription "Insert closing brace or skip" -ScriptBlock {
    param($key, $arg)

    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($line[$cursor] -eq $key.KeyChar) {
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
    }
    else {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($key.KeyChar)
    }
}

引用符

上記の括弧まわりの入力補完を引用符にも適用しています。こちらは開き/閉じの区別がないので少し単純に書けます。

Set-PSReadLineKeyHandler -Key "`"","'" -BriefDescription "smartQuotation" -LongDescription "Put quotation marks and move the cursor between them or put marks around the selection" -ScriptBlock {
    param($key, $arg)
    $mark = $key.KeyChar

    $selectionStart = $null
    $selectionLength = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($selectionStart -ne -1) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $mark + $line.SubString($selectionStart, $selectionLength) + $mark)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
        return
    }

    if ($line[$cursor] -eq $mark) {
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
        return
    }

    $nMark = [regex]::Matches($line, $mark).Count
    if ($nMark % 2 -eq 1) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($mark)
    }
    else {
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($mark + $mark)
        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
    }
}

コマンドを括弧で囲む

文字列選択時は選択文字を丸括弧で囲み、非選択時はその時点のコマンドライン全体をカッコで囲みます。
Get-Childitem -file と入力してから (Get-Childitem -file).LastWriteTime と特定のプロパティだけ取り出したいときなどにカーソルの移動量が減ります。

Set-PSReadLineKeyHandler -Key "alt+w" -BriefDescription "WrapLineByParenthesis" -LongDescription "Wrap the entire line and move the cursor after the closing parenthesis or wrap selected string" -ScriptBlock {
    $selectionStart = $null
    $selectionLength = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
    if ($selectionStart -ne -1) {
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, "(" + $line.SubString($selectionStart, $selectionLength) + ")")
        [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
    }
    else {
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')')
        [Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine()
    }
}

メソッドの補完時に閉じ括弧を忘れない

(Shift + )Tab でメソッドを補完するとき、デフォルトでは .ToString( などと開き括弧しか表示されないのが気になったので閉じ括弧も補完するようにしています。

Remove-PSReadlineKeyHandler "tab"
Set-PSReadLineKeyHandler -Key "tab" -BriefDescription "smartNextCompletion" -LongDescription "insert closing parenthesis in forward completion of method" -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::TabCompleteNext()
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($line[($cursor - 1)] -eq "(") {
        if ($line[$cursor] -ne ")") {
            [Microsoft.PowerShell.PSConsoleReadLine]::Insert(")")
            [Microsoft.PowerShell.PSConsoleReadLine]::BackwardChar()
        }
    }
}

Remove-PSReadlineKeyHandler "shift+tab"
Set-PSReadLineKeyHandler -Key "shift+tab" -BriefDescription "smartPreviousCompletion" -LongDescription "insert closing parenthesis in backward completion of method" -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::TabCompletePrevious()
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($line[($cursor - 1)] -eq "(") {
        if ($line[$cursor] -ne ")") {
            [Microsoft.PowerShell.PSConsoleReadLine]::Insert(")")
            [Microsoft.PowerShell.PSConsoleReadLine]::BackwardChar()
        }
    }
}

プロファイルの再読み込み

プロファイルを更新するショートカットキーを用意しています。プロファイルから読み込んでいる自作コマンドレットを手直ししたときなど、すぐに修正をターミナルに反映させられるので便利です。
上記設定で SKIPHISTORY の文字が含まれるコマンドを履歴に残さないようにしているので、このショートカットキーで入力された内容は履歴を汚しません。

Set-PSReadLineKeyHandler -Key "alt+r" -BriefDescription "reloadPROFILE" -LongDescription "reloadPROFILE" -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert('<#SKIPHISTORY#> . $PROFILE')
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

直前に使用した変数を利用する

デフォルトでは Alt + . ( linux と同じらしいですね)で直前のコマンドの最終要素を入力することができますが、 PowerShell では変数に $ をつける必要があるので一工夫しています。

コマンドの出力を確かめてから | sv hoge をつけて変数に格納することが多いので重宝しています。

Set-PSReadLineKeyHandler -Key "alt+a" -BriefDescription "yankLastArgAsVariable" -LongDescription "yankLastArgAsVariable" -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert("$")
    [Microsoft.PowerShell.PSConsoleReadLine]::YankLastArg()
    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
    if ($line -match '\$\$') {
        $newLine = $line -replace '\$\$', "$"
        [Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, $newLine)
    }
}

クリップボード内容を変数に格納する

簡単な文字列操作はコンソール上で済むことが多いので、 Ctrl + Shift + v でその時点のクリップボード内容を $CLIPPING という変数に格納できるようにしています。 AddToHistory() で直近の履歴として $CLIPPING を挿入しているため、 Ctrl + Shift + v で変数に格納後に上キーを押すだけでクリップボード内容の編集モードに入れます。

Set-PSReadLineKeyHandler -Key "ctrl+V" -BriefDescription "setClipString" -LongDescription "setClipString" -ScriptBlock {
    $command = "<#SKIPHISTORY#> get-clipboard | sv CLIPPING"
    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert($command)
    [Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory('$CLIPPING ')
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

このほかにも 豊富なサンプル が公開されています。自分好みの PowerShell ターミナルを作ってみましょう!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
11
Help us understand the problem. What are the problem?