Интеграција Линук команди у Виндовс помоћу ПоверСхелл-а и ВСЛ-а

Типично питање Виндовс програмера: „Зашто још увек нема <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ 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, и омогући подразумеване опције за Линук команде!

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. У наставку је приказано како да додате упутства свом ПоверСхелл профилу да бисте конфигурисали подразумевана подешавања. Сада то можемо!

$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first"

Пошто су параметри моделовани по $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, сортирајући их и избегавајући знакове као што су размаци и заграде који би иначе били погрешно протумачени.

Као резултат тога, наше Линук командне шкољке ће користити потпуно исто аутоматско довршавање као басх! На пример:

  • ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>

Свако аутоматско довршавање даје вредности специфичне за претходни аргумент, читајући конфигурационе податке као што су познати хостови са ВСЛ-а!

<TAB> ће се кретати кроз параметре. <Ctrl + пробел> ће приказати све доступне опције.

Осим тога, пошто сада имамо басх аутоматско довршавање, можете аутоматски да довршавате Линук путање директно у ПоверСхелл-у!

  • less /etc/<TAB>
  • ls /usr/share/<TAB>
  • vim ~/.bash<TAB>

У случајевима када басх аутоматско довршавање не даје никакве резултате, ПоверСхелл се враћа на подразумеване Виндовс путање. Дакле, у пракси можете истовремено користити оба пута по свом нахођењу.

Закључак

Користећи ПоверСхелл и ВСЛ, можемо интегрисати Линук команде у Виндовс као изворне апликације. Нема потребе да тражите Вин32 верзије или Линук услужне програме или да прекидате ток посла одласком на Линук љуску. Само инсталирајте ВСЛ, конфигуришите ПоверСхелл профил и наведите команде које желите да увезете! Богато аутоматско довршавање за Линук и Виндовс командне параметре и путање датотека је функционалност која данас није доступна ни у изворним Виндовс командама.

Доступан је комплетан изворни код који је горе описан, као и додатне смернице за његово укључивање у ваш радни ток овде.

Које Линук команде сматрате најкориснијим? Које друге уобичајене ствари недостају када радите у Виндовс-у? Напишите у коментарима или на ГитХуб-у!

Извор: ввв.хабр.цом

Додај коментар