Интеграција Линук команди у Виндовс помоћу ПоверСхелл-а и ВСЛ-а
Типично питање Виндовс програмера: „Зашто још увек нема <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>? Било да се ради о снажном потезу less или познати алати grep или sed, Виндовс програмери желе лак приступ овим командама у свом свакодневном раду.
Виндовс подсистем за Линук (ВСЛ) је направио огроман корак напред у том погледу. Омогућава вам да позивате Линук команде из Виндовс-а тако што ћете их проки користити wsl.exe (нпр. wsl ls). Иако је ово значајно побољшање, ова опција има бројне недостатке.
Свеприсутни додатак wsl заморно и неприродно.
Виндовс путање у аргументима не функционишу увек јер се обрнуте косе црте тумаче као излазни знакови, а не као сепаратори директоријума.
Виндовс путање у аргументима се не преводе на одговарајућу тачку монтирања у ВСЛ-у.
Подразумеване поставке се не поштују у ВСЛ профилима са псеудонима и променљивим окружења.
Довршавање Линук путање није подржано.
Довршавање команде није подржано.
Довршавање аргумента није подржано.
Као резултат тога, Линук команде се третирају као грађани другог реда под Виндовс-ом — и теже су за коришћење од изворних команди. За изједначавање њихових права потребно је решити наведене проблеме.
Омотачи функција ПоверСхелл
Са омотачима функција ПоверСхелл, можемо додати довршавање команде и елиминисати потребу за префиксима wsl, преводећи Виндовс путање у ВСЛ путање. Основни захтеви за шкољке:
За сваку Линук команду мора постојати један омотач функције са истим именом.
Схелл мора препознати Виндовс путање прослеђене као аргументе и конвертовати их у ВСЛ путање.
Шкољка би требало да позове wsl са одговарајућом Линук командом на било који улаз цевовода и прослеђивањем свих аргумената командне линије прослеђених функцији.
Пошто се овај образац може применити на било коју команду, можемо апстраховати дефиницију ових омотача и динамички их генерисати са листе команди за увоз.
# 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 (тако што ћете прво уклонити све псеудониме који би били у сукобу са функцијом).
Функција понавља аргументе командне линије, одређује Виндовс путање помоћу команди Split-Path и Test-Pathа затим конвертује ове путање у ВСЛ путање. Путеве покрећемо кроз помоћну функцију Format-WslArgument, које ћемо касније дефинисати. Избегава посебне знакове као што су размаци и заграде који би иначе били погрешно протумачени.
На крају, преносимо wsl унос цевовода и све аргументе командне линије.
Помоћу ових омота можете позивати своје омиљене Линук команде на природнији начин без додавања префикса wsl и без бриге о томе како се путање конвертују:
man bash
less -i $profile.CurrentUserAllHosts
ls -Al C:Windows | less
grep -Ein error *.log
tail -f *.log
Овде је приказан основни скуп команди, али можете креирати љуску за било коју Линук команду једноставним додавањем на листу. Ако додате овај код у свој профил ПоверСхелл, ове команде ће вам бити доступне у свакој ПоверСхелл сесији, баш као и изворне команде!
Подразумевана подешавања
У Линук-у је уобичајено дефинисати псеудониме и/или променљиве окружења у профилима за пријављивање, постављајући подразумеване параметре за често коришћене команде (на пример, alias ls=ls -AFh или export LESS=-i). Један од недостатака проксија кроз неинтерактивну шкољку wsl.exe - да профили нису учитани, па ове опције нису подразумевано доступне (тј. ls у ВСЛ и wsl ls понашаће се другачије са псеудонимом дефинисаним горе).
ПоверСхелл пружа $ПСДефаултПараметерВалуес, стандардни механизам за дефинисање подразумеваних параметара, али само за цмдлет команде и напредне функције. Наравно, можемо направити напредне функције од наших шкољки, али то доводи до непотребних компликација (на пример, ПоверСхелл повезује делимична имена параметара (нпр. -a корелира са -ArgumentList), што ће бити у сукобу са Линук командама које узимају делимична имена као аргументе), а синтакса за дефинисање подразумеваних вредности неће бити најприкладнија (подразумевани аргументи захтевају име параметра у кључу, а не само име команде) .
Међутим, уз малу модификацију наших шкољки, можемо имплементирати модел сличан $PSDefaultParameterValues, и омогући подразумеване опције за Линук команде!
Пролаз $WslDefaultParameterValues у командну линију, шаљемо параметре преко wsl.exe. У наставку је приказано како да додате упутства свом ПоверСхелл профилу да бисте конфигурисали подразумевана подешавања. Сада то можемо!
Пошто су параметри моделовани по $PSDefaultParameterValues, Можете лако их је онемогућити привремено инсталирањем кључа "Disabled" у смисао $true. Додатна предност посебне хеш табеле је могућност онемогућавања $WslDefaultParameterValues одвојено од $PSDefaultParameterValues.
Завршетак аргумента
ПоверСхелл вам омогућава да региструјете приколице аргумента помоћу наредбе Register-ArgumentCompleter. Басх има моћан програмабилни алати за аутоматско довршавање. ВСЛ вам омогућава да позовете басх из ПоверСхелл-а. Ако можемо да региструјемо довршавање аргумената за наше омоте ПоверСхелл функције и позовемо басх да бисмо генерисали довршења, добићемо потпуни завршетак аргумента са истом прецизношћу као и сам басх!
# 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]
}
}
Код је мало густ без разумевања неких од басх интерних функција, али у суштини оно што радимо је ово:
Регистровање довршивача аргумената за све наше омоте функција преношењем листе $commands у параметру -CommandName за Register-ArgumentCompleter.
Сваку команду мапирамо у функцију љуске коју басх користи за аутоматско довршавање (да би дефинисао спецификације аутоматског довршавања, басх користи $F, скраћеница за complete -F <FUNCTION>).
Претварање ПоверСхелл аргумената $wordToComplete, $commandAst и $cursorPosition у формат који очекују басх-ове функције аутодовршавања у складу са спецификацијама програмабилно аутоматско довршавање басх.
Састављамо командну линију за пренос wsl.exe, који осигурава да је окружење исправно подешено, позива одговарајућу функцију аутоматског довршавања и даје резултате ред по ред.
Онда зовемо wsl са командном линијом, одвајамо излаз по сепараторима редова и генеришемо за сваки CompletionResults, сортирајући их и избегавајући знакове као што су размаци и заграде који би иначе били погрешно протумачени.
Као резултат тога, наше Линук командне шкољке ће користити потпуно исто аутоматско довршавање као басх! На пример:
Свако аутоматско довршавање даје вредности специфичне за претходни аргумент, читајући конфигурационе податке као што су познати хостови са ВСЛ-а!
<TAB> ће се кретати кроз параметре. <Ctrl + пробел> ће приказати све доступне опције.
Осим тога, пошто сада имамо басх аутоматско довршавање, можете аутоматски да довршавате Линук путање директно у ПоверСхелл-у!
less /etc/<TAB>
ls /usr/share/<TAB>
vim ~/.bash<TAB>
У случајевима када басх аутоматско довршавање не даје никакве резултате, ПоверСхелл се враћа на подразумеване Виндовс путање. Дакле, у пракси можете истовремено користити оба пута по свом нахођењу.
Закључак
Користећи ПоверСхелл и ВСЛ, можемо интегрисати Линук команде у Виндовс као изворне апликације. Нема потребе да тражите Вин32 верзије или Линук услужне програме или да прекидате ток посла одласком на Линук љуску. Само инсталирајте ВСЛ, конфигуришите ПоверСхелл профил и наведите команде које желите да увезете! Богато аутоматско довршавање за Линук и Виндовс командне параметре и путање датотека је функционалност која данас није доступна ни у изворним Виндовс командама.
Доступан је комплетан изворни код који је горе описан, као и додатне смернице за његово укључивање у ваш радни ток овде.
Које Линук команде сматрате најкориснијим? Које друге уобичајене ствари недостају када радите у Виндовс-у? Напишите у коментарима или на ГитХуб-у!