Linux-kommando's yntegrearje yn Windows mei PowerShell en WSL

In typyske fraach fan Windows-ûntwikkelders: "Wêrom is d'r noch gjin <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Oft it in krêftige swipe is less of fertroude ark grep of sed, Windows-ûntwikkelders wolle maklike tagong ta dizze kommando's yn har deistich wurk.

Windows Subsystem foar Linux (WSL) hat yn dit ferbân in grutte stap foarút makke. It lit jo Linux-kommando's fan Windows neame troch se troch te proxyjen wsl.exe (bgl. wsl ls). Hoewol dit in wichtige ferbettering is, hat dizze opsje te lijen fan in oantal neidielen.

  • Ubiquitous tafoeging wsl ferfeelsum en ûnnatuerlik.
  • Windows-paden yn arguminten wurkje net altyd, om't backslashes wurde ynterpretearre as escape-tekens ynstee fan mapskiedings.
  • Windows-paden yn arguminten wurde net oerset nei it oerienkommende berchpunt yn WSL.
  • Standertynstellingen wurde net respektearre yn WSL-profilen mei aliassen en omjouwingsfariabelen.
  • Linux-paadfoltôging wurdt net stipe.
  • Kommando foltôging wurdt net stipe.
  • Argumintfoltôging wurdt net stipe.

As resultaat wurde Linux-kommando's behannele as twadde-klasse boargers ûnder Windows - en binne dreger te brûken dan native kommando's. Om har rjochten lyk te meitsjen, is it nedich om de neamde problemen op te lossen.

PowerShell funksje wrappers

Mei PowerShell funksje wrappers, kinne wy ​​tafoegje kommando foltôging en elimineren de needsaak foar foarheaksels wsl, Windows-paden oersette yn WSL-paden. Basis easken foar skelpen:

  • Foar elk Linux-kommando moat d'r ien funksje-wrapper wêze mei deselde namme.
  • De shell moat de Windows-paden werkenne dy't as arguminten trochjûn binne en se konvertearje nei WSL-paden.
  • De shell moat roppe wsl mei it passende Linux-kommando nei elke pipeline-ynfier en trochjaan fan alle kommandorigelarguminten dy't trochjûn binne oan 'e funksje.

Sûnt dit patroan kin wurde tapast op elk kommando, kinne wy ​​abstrahere de definysje fan dizze wrappers en dynamysk generearje se út in list mei kommando's te ymportearjen.

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

List fan $command definiearret ymportkommando's. Wy generearje dan dynamysk in funksje-wrapper foar elk fan har mei it kommando Invoke-Expression (troch earst alle aliassen te ferwiderjen dy't yn striid binne mei de funksje).

De funksje iterearret oer kommandorigelarguminten, bepaalt Windows-paden mei kommando's Split-Path и Test-Pathen dan konvertearret dizze paden nei WSL paden. Wy rinne de paden troch in helpfunksje Format-WslArgument, dy't wy letter sille definiearje. It ûntkomt spesjale tekens lykas spaasjes en haakjes dy't oars ferkeard ynterpretearre wurde soene.

As lêste, wy oerbringe wsl pipeline-ynfier en alle arguminten foar kommandorigel.

Mei dizze wrappers kinne jo jo favorite Linux-kommando's op in natuerlikere manier neame sûnder in foarheaksel ta te foegjen wsl en sûnder soargen oer hoe't de paden wurde omboud:

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

De basis set fan kommando's wurdt hjir werjûn, mar jo kinne in shell meitsje foar elk Linux-kommando troch it gewoan ta te foegjen oan 'e list. As jo ​​tafoegje dizze koade oan dyn profyl PowerShell, dizze kommando's sille foar jo beskikber wêze yn elke PowerShell-sesje, krekt as native kommando's!

Standertynstellings

Yn Linux is it gewoanlik om aliasen en/of omjouwingsfariabelen te definiearjen yn oanmeldprofilen, it ynstellen fan standertparameters foar faak brûkte kommando's (bygelyks, alias ls=ls -AFh of export LESS=-i). Ien fan 'e neidielen fan proxying fia in net-ynteraktive shell wsl.exe - dat de profilen net laden binne, sadat dizze opsjes standert net beskikber binne (d.w.s. ls yn WSL en wsl ls sil him oars gedrage mei de hjirboppe definieare alias).

PowerShell biedt $PSDefaultParameterValues, in standertmeganisme foar it definiearjen fan standertparameters, mar allinich foar cmdlets en avansearre funksjes. Fansels kinne wy ​​avansearre funksjes meitsje fan ús skelpen, mar dit yntroduseart ûnnedige komplikaasjes (bygelyks, PowerShell korrelearret dielparameternammen (bygelyks, -a korrelearret mei -ArgumentList), dy't yn konflikt komme mei Linux-kommando's dy't dielnammen as arguminten nimme), en de syntaksis foar it definiearjen fan standertynstellingen sil net de meast geskikte wêze (it definiearjen fan standertarguminten fereasket de namme fan 'e parameter yn 'e kaai, net allinich de kommandonamme).

Lykwols, mei in lichte wiziging oan ús skulpen, kinne wy ​​implementearje in model fergelykber mei $PSDefaultParameterValues, en ynskeakelje standert opsjes foar Linux kommando's!

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

Troch te ferstjoeren $WslDefaultParameterValues nei it kommando rigel, wy stjoere parameters fia wsl.exe. It folgjende lit sjen hoe't jo ynstruksjes tafoegje oan jo PowerShell-profyl om standertynstellingen te konfigurearjen. No kinne wy ​​it dwaan!

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

Sûnt de parameters wurde modelearre na $PSDefaultParameterValues, Do kinst it is maklik om se út te skeakeljen tydlik troch it ynstallearjen fan de kaai "Disabled" yn betsjutting $true. In ekstra foardiel fan in aparte hash-tabel is de mooglikheid om út te skeakeljen $WslDefaultParameterValues apart fan $PSDefaultParameterValues.

Argumint foltôging

PowerShell lit jo arguminttrailers registrearje mei it kommando Register-ArgumentCompleter. Bash hat machtich programmearbere ark foar automatyske foltôging. WSL lit jo bash skilje fan PowerShell. As wy argumintfoltôgings kinne registrearje foar ús PowerShell-funksje-wrappers en bash oproppe om de foltôgings te generearjen, krije wy folsleine argumintfoltôging mei deselde krektens as bash sels!

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

De koade is in bytsje ticht sûnder guon fan 'e ynterne funksjes fan bash te begripen, mar yn prinsipe is wat wy dogge dit:

  • Registrearje in argumintfoltôger foar al ús funksje-wrappers troch in list troch te jaan $commands yn parameter -CommandName foar Register-ArgumentCompleter.
  • Wy bringe elk kommando yn kaart nei de shellfunksje dy't bash brûkt foar autofoltôging (om spesifikaasjes foar autofoltôging te definiearjen, brûkt bash $F, ôfkoarting foar complete -F <FUNCTION>).
  • PowerShell Arguminten omsette $wordToComplete, $commandAst и $cursorPosition yn it formaat ferwachte troch bash's autofoljefunksjes neffens de spesifikaasjes programmearbere automatyske foltôging bash.
  • Wy meitsje in kommandorigel om nei oer te setten wsl.exe, dy't derfoar soarget dat it miljeu goed ynsteld is, ropt de passende auto-foltôgingsfunksje op en jout de resultaten op rigel foar line.
  • Dan belje wy wsl mei de kommandorigel skiede wy de útfier troch rigelskieders en generearje foar elk CompletionResults, sortearje se en ûntsnappe tekens lykas spaasjes en heakjes dy't oars ferkeard ynterpretearre wurde.

As gefolch, ús Linux kommando shells sille brûke krekt deselde autocompletion as bash! Bygelyks:

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

Elke autofoltôging leveret wearden spesifyk foar it foarige argumint, it lêzen fan konfiguraasjegegevens lykas bekende hosts fan WSL!

<TAB> sil troch de parameters fytse. <Ctrl + пробел> sil alle beskikbere opsjes sjen litte.

Plus, om't wy no bash-autofoltôging hawwe, kinne jo Linux-paden direkt yn PowerShell autofolje!

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

Yn gefallen dêr't bash-autofoltôging gjin resultaten produseart, giet PowerShell werom nei de standert Windows-paden fan it systeem. Sa kinne jo yn 'e praktyk beide paden tagelyk brûke nei eigen goedtinken.

konklúzje

Mei PowerShell en WSL kinne wy ​​Linux-kommando's yntegrearje yn Windows as native applikaasjes. D'r is gjin need om te sykjen nei Win32-builds of Linux-hulpprogramma's of jo workflow te ûnderbrekken troch nei in Linux-shell te gean. Krekt ynstallearje WSL, konfigurearje PowerShell profyl и list de kommando's dy't jo ymportearje wolle! Rike autofoltôging foar Linux- en Windows-kommandoparameters en bestânpaden is funksjonaliteit dy't hjoeddedei net iens beskikber is yn native Windows-kommando's.

De folsleine hjirboppe beskreaune boarnekoade, lykas ekstra rjochtlinen foar it opnimmen fan it yn jo workflow, is beskikber hjir.

Hokker Linux-kommando's fine jo it nuttichst? Hokker oare mienskiplike dingen ûntbrekke as jo wurkje yn Windows? Skriuw yn de kommentaren of op GitHub!

Boarne: www.habr.com

Add a comment