Integrasie van Linux-opdragte in Windows met behulp van PowerShell en WSL

'n Tipiese vraag van Windows-ontwikkelaars: "Hoekom is daar steeds geen <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Of dit nou 'n kragtige swipe is less of bekende gereedskap grep of sed, Windows-ontwikkelaars wil maklike toegang tot hierdie opdragte in hul daaglikse werk hê.

Windows-substelsel vir Linux (WSL) het 'n groot stap vorentoe gemaak in hierdie verband. Dit laat jou toe om Linux-opdragte vanaf Windows te bel deur hulle deur te gee wsl.exe (Bv, wsl ls). Alhoewel dit 'n aansienlike verbetering is, ly hierdie opsie aan 'n aantal nadele.

  • Alomteenwoordige toevoeging wsl vervelig en onnatuurlik.
  • Windows-paaie in argumente werk nie altyd nie, want terugskuinsstreepies word geïnterpreteer as ontsnappingskarakters eerder as gidsskeiers.
  • Windows-paaie in argumente word nie na die ooreenstemmende bergpunt in WSL vertaal nie.
  • Standaardinstellings word nie in WSL-profiele met aliasse en omgewingsveranderlikes gerespekteer nie.
  • Linux-padvoltooiing word nie ondersteun nie.
  • Opdragvoltooiing word nie ondersteun nie.
  • Argumentvoltooiing word nie ondersteun nie.

As gevolg hiervan word Linux-opdragte soos tweedeklas-burgers onder Windows behandel - en is dit moeiliker om te gebruik as inheemse opdragte. Om hul regte gelyk te maak, is dit nodig om die gelyste probleme op te los.

PowerShell-funksie-omhulsels

Met PowerShell-funksie-omhulsels kan ons bevelvoltooiing byvoeg en die behoefte aan voorvoegsels uitskakel wsl, wat Windows-paaie in WSL-paaie vertaal. Basiese vereistes vir skulpe:

  • Vir elke Linux-opdrag moet daar een funksie-omslag met dieselfde naam wees.
  • Die dop moet die Windows-paaie herken wat as argumente geslaag is en dit omskakel na WSL-paaie.
  • Die dop moet roep wsl met die toepaslike Linux-opdrag na enige pyplyn-invoer en deur enige opdragreëlargumente wat na die funksie oorgedra word, deur te gee.

Aangesien hierdie patroon op enige opdrag toegepas kan word, kan ons die definisie van hierdie omhulsels abstraheer en dit dinamies genereer uit 'n lys opdragte om in te voer.

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

Lys $command definieer invoeropdragte. Ons genereer dan dinamies 'n funksie-omslag vir elkeen van hulle deur die opdrag te gebruik Invoke-Expression (deur eers enige aliasse te verwyder wat met die funksie sou bots).

Die funksie herhaal oor opdragreëlargumente, bepaal Windows-paaie deur opdragte te gebruik Split-Path и Test-Pathen skakel dan hierdie paaie om na WSL-paaie. Ons loop die paadjies deur 'n helperfunksie Format-WslArgument, wat ons later sal definieer. Dit ontsnap spesiale karakters soos spasies en hakies wat andersins verkeerd geïnterpreteer sou word.

Ten slotte, ons dra wsl pyplyninvoer en enige opdragreëlargumente.

Met hierdie omhulsels kan jy jou gunsteling Linux-opdragte op 'n meer natuurlike manier noem sonder om 'n voorvoegsel by te voeg wsl en sonder om bekommerd te wees oor hoe die paaie omgeskakel word:

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

Die basiese stel opdragte word hier gewys, maar jy kan 'n dop vir enige Linux-opdrag skep deur dit eenvoudig by die lys te voeg. As jy hierdie kode by jou profiel PowerShell, hierdie opdragte sal in elke PowerShell-sessie vir jou beskikbaar wees, net soos inheemse opdragte!

Oorspronklike stellings

In Linux is dit algemeen om aliasse en/of omgewingsveranderlikes in aanmeldprofiele te definieer, deur verstekparameters te stel vir gereelde opdragte (byvoorbeeld, alias ls=ls -AFh of export LESS=-i). Een van die nadele van proxying deur 'n nie-interaktiewe dop wsl.exe - dat die profiele nie gelaai is nie, dus is hierdie opsies nie by verstek beskikbaar nie (d.w.s. ls in WSL en wsl ls sal anders optree met die alias hierbo gedefinieer).

PowerShell verskaf $PSDefaultParameterValues, 'n standaardmeganisme om verstekparameters te definieer, maar slegs vir cmdlets en gevorderde funksies. Natuurlik kan ons gevorderde funksies uit ons skulpe maak, maar dit bring onnodige komplikasies mee (byvoorbeeld, PowerShell korreleer gedeeltelike parametername (byvoorbeeld, -a korreleer met -ArgumentList), wat sal bots met Linux-opdragte wat gedeeltelike name as argumente neem), en die sintaksis vir die definisie van verstekwaardes sal nie die geskikste wees nie (verstekargumente vereis die naam van die parameter in die sleutel, nie net die opdragnaam nie) .

Met 'n effense verandering aan ons skulpe, kan ons egter 'n soortgelyke model implementeer $PSDefaultParameterValues, en aktiveer verstekopsies vir Linux-opdragte!

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

Verby $WslDefaultParameterValues na die opdragreël, stuur ons parameters via wsl.exe. Die volgende wys hoe om instruksies by jou PowerShell-profiel te voeg om verstekinstellings op te stel. Nou kan ons dit doen!

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

Aangesien die parameters gemodelleer word na $PSDefaultParameterValuesjy kan dit is maklik om hulle uit te skakel tydelik deur die sleutel te installeer "Disabled" tot betekenis $true. 'n Bykomende voordeel van 'n aparte hash-tabel is die vermoë om te deaktiveer $WslDefaultParameterValues apart van $PSDefaultParameterValues.

Argumentvoltooiing

PowerShell laat jou toe om argumentsleepwaens te registreer deur die opdrag te gebruik Register-ArgumentCompleter. Bash het kragtige programmeerbare outo-voltooiing gereedskap. WSL laat jou toe om bash vanaf PowerShell te bel. As ons argumentvoltooiings vir ons PowerShell-funksieomhulsels kan registreer en bash kan oproep om die voltooiings te genereer, kry ons volle argumentvoltooiing met dieselfde akkuraatheid as bash self!

# 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]
    }
}

Die kode is 'n bietjie dig sonder om sommige van bash se interne funksies te verstaan, maar basies wat ons doen is dit:

  • Registreer 'n argument voltooier vir al ons funksie omhulsels deur 'n lys deur te gee $commands in parameter -CommandName vir Register-ArgumentCompleter.
  • Ons karteer elke opdrag na die dopfunksie wat bash vir outovoltooiing gebruik (om outovoltooiingsspesifikasies te definieer, gebruik bash $F, afkorting vir complete -F <FUNCTION>).
  • Skakel PowerShell-argumente om $wordToComplete, $commandAst и $cursorPosition in die formaat wat verwag word deur bash se outovoltooiingsfunksies volgens die spesifikasies programmeerbare outo-voltooiing bash.
  • Ons stel 'n opdragreël saam om na oor te dra wsl.exe, wat verseker dat die omgewing korrek opgestel is, roep die toepaslike outo-voltooiingsfunksie en voer die resultate in lyn-vir-lyn uit.
  • Dan bel ons wsl met die opdragreël skei ons die uitvoer deur lynskeiers en genereer vir elkeen CompletionResults, sorteer hulle en ontsnap karakters soos spasies en hakies wat andersins verkeerd geïnterpreteer sou word.

As gevolg hiervan sal ons Linux-opdragdoppe presies dieselfde outovoltooiing as bash gebruik! Byvoorbeeld:

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

Elke outovoltooiing verskaf waardes spesifiek vir die vorige argument, lees konfigurasiedata soos bekende gashere van WSL!

<TAB> sal deur die parameters blaai. <Ctrl + пробел> sal alle beskikbare opsies wys.

Boonop, aangesien ons nou bash-outovoltooiing het, kan u Linux-paaie direk in PowerShell outovoltooi!

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

In gevalle waar bash-outovoltooiing geen resultate lewer nie, keer PowerShell terug na die stelsel se standaard Windows-paaie. In die praktyk kan u dus albei paaie gelyktydig na u goeddunke gebruik.

Gevolgtrekking

Deur PowerShell en WSL te gebruik, kan ons Linux-opdragte in Windows integreer as inheemse toepassings. Dit is nie nodig om na Win32-geboue of Linux-nutsprogramme te soek of jou werkvloei te onderbreek deur na 'n Linux-dop te gaan nie. Net installeer WSL, instel PowerShell-profiel и lys die opdragte wat jy wil invoer! Ryk outovoltooiing vir Linux- en Windows-opdragparameters en lêerpaaie is funksionaliteit wat nie eens vandag in inheemse Windows-opdragte beskikbaar is nie.

Die volledige bronkode wat hierbo beskryf word, sowel as bykomende riglyne om dit by jou werkvloei in te sluit, is beskikbaar hier.

Watter Linux-opdragte vind jy die nuttigste? Watter ander algemene dinge ontbreek wanneer u in Windows werk? Skryf in die kommentaar of op GitHub!

Bron: will.com

Voeg 'n opmerking