„Linux“ komandų integravimas į „Windows“ naudojant „PowerShell“ ir WSL

Tipiškas „Windows“ kūrėjų klausimas: „Kodėl vis dar nėra <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Nesvarbu, ar tai galingas braukimas less arba pažįstamus įrankius grep arba sed, Windows kūrėjai nori lengvai pasiekti šias komandas kasdieniame darbe.

„Windows“ posistemis, skirtas „Linux“ (WSL) šiuo atžvilgiu padarė didžiulį žingsnį į priekį. Tai leidžia jums iškviesti Linux komandas iš Windows, perduodant jas tarpiniu serveriu wsl.exe (Pvz, wsl ls). Nors tai yra reikšmingas patobulinimas, ši parinktis turi daug trūkumų.

  • Visur esantis papildymas wsl varginantis ir nenatūralus.
  • „Windows“ keliai argumentuose ne visada veikia, nes pasvirieji brūkšniai interpretuojami kaip pabėgimo simboliai, o ne kaip katalogų skyrikliai.
  • „Windows“ keliai argumentuose nėra verčiami į atitinkamą WSL prijungimo tašką.
  • WSL profiliuose su slapyvardžiais ir aplinkos kintamaisiais neatsižvelgiama į numatytuosius nustatymus.
  • Linux kelio užbaigimas nepalaikomas.
  • Komandų užbaigimas nepalaikomas.
  • Argumento užbaigimas nepalaikomas.

Todėl „Windows“ sistemoje „Linux“ komandos traktuojamos kaip antros klasės piliečiai ir jas sunkiau naudoti nei vietines komandas. Norint sulyginti jų teises, būtina išspręsti aukščiau išvardintas problemas.

„PowerShell“ funkcijų paketai

Naudodami „PowerShell“ funkcijų paketus galime pridėti komandų užbaigimą ir pašalinti priešdėlių poreikį wsl, paverčiant Windows kelius į WSL kelius. Pagrindiniai reikalavimai kriauklėms:

  • Kiekvienai Linux komandai turi būti vienas funkcijos paketas tuo pačiu pavadinimu.
  • Apvalkalas turi atpažinti „Windows“ kelius, pateiktus kaip argumentus, ir konvertuoti juos į WSL kelius.
  • Lukštas turėtų skambinti wsl su atitinkama Linux komanda į bet kurią konvejerio įvestį ir perduodant visus komandinės eilutės argumentus, perduodamus funkcijai.

Kadangi šis šablonas gali būti taikomas bet kuriai komandai, galime abstrakčiai apibrėžti šiuos įpakavimus ir dinamiškai generuoti juos iš importuojamų komandų sąrašo.

# 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 ' ')
    }
}
"@
}

Sąrašas $command apibrėžia importo komandas. Tada dinamiškai generuojame kiekvienos iš jų funkcijų paketą naudodami komandą Invoke-Expression (pirmiausia pašalindami visus slapyvardžius, kurie prieštarautų funkcijai).

Funkcija kartojasi per komandinės eilutės argumentus, nustato Windows kelius naudodama komandas Split-Path и Test-Pathir tada konvertuoja šiuos kelius į WSL kelius. Mes vykdome kelius per pagalbinę funkciją Format-WslArgument, kurį apibrėšime vėliau. Jame nenaudojami specialūs simboliai, pvz., tarpai ir skliaustai, kurie kitu atveju būtų neteisingai interpretuojami.

Galiausiai perteikiame wsl konvejerio įvestis ir bet kokie komandinės eilutės argumentai.

Naudodami šiuos paketus galite iškviesti mėgstamas Linux komandas natūraliau, nepridėdami priešdėlio wsl ir nesijaudindami, kaip paverčiami keliai:

  • man bash
  • less -i $profile.CurrentUserAllHosts
  • ls -Al C:Windows | less
  • grep -Ein error *.log
  • tail -f *.log

Čia rodomas pagrindinis komandų rinkinys, tačiau galite sukurti apvalkalą bet kuriai Linux komandai tiesiog įtraukdami ją į sąrašą. Jei pridėsite šį kodą prie savo profilis PowerShell, šios komandos bus pasiekiamos kiekvienoje PowerShell sesijoje, kaip ir vietinės komandos!

Numatytieji nustatymai

Linux sistemoje įprasta apibrėžti slapyvardžius ir (arba) aplinkos kintamuosius prisijungimo profiliuose, nustatant numatytuosius dažnai naudojamų komandų parametrus (pvz., alias ls=ls -AFh arba export LESS=-i). Vienas iš tarpinio serverio per neinteraktyvų apvalkalą trūkumų wsl.exe - kad profiliai neįkelti, todėl šios parinktys pagal numatytuosius nustatymus nepasiekiamos (t.y. ls WSL ir wsl ls elgsis kitaip su pirmiau nurodytu slapyvardžiu).

„PowerShell“ teikia $PSDefaultParameterValues, standartinis numatytiesiems parametrams apibrėžti skirtas mechanizmas, tačiau tik cmdlet ir išplėstinėms funkcijoms. Žinoma, iš savo apvalkalų galime sukurti išplėstines funkcijas, tačiau tai sukelia nereikalingų komplikacijų (pvz., „PowerShell“ koreliuoja dalinių parametrų pavadinimus (pvz., -a koreliuoja su -ArgumentList).

Tačiau šiek tiek pakeitę savo apvalkalus galime įgyvendinti panašų modelį į $PSDefaultParameterValues, ir įgalinkite numatytąsias Linux komandų parinktis!

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 ' ')
    }
}

Pravažiavimas $WslDefaultParameterValues į komandinę eilutę siunčiame parametrus per wsl.exe. Toliau parodyta, kaip prie „PowerShell“ profilio pridėti instrukcijų, kaip konfigūruoti numatytuosius nustatymus. Dabar mes galime tai padaryti!

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

Kadangi parametrai modeliuojami po $PSDefaultParameterValues, Tu gali lengva juos išjungti laikinai įdėjus raktą "Disabled" į prasmę $true. Papildomas atskiros maišos lentelės pranašumas yra galimybė išjungti $WslDefaultParameterValues atskirai nuo $PSDefaultParameterValues.

Argumento užbaigimas

„PowerShell“ leidžia registruoti argumentų priekabas naudojant komandą Register-ArgumentCompleter. Bašas turi galingą programuojami automatinio užbaigimo įrankiai. WSL leidžia iškviesti bash iš PowerShell. Jei galime užregistruoti argumentų užbaigimus savo PowerShell funkcijų paketams ir iškviesti bash, kad sugeneruotume užbaigimus, gautume visą argumentų užbaigimą tokiu pat tikslumu kaip ir pats 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]
    }
}

Kodas yra šiek tiek tankus, nesuprantant kai kurių vidinių bash funkcijų, bet iš esmės tai, ką mes darome, yra tai:

  • Argumentų užbaigimo registravimas visoms mūsų funkcijų įpakavimo priemonėms perduodant sąrašą $commands parametre -CommandNameRegister-ArgumentCompleter.
  • Kiekvieną komandą susiejame su apvalkalo funkcija, kurią bash naudoja automatiniam užbaigimui (norėdami apibrėžti automatinio užbaigimo specifikacijas, bash naudoja $F, santrumpa complete -F <FUNCTION>).
  • „PowerShell“ argumentų konvertavimas $wordToComplete, $commandAst и $cursorPosition į formatą, kurio tikisi bash automatinio užbaigimo funkcijos pagal specifikacijas programuojamas automatinis užbaigimas bash.
  • Sudarome komandų eilutę, į kurią norite perkelti wsl.exe, kuri užtikrina, kad aplinka būtų tinkamai nustatyta, iškviečia atitinkamą automatinio užbaigimo funkciją ir išveda rezultatus eilutę po eilutės.
  • Tada skambiname wsl su komandine eilute išvestį atskiriame eilučių skyrikliais ir generuojame kiekvienam CompletionResults, rūšiuodami juos ir pašalindami simbolius, pvz., tarpus ir skliaustus, kurie kitu atveju būtų neteisingai interpretuojami.

Dėl to mūsų Linux komandų apvalkalai naudos lygiai tą patį automatinį užbaigimą kaip ir bash! Pavyzdžiui:

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

Kiekvienas automatinis užbaigimas pateikia reikšmes, būdingas ankstesniam argumentui, nuskaitydamas konfigūracijos duomenis, pvz., žinomus pagrindinius kompiuterius iš WSL!

<TAB> pereis per parametrus. <Ctrl + пробел> parodys visas galimas parinktis.

Be to, kadangi dabar turime „bash“ automatinį užbaigimą, galite automatiškai užbaigti „Linux“ kelius tiesiai „PowerShell“!

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

Tais atvejais, kai automatinis bash užbaigimas neduoda jokių rezultatų, „PowerShell“ grąžina numatytuosius sistemos „Windows“ kelius. Taigi praktiškai savo nuožiūra galite vienu metu naudoti abu kelius.

išvada

Naudodami PowerShell ir WSL galime integruoti Linux komandas į Windows kaip vietines programas. Nereikia ieškoti „Win32“ versijų ar „Linux“ paslaugų arba nutraukti darbo eigos, einant į „Linux“ apvalkalą. Tiesiog įdiegti WSL, konfigūruoti PowerShell profilis и nurodykite komandas, kurias norite importuoti! Turtingas automatinis „Linux“ ir „Windows“ komandų parametrų ir failų kelių užbaigimas yra funkcionalumas, kuris šiandien net nepasiekiamas vietinėse „Windows“ komandose.

Galimas visas pirmiau aprašytas šaltinio kodas ir papildomos gairės, kaip jį įtraukti į darbo eigą čia.

Kurios „Linux“ komandos jums atrodo naudingiausios? Kokių kitų dalykų trūksta dirbant sistemoje „Windows“? Rašykite komentaruose arba „GitHub“.!

Šaltinis: www.habr.com

Добавить комментарий