開発者からの典型的な質問 Windows「なぜここにはまだ何もないのですか?」 <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?。 強力なスワイプかどうか less または使い慣れたツール grep または sed開発者 Windows 彼らは日々の業務でこれらのコマンドに簡単にアクセスしたいと考えている。
この点において、大きな前進を遂げました。コマンドを呼び出すことができます。 Linux の Windowsプロキシ経由で wsl.exe (たとえば、 wsl ls)。 これは大幅な改善ですが、このオプションには多くの欠点があります。
- ユビキタスな追加
wsl退屈で不自然。 - パス Windows 引数でバックスラッシュを使用すると、バックスラッシュがディレクトリ区切り文字ではなくエスケープ文字として解釈されるため、常に正しく動作するとは限りません。
- パス Windows 引数はWSL内の対応するマウントポイントに変換されません。
- デフォルト設定は、エイリアスと環境変数を含む WSL プロファイルでは尊重されません。
- パス補完はサポートされていません Linux.
- コマンド補完はサポートされていません。
- 引数の補完はサポートされていません。
チームの結果として Linux 認識される Windows 彼らはまるで二級市民のようで、地元チームよりも扱いにくい。彼らの権利を平等にするためには、これらの問題に対処する必要がある。
PowerShell 関数ラッパー
PowerShell 関数ラッパーを使用すると、コマンド補完を追加し、プレフィックスの必要性を排除できます。 wsl放送経路 Windows WSL パス上で、シェルに必要な基本要件は以下のとおりです。
- 各チームについて Linux 同じ名前の関数ラッパーが1つ存在する必要があります。
- シェルはパスを認識する必要がある Windows引数として渡されたものを、WSL パスに変換します。
- シェルは呼び出す必要があります
wsl適切なコマンドで Linux パイプライン入力に対して、関数に渡されたコマンドライン引数をすべて渡します。
このパターンは任意のコマンドに適用できるため、これらのラッパーの定義を抽象化し、インポートするコマンドのリストからラッパーを動的に生成できます。
# The commands to import.
$commands = "awk", "emacs", "grep", "head", "less", "ls", "man", "sed", "seq", "ssh", "tail", "vim"
# Register a function for each command.
$commands | ForEach-Object { Invoke-Expression @"
Remove-Alias $_ -Force -ErrorAction Ignore
function global:$_() {
for (`$i = 0; `$i -lt `$args.Count; `$i++) {
# If a path is absolute with a qualifier (e.g. C:), run it through wslpath to map it to the appropriate mount point.
if (Split-Path `$args[`$i] -IsAbsolute -ErrorAction Ignore) {
`$args[`$i] = Format-WslArgument (wsl.exe wslpath (`$args[`$i] -replace "", "/"))
# If a path is relative, the current working directory will be translated to an appropriate mount point, so just format it.
} elseif (Test-Path `$args[`$i] -ErrorAction Ignore) {
`$args[`$i] = Format-WslArgument (`$args[`$i] -replace "", "/")
}
}
if (`$input.MoveNext()) {
`$input.Reset()
`$input | wsl.exe $_ (`$args -split ' ')
} else {
wsl.exe $_ (`$args -split ' ')
}
}
"@
} リスト $command インポートコマンドを定義します。 次に、次のコマンドを使用して、それぞれの関数ラッパーを動的に生成します。 Invoke-Expression (関数と競合するエイリアスを最初に削除します)。
この関数はコマンドライン引数を反復処理し、パスを決定します。 Windows コマンドを使用する Split-Path и Test-Path次に、これらのパスを WSL パスに変換します。 ヘルパー関数を通じてパスを実行します Format-WslArgument、後で定義します。 誤って解釈される可能性があるスペースや括弧などの特殊文字をエスケープします。
最後にお伝えするのは、 wsl パイプライン入力とコマンドライン引数。
このようなラッパーを使えば、お気に入りのコマンドを呼び出すことができます。 Linux より自然な方法で、接頭辞を追加せずに wsl パスがどのように変換されるかを気にする必要はありません。
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
ここでは基本的なコマンドセットを示していますが、任意のコマンドに対応するラッパーを作成できます。 Linuxリストに追加するだけです。このコードをリストに追加すると、 PowerShell では、これらのコマンドは、ネイティブ コマンドと同様に、すべての PowerShell セッションで使用できるようになります。
デフォルトの設定
В Linux プロファイル (ログイン プロファイル) でエイリアスや環境変数を定義し、よく使用するコマンド (たとえば、 alias ls=ls -AFh または export LESS=-i)。 非対話型シェルを介したプロキシの欠点の XNUMX つは、 wsl.exe - プロファイルがロードされていないため、これらのオプションはデフォルトでは利用できません(つまり、 ls WSLと wsl ls 上記で定義したエイリアスでは動作が異なります)。
PowerShell が提供するもの 、デフォルトのパラメーターを定義するための標準メカニズムですが、コマンドレットと高度な機能のみを対象としています。 もちろん、シェルから高度な関数を作成することもできますが、これにより不必要な複雑さが生じます (たとえば、PowerShell は部分的なパラメーター名を関連付けます (たとえば、 -a と相関する -ArgumentListこれはコマンドと競合します。 Linux部分的な名前を引数として受け取る場合)、デフォルト値を定義する構文は最も適切なものではありません(デフォルト引数を定義するには、コマンド名だけでなく、スイッチ内のパラメーター名が必要です)。
ただし、シェルにわずかな変更を加えることで、次のようなモデルを実装できます。 $PSDefaultParameterValues、コマンドのデフォルトオプションを有効にする Linux!
function global:$_() {
…
`$defaultArgs = ((`$WslDefaultParameterValues.$_ -split ' '), "")[`$WslDefaultParameterValues.Disabled -eq `$true]
if (`$input.MoveNext()) {
`$input.Reset()
`$input | wsl.exe $_ `$defaultArgs (`$args -split ' ')
} else {
wsl.exe $_ `$defaultArgs (`$args -split ' ')
}
} 通過 $WslDefaultParameterValues コマンドラインに、パラメータを送信します。 wsl.exe。 以下に、PowerShell プロファイルに指示を追加してデフォルト設定を構成する方法を示します。 さあ、できるようになりました!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" パラメータは次のようにモデル化されているため、 $PSDefaultParameterValuesできます キーをインストールすることで一時的に "Disabled" 意味に $true。 別個のハッシュ テーブルの追加の利点は、 $WslDefaultParameterValues とは別に $PSDefaultParameterValues.
引数の補完
PowerShell では、次のコマンドを使用して引数トレーラーを登録できます。 Register-ArgumentCompleter。 バッシュには強力な機能があります 。 WSL を使用すると、PowerShell から bash を呼び出すことができます。 PowerShell 関数ラッパーの引数補完を登録し、bash を呼び出して補完を生成できれば、bash 自体と同じ精度で完全な引数補完が得られます。
# Register an ArgumentCompleter that shims bash's programmable completion.
Register-ArgumentCompleter -CommandName $commands -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
# Map the command to the appropriate bash completion function.
$F = switch ($commandAst.CommandElements[0].Value) {
{$_ -in "awk", "grep", "head", "less", "ls", "sed", "seq", "tail"} {
"_longopt"
break
}
"man" {
"_man"
break
}
"ssh" {
"_ssh"
break
}
Default {
"_minimal"
break
}
}
# Populate bash programmable completion variables.
$COMP_LINE = "`"$commandAst`""
$COMP_WORDS = "('$($commandAst.CommandElements.Extent.Text -join "' '")')" -replace "''", "'"
for ($i = 1; $i -lt $commandAst.CommandElements.Count; $i++) {
$extent = $commandAst.CommandElements[$i].Extent
if ($cursorPosition -lt $extent.EndColumnNumber) {
# The cursor is in the middle of a word to complete.
$previousWord = $commandAst.CommandElements[$i - 1].Extent.Text
$COMP_CWORD = $i
break
} elseif ($cursorPosition -eq $extent.EndColumnNumber) {
# The cursor is immediately after the current word.
$previousWord = $extent.Text
$COMP_CWORD = $i + 1
break
} elseif ($cursorPosition -lt $extent.StartColumnNumber) {
# The cursor is within whitespace between the previous and current words.
$previousWord = $commandAst.CommandElements[$i - 1].Extent.Text
$COMP_CWORD = $i
break
} elseif ($i -eq $commandAst.CommandElements.Count - 1 -and $cursorPosition -gt $extent.EndColumnNumber) {
# The cursor is within whitespace at the end of the line.
$previousWord = $extent.Text
$COMP_CWORD = $i + 1
break
}
}
# Repopulate bash programmable completion variables for scenarios like '/mnt/c/Program Files'/<TAB> where <TAB> should continue completing the quoted path.
$currentExtent = $commandAst.CommandElements[$COMP_CWORD].Extent
$previousExtent = $commandAst.CommandElements[$COMP_CWORD - 1].Extent
if ($currentExtent.Text -like "/*" -and $currentExtent.StartColumnNumber -eq $previousExtent.EndColumnNumber) {
$COMP_LINE = $COMP_LINE -replace "$($previousExtent.Text)$($currentExtent.Text)", $wordToComplete
$COMP_WORDS = $COMP_WORDS -replace "$($previousExtent.Text) '$($currentExtent.Text)'", $wordToComplete
$previousWord = $commandAst.CommandElements[$COMP_CWORD - 2].Extent.Text
$COMP_CWORD -= 1
}
# Build the command to pass to WSL.
$command = $commandAst.CommandElements[0].Value
$bashCompletion = ". /usr/share/bash-completion/bash_completion 2> /dev/null"
$commandCompletion = ". /usr/share/bash-completion/completions/$command 2> /dev/null"
$COMPINPUT = "COMP_LINE=$COMP_LINE; COMP_WORDS=$COMP_WORDS; COMP_CWORD=$COMP_CWORD; COMP_POINT=$cursorPosition"
$COMPGEN = "bind `"set completion-ignore-case on`" 2> /dev/null; $F `"$command`" `"$wordToComplete`" `"$previousWord`" 2> /dev/null"
$COMPREPLY = "IFS=`$'n'; echo `"`${COMPREPLY[*]}`""
$commandLine = "$bashCompletion; $commandCompletion; $COMPINPUT; $COMPGEN; $COMPREPLY" -split ' '
# Invoke bash completion and return CompletionResults.
$previousCompletionText = ""
(wsl.exe $commandLine) -split 'n' |
Sort-Object -Unique -CaseSensitive |
ForEach-Object {
if ($wordToComplete -match "(.*=).*") {
$completionText = Format-WslArgument ($Matches[1] + $_) $true
$listItemText = $_
} else {
$completionText = Format-WslArgument $_ $true
$listItemText = $completionText
}
if ($completionText -eq $previousCompletionText) {
# Differentiate completions that differ only by case otherwise PowerShell will view them as duplicate.
$listItemText += ' '
}
$previousCompletionText = $completionText
[System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterName', $completionText)
}
}
# Helper function to escape characters in arguments passed to WSL that would otherwise be misinterpreted.
function global:Format-WslArgument([string]$arg, [bool]$interactive) {
if ($interactive -and $arg.Contains(" ")) {
return "'$arg'"
} else {
return ($arg -replace " ", " ") -replace "([()|])", ('$1', '`$1')[$interactive]
}
}bash の内部関数の一部を理解していないと、コードは少し複雑ですが、基本的にやることは次のとおりです。
- リストを渡すことによるすべての関数ラッパーの引数コンプリーターの登録
$commandsパラメータで-CommandNameのためにRegister-ArgumentCompleter. - 各コマンドを bash がオートコンプリートに使用するシェル関数にマップします (オートコンプリートの仕様を定義するために、bash は
$F、の略語complete -F <FUNCTION>). - PowerShell の引数の変換
$wordToComplete,$commandAstи$cursorPosition仕様に従って bash のオートコンプリート機能が期待する形式に変換します bash。 - に転送するコマンドラインを作成します。
wsl.exeこれにより、環境が正しく設定されていることを確認し、適切なオートコンプリート関数を呼び出して、結果を XNUMX 行ずつ出力します。 - それから電話します
wslコマンドラインを使用して、出力を行区切り文字で区切って、それぞれに対して生成します。CompletionResults、それらを並べ替えたり、誤って解釈される可能性のあるスペースや括弧などの文字をエスケープしたりできます。
その結果、コマンドシェルは Linux bash とまったく同じオートコンプリート機能を使用します。例:
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>
各オートコンプリートは、前の引数に固有の値を提供し、WSL から既知のホストなどの構成データを読み取ります。
<TAB> パラメータを循環します。 <Ctrl + пробел> 利用可能なすべてのオプションが表示されます。
また、bashのオートコンプリート機能が動作するようになったので、パスのオートコンプリートも可能です。 Linux PowerShellで直接!
less /etc/<TAB>ls /usr/share/<TAB>vim ~/.bash<TAB>
bashのオートコンプリートで結果が見つからない場合、PowerShellはシステムのデフォルトパスにフォールバックします。 Windowsしたがって、実際には、ご自身の判断で両方の方法を同時に使用することができます。
まとめ
PowerShellとWSLを使用すると、コマンドを統合できます Linux в Windows ネイティブアプリのように動作します。Win32ビルドやユーティリティを探す必要はありません。 Linux または、次の場所に移動してワークフローを中断します。 Linux-シェル。ただ 、 構成、設定 и コマンドパラメータとファイルパスの豊富なオートコンプリート機能 Linux и Windows これは、今日のネイティブコマンドでも利用できない機能です。 Windows.
上記の完全なソース コードと、それをワークフローに組み込むための追加のガイドラインが利用可能です。 .
どのチームですか? Linux 最も役立つものは何ですか? Windows?コメントに書き込むか、 !
出所: habr.com
