Integrácia tímov Linux в Windows pomocou PowerShellu a WSL

Typická otázka od vývojárov Windows„Prečo tu ešte nič nie je?“ <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Či už ide o silný ťah less alebo známe nástroje grep alebo sed, vývojári pod Windows chcú mať k týmto príkazom jednoduchý prístup pri svojej každodennej práci.

Subsystém Windows pre Linux (WSL) v tomto smere urobil obrovský krok vpred. Umožňuje vám volať príkazy Linux z Windows, sprostredkovaním ich prostredníctvom wsl.exe (Napr wsl ls). Hoci ide o výrazné zlepšenie, táto možnosť trpí množstvom nevýhod.

  • Všadeprítomný prídavok wsl únavné a neprirodzené.
  • Cesty Windows v argumentoch nie vždy fungujú, pretože spätné lomky sa interpretujú ako escape znaky, nie ako oddeľovače adresárov.
  • Cesty Windows Argumenty v WSL sa neprekladajú do zodpovedajúceho bodu pripojenia.
  • V profiloch WSL s aliasmi a premennými prostredia sa nerešpektujú predvolené nastavenia.
  • Dokončovanie cesty nie je podporované Linux.
  • Dokončenie príkazu nie je podporované.
  • Dokončenie argumentu nie je podporované.

V dôsledku tímu Linux sú vnímané pod Windows Sú ako občania druhej kategórie – a je ťažšie ich využiť ako ich domáce tímy. Aby sa im vyrovnali práva, je potrebné tieto problémy riešiť.

Obal funkcií PowerShell

Pomocou obalov funkcií PowerShell môžeme pridať dokončenie príkazov a eliminovať potrebu prefixov wsl, vysielacie cesty Windows Na ceste WSL. Základné požiadavky na shell:

  • Pre každý tím Linux Musí existovať jeden funkčný obal s rovnakým názvom.
  • Shell musí rozpoznávať cesty Windows, odovzdané ako argumenty a previesť ich na cesty WSL.
  • Škrupina by mala zavolať wsl s príslušným príkazom Linux na akýkoľvek vstup kanála a odovzdávanie akýchkoľvek argumentov príkazového riadka odovzdaných funkcii.

Keďže tento vzor možno použiť na akýkoľvek príkaz, môžeme abstrahovať definíciu týchto obalov a dynamicky ich generovať zo zoznamu príkazov na import.

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

Zoznam $command definuje importné príkazy. Potom pomocou príkazu dynamicky vygenerujeme obal funkcií pre každý z nich Invoke-Expression (najprv odstránením všetkých aliasov, ktoré by boli v konflikte s funkciou).

Funkcia iteruje cez argumenty príkazového riadku a určuje cesty Windows pomocou príkazov Split-Path и Test-Patha potom tieto cesty skonvertuje na cesty WSL. Cesty vedieme cez pomocnú funkciu Format-WslArgument, ktorý si zadefinujeme neskôr. Vynecháva špeciálne znaky, ako sú medzery a zátvorky, ktoré by inak boli nesprávne interpretované.

Nakoniec prenesieme wsl potrubný vstup a všetky argumenty príkazového riadku.

Pomocou takýchto obalov môžete volať svoje obľúbené príkazy Linux prirodzenejším spôsobom, bez pridania predpony wsl a bez obáv o to, ako sa cesty prevedú:

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

Tu je zobrazená základná sada príkazov, ale pre ľubovoľný príkaz môžete vytvoriť obal. Linux, jednoduchým pridaním do zoznamu. Ak pridáte tento kód do svojho profil PowerShell, tieto príkazy budete mať k dispozícii v každej relácii PowerShell, rovnako ako natívne príkazy!

Predvolené nastavenia

В Linux Je bežné definovať aliasy a/alebo premenné prostredia v profiloch (prihlasovací profil), čím sa nastavia predvolené parametre pre často používané príkazy (napríklad alias ls=ls -AFh alebo export LESS=-i). Jedna z nevýhod proxyovania cez neinteraktívny shell wsl.exe - že profily nie sú načítané, takže tieto možnosti nie sú štandardne dostupné (t.j. ls vo WSL a wsl ls sa bude správať inak s aliasom definovaným vyššie).

PowerShell poskytuje $PSDefaultParameterValues, štandardný mechanizmus na definovanie predvolených parametrov, ale len pre cmdlety a pokročilé funkcie. Samozrejme, môžeme z našich shellov urobiť pokročilé funkcie, ale to prináša zbytočné komplikácie (napríklad PowerShell koreluje názvy čiastkových parametrov (napr. -a koreluje s -ArgumentList), čo bude v konflikte s príkazmi Linux, pričom ako argumenty sa berú čiastočné názvy) a syntax na definovanie predvolených hodnôt nebude najvhodnejšia (definovanie predvolených argumentov vyžaduje názov parametra v prepínači, nielen názov príkazu).

S miernou úpravou našich mušlí však môžeme implementovať model podobný ako $PSDefaultParameterValuesa povoliť predvolené možnosti pre príkazy 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 ' ')
    }
}

Pasovanie $WslDefaultParameterValues do príkazového riadku posielame parametre cez wsl.exe. Nasledujúci text ukazuje, ako pridať pokyny do profilu PowerShell na konfiguráciu predvolených nastavení. Teraz to dokážeme!

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

Keďže parametre sú modelované po $PSDefaultParameterValuesmôžete je ľahké ich deaktivovať dočasne inštaláciou kľúča "Disabled" do významu $true. Ďalšou výhodou samostatnej hašovacej tabuľky je možnosť deaktivácie $WslDefaultParameterValues oddelene od $PSDefaultParameterValues.

Dokončenie argumentácie

PowerShell vám umožňuje zaregistrovať upútavky argumentov pomocou príkazu Register-ArgumentCompleter. Bash má moc programovateľné nástroje automatického dokončovania. WSL vám umožňuje volať bash z PowerShell. Ak dokážeme zaregistrovať dokončenia argumentov pre naše obaly funkcií PowerShell a zavolať bash na generovanie dokončení, získame úplné dokončenie argumentov s rovnakou presnosťou ako samotný 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]
    }
}

Kód je trochu hustý bez pochopenia niektorých vnútorných funkcií bashu, ale v podstate robíme toto:

  • Registrácia dokončovača argumentov pre všetky naše funkcie wrapper odovzdaním zoznamu $commands v parametri -CommandName pre Register-ArgumentCompleter.
  • Každý príkaz mapujeme na funkciu shell, ktorú bash používa na automatické dopĺňanie (na definovanie špecifikácií automatického dopĺňania používa bash $F, skratka pre complete -F <FUNCTION>).
  • Konverzia argumentov PowerShell $wordToComplete, $commandAst и $cursorPosition do formátu očakávaného funkciami automatického dopĺňania bash podľa špecifikácií programovateľné automatické dokončovanie bash.
  • Vytvoríme príkazový riadok na prenos wsl.exe, ktorý zaisťuje správne nastavenie prostredia, zavolá príslušnú funkciu automatického dokončovania a výsledky vypíše riadok po riadku.
  • Potom zavoláme wsl s príkazovým riadkom oddeľujeme výstup oddeľovačmi riadkov a generujeme pre každý CompletionResults, ich triedenie a escapovanie znakov, ako sú medzery a zátvorky, ktoré by inak boli nesprávne interpretované.

Výsledkom je, že naše veliteľské shell-y Linux použije presne to isté automatické dopĺňanie ako v bash! Napríklad:

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

Každé automatické dopĺňanie dodáva hodnoty špecifické pre predchádzajúci argument, čítajúc konfiguračné údaje, ako sú napríklad známi hostitelia z WSL!

<TAB> bude cyklicky prechádzať parametrami. <Ctrl + пробел> zobrazí všetky dostupné možnosti.

Keďže teraz máme funkčné automatické dopĺňanie v bash, môžete automaticky dopĺňať cesty Linux priamo v PowerShelle!

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

V prípadoch, keď automatické dopĺňanie v bash neprináša žiadne výsledky, PowerShell sa vráti k predvoleným systémovým cestám. WindowsV praxi teda môžete podľa vlastného uváženia použiť obe cesty súčasne.

Záver

Pomocou PowerShellu a WSL môžeme integrovať príkazy Linux в Windows ako natívne aplikácie. Nie je potrebné hľadať zostavenia alebo nástroje pre Win32 Linux alebo prerušte pracovný postup prechodom na Linux-shell. Len nainštalovať WSL, nakonfigurovať Profil PowerShell и zoznam príkazov, ktoré chcete importovaťBohaté automatické dopĺňanie pre parametre príkazov a cesty k súborom! Linux и Windows — toto je funkcia, ktorá dnes nie je dostupná ani v natívnych príkazoch Windows.

K dispozícii je úplný zdrojový kód popísaný vyššie, ako aj ďalšie pokyny na jeho začlenenie do vášho pracovného postupu tu.

Aké tímy? Linux Čo považujete za najužitočnejšie? Aké ďalšie známe veci chýbajú pri práci v WindowsNapíšte do komentárov alebo na GitHub!

Zdroj: hab.com

Kúpte si spoľahlivý hosting pre stránky s DDoS ochranou, VPS VDS servery 🔥 Kúpte si spoľahlivý webhosting s ochranou DDoS, VPS VDS servery | ProHoster