Integratioun vu Linux Kommandoen a Windows mat PowerShell a WSL

Eng typesch Fro vun Windows Entwéckler: "Firwat gëtt et nach ëmmer keen <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Ob et e mächtege Swipe ass less oder vertraut Tools grep oder sed, Windows Entwéckler wëllen einfach Zougang zu dëse Kommandoen an hirer alldeeglecher Aarbecht.

Windows Subsystem fir Linux (WSL) huet an deem Sënn e grousse Schrëtt no vir gemaach. Et erlaabt Iech Linux Befehle vu Windows ze ruffen andeems se se duerch proxyen wsl.exe (zum Beispill, wsl ls). Obwuel dëst eng bedeitend Verbesserung ass, leiden dës Optioun ënner enger Rei vun Nodeeler.

  • Ubiquitär Zousatz wsl langweileg an onnatierlech.
  • Windows Weeër an Argumenter funktionnéieren net ëmmer, well Réckschnëtter ginn als Fluchtzeechen interpretéiert anstatt Verzeichnungsseparatoren.
  • Windows Weeër an Argumenter ginn net op den entspriechende Mountpunkt am WSL iwwersat.
  • Standardastellunge ginn net an WSL Profiler mat Aliasen an Ëmfeldvariablen respektéiert.
  • Linux Wee Fäerdegstellung gëtt net ënnerstëtzt.
  • Kommando Ofschloss gëtt net ënnerstëtzt.
  • Argument Fäerdegstellung gëtt net ënnerstëtzt.

Als Resultat ginn Linux Kommandoen wéi zweetklasseg Bierger ënner Windows behandelt - a si méi schwéier ze benotzen wéi gebierteg Kommandoen. Fir hir Rechter ausgläichen, ass et néideg déi opgezielt Problemer ze léisen.

PowerShell Funktioun wrappers

Mat PowerShell Funktioun Wrapper, kënne mir Kommando Fäerdegstellung an eliminéiert de Besoin fir Präfixe wsl, Windows Weeër an WSL Weeër iwwersetzen. Basis Ufuerderunge fir Muschelen:

  • Fir all Linux Kommando muss et e Funktiounswrapper mam selwechten Numm sinn.
  • D'Shell muss d'Windows Weeër erkennen, déi als Argumenter passéiert sinn an se op WSL Weeër konvertéieren.
  • D'Schuel soll ruffen wsl mat dem passenden Linux Kommando op all Pipeline-Input a gitt all Kommandozeilargumenter un d'Funktioun weiderginn.

Zënter datt dëst Muster op all Kommando applizéiert ka ginn, kënne mir d'Definitioun vun dësen Wrapper abstrakt an dynamesch generéieren aus enger Lëscht vun Kommandoen fir z'importéieren.

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

Lëscht $command definéiert Import Kommandoen. Mir generéieren dann dynamesch eng Funktiounswrapper fir jidderee vun hinnen mam Kommando Invoke-Expression (duerch d'éischt all Aliasen ze läschen, déi mat der Funktioun konflikt wieren).

D'Funktioun iteréiert iwwer Kommandozeilargumenter, bestëmmt Windows Weeër mat Kommandoen Split-Path и Test-Pathan dann dës Weeër ze WSL Weeër ëmgerechent. Mir lafen d'Weeër duerch eng Hëllefsfunktioun Format-WslArgument, déi mir spéider definéieren. Et entkommt speziell Zeechen wéi Plazen a Klammern, déi soss falsch interpretéiert ginn.

Schlussendlech vermëttele mir wsl Pipeline Input an all Kommandozeil Argumenter.

Mat dëse Wrapper kënnt Dir Är Liiblings Linux Kommandoen op eng méi natierlech Manéier nennen ouni e Präfix ze addéieren wsl an ouni Suergen iwwer wéi d'Weeër ëmgewandelt ginn:

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

De Basisset vu Befehle gëtt hei gewisen, awer Dir kënnt eng Shell fir all Linux Kommando erstellen andeems Dir se einfach op d'Lëscht bäidréit. Wann Dir dëse Code op Är Profil PowerShell, dës Kommandoe wäerte fir Iech an all PowerShell Sessioun verfügbar sinn, sou wéi gebierteg Kommandoen!

Standard Optiounen

Am Linux ass et üblech Aliasen an / oder Ëmfeldvariablen a Loginprofiler ze definéieren, Standardparameter fir dacks benotzte Kommandoen ze setzen (zum Beispill, alias ls=ls -AFh oder export LESS=-i). Ee vun den Nodeeler vum Proxy duerch eng net-interaktiv Shell wsl.exe - datt d'Profiler net gelueden sinn, sou datt dës Optiounen net als Standard verfügbar sinn (d.h. ls an WSL an wsl ls wäert sech anescht behuelen mam Alias ​​uewen definéiert).

PowerShell bitt $PSDefaultParameterValues, e Standardmechanismus fir Standardparameter ze definéieren, awer nëmme fir cmdlets a fortgeschratt Funktiounen. Natierlech kënne mir fortgeschratt Funktiounen aus eise Muschelen maachen, awer dëst féiert onnéideg Komplikatioune vir (zum Beispill, PowerShell korreléiert deelweis Parameternimm (zum Beispill, -a korreléiert mat -ArgumentList), déi mat Linux Befehle Konflikt, déi deelweis Nimm als Argumenter huelen), an d'Syntax fir Standardwäerter ze definéieren wäert net déi passend sinn (Standardargumenter erfuerderen den Numm vum Parameter am Schlëssel, net nëmmen de Kommandonumm) .

Wéi och ëmmer, mat enger liichter Ännerung vun eise Muschelen, kënne mir e Modell ähnlech wéi $PSDefaultParameterValues, an aktivéiert Standardoptiounen fir Linux Kommandoen!

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

Passéieren $WslDefaultParameterValues op de Kommando Linn, mir schécken Parameteren iwwer wsl.exe. Déi folgend weist wéi Dir Instruktiounen op Äre PowerShell Profil addéiere kënnt fir Standardastellungen ze konfiguréieren. Elo kënne mir et maachen!

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

Well d'Parameteren sinn modelléiert no $PSDefaultParameterValues, Du kanns et ass einfach se auszeschalten temporär andeems Dir de Schlëssel installéiert "Disabled" an Bedeitung $true. En zousätzleche Virdeel vun enger separater Hash-Tabelle ass d'Fäegkeet fir auszeschalten $WslDefaultParameterValues getrennt vun $PSDefaultParameterValues.

Argument Ofschloss

PowerShell erlaabt Iech Argument Trailer mat dem Kommando ze registréieren Register-ArgumentCompleter. Bash huet mächteg programméierbar automatesch Fäerdegstellungsinstrumenter. WSL erlaabt Iech Bash vu PowerShell ze ruffen. Wa mir Argumenterfäegkeete fir eis PowerShell Funktiounswrapper registréiere kënnen an Bash ruffen fir d'Ergänzunge ze generéieren, kréie mir voll Argumenterfähegkeet mat der selwechter Präzisioun wéi Bash selwer!

# 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 Code ass e bëssen dicht ouni e puer vun den internen Funktiounen vum Bash ze verstoen, awer am Fong wat mir maachen ass dëst:

  • En Argument Kompletter registréiert fir all eis Funktiounswrappers andeems Dir eng Lëscht passéiert $commands am Parameter -CommandName fir Register-ArgumentCompleter.
  • Mir mapen all Kommando op d'Shell Funktioun déi Bash fir Autocompletion benotzt (fir d'Autocompletion Spezifikatioune ze definéieren, Bash benotzt $F, Ofkierzung fir complete -F <FUNCTION>).
  • Konvertéieren PowerShell Argumenter $wordToComplete, $commandAst и $cursorPosition an de Format erwaart vun de Bash Autocompletion Funktiounen no de Spezifikatioune programméierbar automatesch Fäerdegstellung bash.
  • Mir komponéieren eng Kommandozeil fir ze transferéieren wsl.exe, déi garantéiert datt d'Ëmfeld richteg ageriicht ass, rifft déi entspriechend Auto-Fäerdegstellungsfunktioun un, an d'Resultater op Linn-fir-Linn Manéier eraus.
  • Da ruffe mir wsl mat der Kommandozeil trennen mir d'Ausgang duerch Zeilseparatoren a generéiere fir all CompletionResults, sortéieren se an entgoe Charaktere wéi Plazen a Klammern déi soss falsch interpretéiert ginn.

Als Resultat wäerten eis Linux Kommando Shells genau déiselwecht Autocompletion benotzen wéi Bash! Zum Beispill:

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

All Autocompletion liwwert Wäerter spezifesch fir dat viregt Argument, liest Konfiguratiounsdaten wéi bekannte Hosten vu WSL!

<TAB> wäert Zyklus duerch d'Parameteren. <Ctrl + пробел> wäert all verfügbar Optiounen weisen.

Plus, well mir elo bash Autocompletion hunn, kënnt Dir Linux Weeër direkt an PowerShell automatesch ausfëllen!

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

A Fäll wou d'Bash Autocompletion keng Resultater produzéiert, gëtt PowerShell op de System Standard Windows Weeër zréck. Also, an der Praxis, kënnt Dir gläichzäiteg béid Weeër no Ärem Diskretioun benotzen.

Konklusioun

Mat PowerShell a WSL kënne mir Linux Kommandoen an Windows als gebierteg Uwendungen integréieren. Et ass net néideg fir Win32 Builds oder Linux Utilities ze sichen oder Äre Workflow ze ënnerbriechen andeems Dir op eng Linux Shell gitt. Just installéieren WSL, konfiguréieren PowerShell Profil и Lëscht d'Befehle déi Dir importéiere wëllt! Rich Autocompletion fir Linux a Windows Kommandoparameter a Dateiweeër ass Funktionalitéit déi haut net emol an native Windows Kommandoen verfügbar ass.

De komplette Quellcode hei uewen beschriwwen, souwéi zousätzlech Richtlinnen fir en an Ärem Workflow z'integréieren, ass verfügbar hei.

Wéi eng Linux Kommandoen fannt Dir am nëtzlechsten? Wat aner gemeinsam Saachen fehlen wann Dir a Windows schafft? Schreift an de Kommentaren oder op GitHub!

Source: will.com

Setzt e Commentaire