Að samþætta Linux skipanir í Windows með PowerShell og WSL

Dæmigerð spurning frá Windows forriturum: „Af hverju er enn engin <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Hvort sem það er öflugt högg less eða kunnugleg verkfæri grep eða sed, Windows forritarar vilja greiðan aðgang að þessum skipunum í daglegu starfi sínu.

Windows undirkerfi fyrir Linux (WSL) hefur stigið stórt skref fram á við í þessum efnum. Það gerir þér kleift að hringja í Linux skipanir frá Windows með því að senda þær í gegnum proxy wsl.exe (t.d. wsl ls). Þrátt fyrir að þetta sé umtalsverð framför, þá hefur þessi valkostur ýmsa ókosti.

  • Alls staðar nálæg viðbót wsl leiðinlegt og óeðlilegt.
  • Windows slóðir í rökum virka ekki alltaf vegna þess að bakskástrik eru túlkuð sem escape stafir frekar en möppuskil.
  • Windows slóðir í rökum eru ekki þýddar á samsvarandi tengipunkt í WSL.
  • Sjálfgefnar stillingar eru ekki virtar í WSL sniðum með samnöfnum og umhverfisbreytum.
  • Ekki er stutt við að ljúka Linux slóð.
  • Ekki er stutt við að ljúka skipunum.
  • Ekki er stutt við að ljúka rökum.

Þar af leiðandi er farið með Linux skipanir eins og annars flokks borgara undir Windows - og eru erfiðari í notkun en innfæddar skipanir. Til að jafna réttindi þeirra er nauðsynlegt að leysa upptalin vandamál.

PowerShell virka umbúðir

Með PowerShell virka umbúðum getum við bætt við að ljúka skipunum og útrýma þörfinni fyrir forskeyti wsl, þýða Windows slóðir yfir á WSL slóðir. Grunnkröfur fyrir skeljar:

  • Fyrir hverja Linux skipun verður að vera einn virka umbúðir með sama nafni.
  • Skelin verður að þekkja Windows slóðirnar sem sendar eru sem rök og breyta þeim í WSL slóðir.
  • Skelin ætti að kalla wsl með viðeigandi Linux skipun í hvaða leiðsluinntak sem er og senda hvaða skipanalínurök sem eru send til fallsins.

Þar sem hægt er að nota þetta mynstur á hvaða skipun sem er, getum við dregið úr skilgreiningu þessara umbúða og búið þær til á kraftmikinn hátt úr lista yfir skipanir sem á að flytja inn.

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

Listi $command skilgreinir innflutningsskipanir. Við búum síðan til virka umbúðir fyrir hvern þeirra með því að nota skipunina Invoke-Expression (með því að fjarlægja fyrst öll samheiti sem myndu stangast á við aðgerðina).

Aðgerðin endurtekur yfir skipanalínurök, ákvarðar Windows slóðir með skipunum Split-Path и Test-Pathog breytir síðan þessum slóðum í WSL slóðir. Við keyrum slóðirnar í gegnum hjálparaðgerð Format-WslArgument, sem við munum skilgreina síðar. Það sleppur við sérstafi eins og bil og sviga sem annars væru rangtúlkaðir.

Að lokum flytjum við wsl inntak leiðslu og hvaða skipanalínurök sem er.

Með þessum umbúðum geturðu hringt í uppáhalds Linux skipanirnar þínar á náttúrulegri hátt án þess að bæta við forskeyti wsl og án þess að hafa áhyggjur af því hvernig leiðunum er breytt:

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

Grunnsettið af skipunum er sýnt hér, en þú getur búið til skel fyrir hvaða Linux skipun sem er með því einfaldlega að bæta því við listann. Ef þú bætir þessum kóða við þinn uppsetningu PowerShell, þessar skipanir verða aðgengilegar þér í hverri PowerShell lotu, alveg eins og innfæddar skipanir!

Sjálfgefnar stillingar

Í Linux er algengt að skilgreina samnefni og/eða umhverfisbreytur í innskráningarsniðum, setja sjálfgefnar færibreytur fyrir oft notaðar skipanir (td, alias ls=ls -AFh eða export LESS=-i). Einn af ókostunum við umboð í gegnum ógagnvirka skel wsl.exe - að sniðin séu ekki hlaðin, þannig að þessir valkostir eru ekki sjálfgefnir tiltækir (þ.e. ls í WSL og wsl ls mun hegða sér öðruvísi með samnefninu sem skilgreint er hér að ofan).

PowerShell veitir $PSDefaultParameterValues, staðlað kerfi til að skilgreina sjálfgefnar færibreytur, en aðeins fyrir cmdlets og háþróaðar aðgerðir. Auðvitað getum við búið til háþróaðar aðgerðir úr skeljunum okkar, en þetta leiðir til óþarfa fylgikvilla (til dæmis, PowerShell tengir hluta færibreytuheiti (td, -a tengist -ArgumentList), sem mun stangast á við Linux skipanir sem taka hlutanöfn sem rök), og setningafræðin til að skilgreina sjálfgefin gildi mun ekki vera sú viðeigandi (sjálfgefin rök krefjast nafns á færibreytunni í lyklinum, ekki bara skipanafnið) .

Hins vegar, með smá breytingum á skeljunum okkar, getum við innleitt líkan svipað og $PSDefaultParameterValues, og virkja sjálfgefna valkosti fyrir Linux skipanir!

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

Framhjá $WslDefaultParameterValues í skipanalínuna sendum við breytur í gegnum wsl.exe. Eftirfarandi sýnir hvernig á að bæta leiðbeiningum við PowerShell prófílinn þinn til að stilla sjálfgefnar stillingar. Nú getum við gert það!

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

Þar sem breytur eru gerðar eftir $PSDefaultParameterValuesþú getur það það er auðvelt að slökkva á þeim tímabundið með því að setja upp lykilinn "Disabled" í merkingu $true. Viðbótarávinningur af sérstakri kjötkássatöflu er hæfileikinn til að slökkva $WslDefaultParameterValues sérstaklega frá $PSDefaultParameterValues.

Frágangur rökræðna

PowerShell gerir þér kleift að skrá rifrildi eftirvagna með því að nota skipunina Register-ArgumentCompleter. Bash hefur öflugt forritanleg sjálfvirk útfyllingartæki. WSL gerir þér kleift að hringja í bash frá PowerShell. Ef við getum skráð rifrildaútfyllingar fyrir PowerShell aðgerðaumbúðirnar okkar og kallað bash til að búa til fráganginn, fáum við fullan rifrildisuppfyllingu með sömu nákvæmni og bash sjálft!

# 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óðinn er svolítið þéttur án þess að skilja nokkrar af innri aðgerðum bash, en í grundvallaratriðum er það sem við gerum þetta:

  • Að skrá rifrildisútfyllingarforrit fyrir alla virka umbúðir okkar með því að senda lista $commands í færibreytu -CommandName í Register-ArgumentCompleter.
  • Við kortleggjum hverja skipun við skeljaaðgerðina sem bash notar fyrir sjálfvirka útfyllingu (til að skilgreina sjálfvirka útfyllingu, notar bash $F, skammstöfun fyrir complete -F <FUNCTION>).
  • Umbreytir PowerShell rökum $wordToComplete, $commandAst и $cursorPosition í það snið sem sjálfvirkar útfyllingaraðgerðir bash búast við samkvæmt forskriftunum forritanleg sjálfvirk útfylling bash.
  • Við setjum saman skipanalínu til að flytja á wsl.exe, sem tryggir að umhverfið sé rétt sett upp, kallar á viðeigandi sjálfvirka útfyllingaraðgerð og gefur út niðurstöðurnar í línu fyrir línu.
  • Svo hringjum við wsl með skipanalínunni aðskiljum við úttakið með línuskilum og búum til fyrir hvern CompletionResults, flokka þá og sleppa stafi eins og bilum og sviga sem annars væru rangtúlkaðir.

Fyrir vikið munu Linux skipanaskeljar okkar nota nákvæmlega sömu sjálfvirka útfyllingu og bash! Til dæmis:

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

Hver sjálfvirk útfylling gefur upp gildi sem eru sértæk fyrir fyrri rök, les stillingargögn eins og þekkta véla frá WSL!

<TAB> mun fletta í gegnum færibreyturnar. <Ctrl + пробел> mun sýna alla tiltæka valkosti.

Auk þess, þar sem við höfum nú bash sjálfvirka útfyllingu, geturðu sjálfkrafa útfyllt Linux slóðir beint í PowerShell!

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

Í þeim tilvikum þar sem sjálfvirk útfylling bash skilar engum árangri, fer PowerShell aftur í sjálfgefna Windows slóðir kerfisins. Þannig geturðu í reynd notað báðar leiðirnar samtímis að eigin vali.

Ályktun

Með því að nota PowerShell og WSL getum við samþætt Linux skipanir inn í Windows sem innfædd forrit. Það er engin þörf á að leita að Win32 smíðum eða Linux tólum eða trufla vinnuflæði þitt með því að fara í Linux skel. Bara setja upp WSL, stilla PowerShell prófíllinn и listaðu skipanirnar sem þú vilt flytja inn! Rík sjálfvirk útfylling fyrir Linux og Windows skipanafæribreytur og skráarslóðir er virkni sem er ekki einu sinni í boði í innfæddum Windows skipunum í dag.

Fullur frumkóði sem lýst er hér að ofan, sem og viðbótarleiðbeiningar um að fella hann inn í verkflæðið þitt, er fáanlegur hér.

Hvaða Linux skipanir finnst þér gagnlegastar? Hvaða aðrir algengir hlutir vantar þegar unnið er í Windows? Skrifaðu í athugasemdir eða á GitHub!

Heimild: www.habr.com

Bæta við athugasemd