Pag-integrate sa mga command sa Linux ngadto sa Windows gamit ang PowerShell ug WSL

Usa ka kasagaran nga pangutana gikan sa mga nag-develop sa Windows: "Ngano nga wala pa <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Kung kini usa ka kusog nga pag-swipe less o pamilyar nga mga himan grep o sed, Gusto sa mga developer sa Windows nga dali nga ma-access kini nga mga sugo sa ilang adlaw-adlaw nga trabaho.

Windows Subsystem para sa Linux (WSL) nakahimo ug dakong lakang sa unahan niining bahina. Gitugotan ka niini nga tawagan ang mga mando sa Linux gikan sa Windows pinaagi sa pag-proxy niini wsl.exe (pananglitan, wsl ls). Bisan kung kini usa ka hinungdanon nga pag-uswag, kini nga kapilian adunay daghang mga kakulangan.

  • Ubiquitous nga pagdugang wsl kapoy ug dili natural.
  • Ang mga agianan sa Windows sa mga argumento dili kanunay molihok tungod kay ang mga backslashes gihubad nga mga karakter sa pag-ikyas kaysa mga separator sa direktoryo.
  • Ang mga agianan sa Windows sa mga argumento wala gihubad ngadto sa katugbang nga mount point sa WSL.
  • Ang mga default nga setting wala gitahud sa mga profile sa WSL nga adunay mga alyas ug mga variable sa palibot.
  • Dili suportado ang pagkompleto sa agianan sa Linux.
  • Ang pagkompleto sa sugo wala gisuportahan.
  • Ang pagkompleto sa argumento wala gisuportahan.

Isip resulta, ang Linux commands gitratar sama sa second-class citizen ubos sa Windows—ug mas lisod gamiton kay sa lumad nga mga command. Aron maparehas ang ilang mga katungod, kinahanglan nga masulbad ang nalista nga mga problema.

PowerShell function wrapper

Uban sa PowerShell function wrappers, makadugang mi sa pagkompleto sa command ug mawagtang ang panginahanglan sa mga prefix wsl, paghubad sa mga agianan sa Windows ngadto sa mga agianan sa WSL. Panguna nga mga kinahanglanon alang sa mga kabhang:

  • Alang sa matag command sa Linux kinahanglan adunay usa ka function wrapper nga adunay parehas nga ngalan.
  • Kinahanglang mailhan sa kabhang ang mga agianan sa Windows nga gipasa ingon mga argumento ug i-convert kini sa mga agianan sa WSL.
  • Ang kabhang kinahanglan nga motawag wsl uban ang angay nga Linux command sa bisan unsang pipeline input ug pagpasa sa bisan unsang command line arguments nga gipasa sa function.

Tungod kay kini nga sumbanan mahimong magamit sa bisan unsang command, mahimo natong abstract ang kahulugan niini nga mga wrapper ug dinamikong pagmugna niini gikan sa listahan sa mga sugo nga i-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 ' ')
    }
}
"@
}

Listahan sa $command naghubit sa mga sugo sa import. Kami dayon nagmugna og usa ka function wrapper alang sa matag usa kanila gamit ang command Invoke-Expression (pinaagi sa pagtangtang una sa bisan unsang mga alias nga mosumpaki sa function).

Ang function nag-usab sa mga argumento sa command line, nagtino sa mga agianan sa Windows gamit ang mga command Split-Path и Test-Pathug dayon i-convert kini nga mga agianan ngadto sa mga agianan sa WSL. Gipadagan namon ang mga agianan pinaagi sa usa ka function sa katabang Format-WslArgument, nga atong ipasabut sa ulahi. Kini makalingkawas sa mga espesyal nga karakter sama sa mga espasyo ug mga parentesis nga mahimong masaypan sa pagsabot.

Sa katapusan, among gipahayag wsl pipeline input ug bisan unsang mga argumento sa command line.

Uban niini nga mga wrapper mahimo nimong tawagan ang imong paborito nga mga sugo sa Linux sa mas natural nga paagi nga walay pagdugang og prefix wsl ug sa walay pagkabalaka kon sa unsang paagi ang mga dalan nakabig:

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

Ang sukaranan nga hugpong sa mga sugo gipakita dinhi, apan makahimo ka og kabhang alang sa bisan unsang Linux command pinaagi lamang sa pagdugang niini sa listahan. Kung imong idugang kini nga code sa imong profile PowerShell, kini nga mga sugo magamit nimo sa matag sesyon sa PowerShell, sama sa lumad nga mga sugo!

Default nga mga Setting

Sa Linux, kasagaran ang paghubit sa mga alyas ug/o mga variable sa palibot sa mga profile sa pag-login, pagtakda sa mga default nga parameter alang sa kanunay nga gigamit nga mga sugo (pananglitan, alias ls=ls -AFh o export LESS=-i). Usa sa mga disadvantages sa proxying pinaagi sa usa ka non-interactive nga kabhang wsl.exe - nga ang mga profile wala gikarga, mao nga kini nga mga kapilian dili magamit pinaagi sa default (ie. ls sa WSL ug wsl ls lahi ang paggawi sa alyas nga gihubit sa ibabaw).

Naghatag ang PowerShell $PSDefaultParameterValues, usa ka sumbanan nga mekanismo alang sa pagtino sa default nga mga parameter, apan alang lamang sa mga cmdlet ug advanced nga mga gimbuhaton. Siyempre, makahimo kami og mga advanced function gikan sa among mga shell, apan kini nagpaila sa dili kinahanglan nga mga komplikasyon (pananglitan, ang PowerShell nag-correlate sa partial parameter nga mga ngalan (pananglitan, -a nakig-uban sa -ArgumentList), nga magkasumpaki sa mga sugo sa Linux nga nagkuha sa partial nga mga ngalan isip mga argumento), ug ang syntax alang sa pagtino sa default nga mga bili dili mao ang labing tukma (ang default nga mga argumento nagkinahanglan sa ngalan sa parameter sa yawe, dili lamang sa command name) .

Bisan pa, sa usa ka gamay nga pagbag-o sa among mga kabhang, mahimo namon nga ipatuman ang usa ka modelo nga parehas sa $PSDefaultParameterValues, ug i-enable ang default nga mga opsyon alang sa Linux commands!

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

Paglabay $WslDefaultParameterValues sa command line, nagpadala kami og mga parameter pinaagi sa wsl.exe. Ang mosunud nagpakita kung giunsa pagdugang mga panudlo sa imong profile sa PowerShell aron ma-configure ang mga default nga setting. Karon mahimo na nato kini!

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

Tungod kay ang mga parameter gimodelo pagkahuman $PSDefaultParameterValuesmahimo nimo sayon ​​ra ang pag-disable kanila temporaryo pinaagi sa pag-instalar sa yawe "Disabled" ngadto sa kahulogan $true. Ang usa ka dugang nga kaayohan sa usa ka bulag nga lamesa sa hash mao ang abilidad sa pag-disable $WslDefaultParameterValues bulag sa $PSDefaultParameterValues.

Pagkompleto sa argumento

Gitugotan ka sa PowerShell nga magparehistro sa mga trailer sa argumento gamit ang command Register-ArgumentCompleter. Ang Bash adunay kusog programmable auto-completion himan. Gitugotan ka sa WSL nga tawagan ang bash gikan sa PowerShell. Kung mahimo namon nga irehistro ang mga pagkompleto sa argumento alang sa among PowerShell function wrapper ug tawagan ang bash aron makamugna ang mga pagkompleto, makuha namon ang bug-os nga pagkompleto sa argumento sa parehas nga katukma sama sa bash mismo!

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

Ang code medyo dasok nga wala makasabut sa pipila ka mga internal nga gimbuhaton sa bash, apan sa panguna kung unsa ang among gibuhat mao kini:

  • Pagrehistro sa usa ka argumento completer para sa tanan namong function wrapper pinaagi sa pagpasa sa usa ka listahan $commands sa parameter -CommandName alang sa Register-ArgumentCompleter.
  • Gimapa namo ang matag command ngadto sa shell function nga gigamit sa bash para sa autocompletion (aron ipasabot ang autocompletion specifications, bash use $F, minubo sa complete -F <FUNCTION>).
  • Pag-convert sa PowerShell Arguments $wordToComplete, $commandAst и $cursorPosition ngadto sa format nga gipaabot sa bash's autocompletion functions sumala sa specifications programmable nga auto-completion bash.
  • Naghimo kami usa ka linya sa mando aron mabalhin wsl.exe, nga nagsiguro nga ang palibot gipahimutang sa husto, nagtawag sa angay nga auto-completion function, ug nagpagawas sa mga resulta sa line-by-line nga paagi.
  • Unya nanawag mi wsl uban sa command line, gibulag namo ang output pinaagi sa mga separator sa linya ug nagmugna alang sa matag usa CompletionResults, paghan-ay niini ug pag-eskapo sa mga karakter sama sa mga espasyo ug parentesis nga mahimong masaypan sa pagsabot.

Ingon usa ka sangputanan, ang among Linux command shell mogamit sa parehas nga autocompletion sama sa bash! Pananglitan:

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

Ang matag autocompletion naghatag mga kantidad nga piho sa miaging argumento, nagbasa sa datos sa pagsumpo sama sa nailhan nga mga host gikan sa WSL!

<TAB> mag-cycle pinaagi sa mga parameter. <Ctrl + пробел> ipakita ang tanan nga magamit nga mga kapilian.

Dugang pa, tungod kay aduna na kitay bash autocompletion, mahimo nimong i-autocomplete ang mga agianan sa Linux direkta sa PowerShell!

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

Sa mga kaso diin ang bash autocompletion walay resulta, ang PowerShell mobalik sa sistema sa default nga mga agianan sa Windows. Sa ingon, sa praktis, mahimo nimong dungan nga magamit ang duha nga mga agianan sa imong pagkabuotan.

konklusyon

Gamit ang PowerShell ug WSL, mahimo natong i-integrate ang Linux commands ngadto sa Windows isip native applications. Dili na kinahanglan pangitaon ang Win32 builds o Linux utilities o i-interrupt ang imong workflow pinaagi sa pag-adto sa Linux shell. Basta i-install ang WSL, i-configure Profile sa PowerShell и ilista ang mga sugo nga gusto nimong i-import! Ang dato nga autocompletion para sa Linux ug Windows command parameters ug file paths kay functionality nga dili gani magamit sa native Windows commands karon.

Ang bug-os nga source code nga gihulagway sa ibabaw, ingon man ang dugang nga mga panudlo alang sa pag-apil niini sa imong workflow, magamit dinhi.

Unsa nga mga sugo sa Linux ang imong nakita nga labing mapuslanon? Unsa ang ubang kasagarang mga butang nga kulang kung nagtrabaho sa Windows? Isulat sa mga komento o sa GitHub!

Source: www.habr.com

Idugang sa usa ka comment