Entegre kòmandman Linux nan Windows lè l sèvi avèk PowerShell ak WSL

Yon kesyon tipik soti nan devlopè Windows: "Poukisa toujou pa gen okenn <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Kit se yon swipe pwisan less oswa zouti abitye grep oswa sed, Devlopè Windows vle aksè fasil nan kòmandman sa yo nan travay chak jou yo.

Windows Subsystem pou Linux (WSL) te fè yon gwo etap pi devan nan sans sa a. Li pèmèt ou rele kòmandman Linux soti nan Windows pa proxy yo atravè wsl.exe (pa egzanp, wsl ls). Malgre ke sa a se yon amelyorasyon siyifikatif, opsyon sa a soufri de yon kantite dezavantaj.

  • Ubiquitous adisyon wsl fatigan ak anòmal.
  • Chemen Windows nan agiman pa toujou travay paske backslash yo entèprete kòm karaktè chape olye ke separasyon anyè.
  • Chemen Windows nan agiman yo pa tradui nan pwen mòn ki koresponn lan nan WSL.
  • Anviwònman defo yo pa respekte nan pwofil WSL ak alyas ak varyab anviwònman.
  • Finisyon chemen Linux pa sipòte.
  • Fini kòmand pa sipòte.
  • Fini agiman pa sipòte.

Kòm yon rezilta, kòmandman Linux yo trete tankou sitwayen dezyèm klas anba Windows-epi yo pi difisil pou itilize pase kòmandman natif natal. Pou egalize dwa yo, li nesesè pou rezoud pwoblèm ki nan lis la.

Anbalaj fonksyon PowerShell

Avèk anvlòp fonksyon PowerShell, nou ka ajoute fini kòmand epi elimine nesesite pou prefiks wsl, tradui chemen Windows nan chemen WSL. Kondisyon debaz pou kokiy:

  • Pou chak kòmand Linux dwe genyen yon sèl fonksyon wrapper ak menm non an.
  • Koki a dwe rekonèt chemen Windows yo pase kòm agiman epi konvèti yo nan chemen WSL.
  • Koki a ta dwe rele wsl ak kòmandman Linux apwopriye a nan nenpòt ki opinyon tiyo ak pase nenpòt agiman liy lòd pase nan fonksyon an.

Depi modèl sa a ka aplike nan nenpòt kòmand, nou ka abstrè definisyon an nan anbalaj sa yo ak dinamik jenere yo nan yon lis kòmandman yo enpòte.

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

Lis $command defini kòmandman enpòte. Nou Lè sa a, dinamik jenere yon wrapper fonksyon pou chak nan yo lè l sèvi avèk lòd la Invoke-Expression (nan premye retire nenpòt alyas ki ta konfli ak fonksyon an).

Fonksyon an itere sou agiman liy kòmand, detèmine chemen Windows lè l sèvi avèk kòmandman Split-Path и Test-Pathak Lè sa a, konvèti chemen sa yo nan chemen WSL. Nou kouri chemen yo atravè yon fonksyon asistan Format-WslArgument, ke nou pral defini pita. Li chape karaktè espesyal tankou espas ak parantèz ki ta ka mal entèprete.

Finalman, nou transmèt wsl opinyon tiyo ak nenpòt agiman liy lòd.

Avèk anvlòp sa yo ou ka rele kòmandman Linux pi renmen ou yo nan yon fason ki pi natirèl san yo pa ajoute yon prefiks wsl epi san enkyete sou fason chemen yo konvèti:

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

Ansanm debaz kòmandman yo montre isit la, men ou ka kreye yon koki pou nenpòt kòmand Linux lè w tou senpleman ajoute li nan lis la. Si ou ajoute kòd sa a sou ou pwofil PowerShell, kòmandman sa yo ap disponib pou ou nan chak sesyon PowerShell, menm jan ak kòmandman natif natal!

Anviwònman Default

Nan Linux, li komen pou defini alyas ak/oswa varyab anviwònman nan pwofil konekte, mete paramèt default pou kòmandman yo itilize souvan (pa egzanp, alias ls=ls -AFh oswa export LESS=-i). Youn nan dezavantaj yo nan proxy atravè yon koki ki pa entèaktif wsl.exe - ke pwofil yo pa chaje, kidonk opsyon sa yo pa disponib pa default (sa vle di. ls nan WSL ak wsl ls pral konpòte yon fason diferan ak alyas defini pi wo a).

PowerShell bay $PSDefaultParameterValues, yon mekanis estanda pou defini paramèt default, men sèlman pou cmdlets ak fonksyon avanse. Natirèlman, nou ka fè fonksyon avanse soti nan kokiy nou yo, men sa a entwodui konplikasyon ki pa nesesè (pa egzanp, PowerShell korelasyon non paramèt pasyèl (pa egzanp, -a korelasyon ak -ArgumentList), ki pral konfli ak kòmandman Linux ki pran non pasyèl kòm agiman), ak sentaks la pou defini valè default pa pral pi apwopriye (agiman defo mande pou non an nan paramèt nan kle a, pa sèlman non an kòmand) .

Sepandan, ak yon ti modifikasyon nan kokiy nou an, nou ka aplike yon modèl ki sanble ak $PSDefaultParameterValues, ak pèmèt opsyon default pou kòmandman 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 ' ')
    }
}

Pase $WslDefaultParameterValues nan liy lòd la, nou voye paramèt atravè wsl.exe. Sa ki anba la a montre kijan pou ajoute enstriksyon sou pwofil PowerShell ou a pou konfigirasyon paramèt default yo. Koulye a, nou ka fè li!

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

Depi paramèt yo modle apre $PSDefaultParameterValues, Ou kapab li fasil pou enfim yo tanporèman pa enstale kle a "Disabled" nan sans $true. Yon benefis adisyonèl nan yon tab hash separe se kapasite nan enfim $WslDefaultParameterValues separeman de $PSDefaultParameterValues.

Agiman fini

PowerShell pèmèt ou anrejistre trelè agiman lè l sèvi avèk lòd la Register-ArgumentCompleter. Bash gen pwisan zouti pwogramasyon oto-konpletman. WSL pèmèt ou rele bash nan PowerShell. Si nou ka anrejistre konpletman agiman pou wrappers fonksyon PowerShell nou yo epi rele bash pou jenere konpletman yo, nou jwenn konpletman agiman konplè ak menm presizyon ak bash tèt li!

# 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 la se yon ti jan dans san yo pa konprann kèk nan fonksyon entèn bash a, men fondamantalman sa nou fè se sa a:

  • Anrejistre yon konpletè agiman pou tout wrappers fonksyon nou yo pa pase yon lis $commands nan paramèt -CommandName pou Register-ArgumentCompleter.
  • Nou mete chak kòmandman nan fonksyon koki ke bash itilize pou ranpli otokonpliman (pou defini espesifikasyon otokonplesyon, bash itilize $F, Abreviyasyon pou complete -F <FUNCTION>).
  • Konvèti Agiman PowerShell $wordToComplete, $commandAst и $cursorPosition nan fòma fonksyon otokonplesyon bash yo te espere selon espesifikasyon yo pwogramasyon oto-konpletman bash.
  • Nou konpoze yon liy kòmand pou transfere nan wsl.exe, ki asire ke anviwònman an mete kanpe kòrèkteman, rele fonksyon oto-konpleman apwopriye a, ak pwodiksyon rezilta yo nan mòd liy-pa-liy.
  • Lè sa a, nou rele wsl ak liy lòd la, nou separe pwodiksyon an pa liy separateur ak jenere pou chak CompletionResults, klasman yo epi chape karaktè tankou espas ak parantèz ki ta ka mal entèprete.

Kòm yon rezilta, kokiy kòmand Linux nou an pral sèvi ak egzakteman otokonplesyon an menm jan ak bash! Pa egzanp:

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

Chak otokonplesyon bay valè espesifik nan agiman anvan an, li done konfigirasyon tankou lame li te ye nan WSL!

<TAB> pral sikile nan paramèt yo. <Ctrl + пробел> pral montre tout opsyon ki disponib.

Anplis, depi kounye a nou gen bash autocompletion, ou ka ranpli otomatik chemen Linux dirèkteman nan PowerShell!

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

Nan ka kote autocompletion bash pa pwodui okenn rezilta, PowerShell retounen nan chemen Windows default sistèm yo. Se konsa, nan pratik, ou ka ansanm itilize tou de chemen nan diskresyon ou.

Konklizyon

Sèvi ak PowerShell ak WSL, nou ka entegre kòmandman Linux nan Windows kòm aplikasyon natif natal. Pa gen okenn nesesite pou chèche Win32 bati oswa sèvis piblik Linux oswa entèwonp workflow ou lè w ale nan yon kokiy Linux. Jis enstale WSL, konfigirasyon Pwofil PowerShell и lis kòmandman ou vle enpòte! Rich otocompletion pou Linux ak Windows paramèt kòmand ak chemen dosye se fonksyonalite ki pa menm disponib nan kòmandman Windows natif natal jodi a.

Kòd sous konplè ki dekri pi wo a, ansanm ak gid adisyonèl pou enkòpore li nan workflow ou a, ki disponib isit la.

Ki kòmandman Linux ou jwenn pi itil? Ki lòt bagay komen ki manke lè w ap travay nan Windows? Ekri nan kòmantè yo oswa sou GitHub!

Sous: www.habr.com

Add nouvo kòmantè