Integrojmë komandat Linux në Windows duke përdorur PowerShell dhe WSL

Një pyetje tipike për zhvilluesit nën Windows: «Pse këtu nuk ka ende <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less apo mjetet e njohura grep ose sed, zhvilluesit nën Windows duan qasje të lehtë në këto komanda në punën e përditshme.

Nën-sistemi Windows për Linux (WSL) ka bërë një hap të madh përpara në këtë drejtim. Ajo lejon thirrjen e komandave Linux nga Windows, duke i proxy-tuar ato përmes wsl.exe (për shembull, wsl ls). Megjithëse kjo është një përmirësim i rëndësishëm, ky variant ka disa disavantazhe.

  • Shtimi i gjithëpërfshirës wsl është lodhës dhe jo natyral.
  • Rrugët Windows në argumente nuk funksionojnë gjithmonë, sepse shenjat e prapme interpretohen si simbole shkëputëse dhe jo si ndarës katalogësh.
  • Rrugët Windows në argumente nuk konvertohen në pikën e montimit përkatëse në WSL.
  • Parametrat e parazgjedhur në profilet WSL me alias dhe variablat e ambientit nuk merren parasysh.
  • Përfundimi i rrugëve Linux nuk mbështetet.
  • Përfundimi i komandave nuk mbështetet.
  • Përfundimi i argumenteve nuk mbështetet.

Si rezultat, komandat Linux perceptohen nën Windows si qytetarë të rangut të dytë — dhe është më e vështirë të përdoren se komandat vendase. Për të barazuar ato, duhet të zgjidhen problemet e përmendura.

Shellet e funksioneve PowerShell

Me shell-at e funksioneve PowerShell, ne mund të shtojmë mbështetje për automatikisht përfundimin e komandave dhe të eliminojmë nevojën për prefikse wsl, duke transmetuar rrugët Windows në rrugët WSL. Kërkesat kryesore për shell-at janë:

  • Për çdo komandë Linux duhet të ketë një shell funksioni me emrin e njëjtë.
  • Shell-i duhet të pranojë rrugët Windows, të dërguara si argumente, dhe t'i konvertojë ato në rrugë WSL.
  • Shell-i duhet të thërrasë wsl me komandën përkatëse Linux në çdo hyrje të tubit dhe të kalojë çdo argument të linjës së komandës të dërguar në funksion.

Duke qenë se ky model mund të aplikohet në çdo komandë, ne mund të abstrahojmë përcaktimin e këtyre shell-ve dhe t'i gjenerojmë ato dinamikisht nga lista e komandave për importim.

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

Lista $command përcakton komandat për importim. Pastaj ne gjenerojmë dinamikisht një mbështjellës funksioni për secilën prej tyre, duke përdorur komandën Invoke-Expression (së pari duke hequr çdo alias që do të përplaset me funksionin).

Funksioni kalon nëpër argumentet e linjës së komandës, përcakton rrugët Windows me komandat Split-Path dhe Test-Path, dhe pastaj i konverton këto rrugë në rrugë WSL. Ne kalojmë rrugët përmes një funksioni ndihmës Format-WslArgument, të cilin do ta përcaktojmë më vonë. Ai ekranohet simbollet speciale, si hapësirat dhe kllapat, të cilat përndryshe do të interpretoheshin gabimisht.

Më në fund, kalojmë wsl inputin e pipeline dhe çdo argument të linjës së komandës.

Me këto mbështjellëse, mund të thërrisni komandat tuaja të preferuara Linux në një mënyrë më natyrale, pa shtuar prefiks wsl dhe pa u shqetësuar se si konvertohen rrugët:

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

Këtu është një grup themelor komandash, por mund të krijoni një shell për çdo komandë Linux, duke e shtuar atë në listë. Nëse e shtoni këtë kod në tuajin profilin PowerShell, këto komanda do të jenë të aksesueshme për ju në çdo seancë PowerShell, ashtu si komandat natyrale!

Parametrat e paracaktuar

Në Linux është zakon që të përcaktohen alias dhe/ose variabla të mjedisit në profile (login profile), duke vendosur parametrat e paracaktuar për komandat që përdoren shpesh (për shembull, alias ls=ls -AFh ose export LESS=-i). Një nga disavantazhet e proksimit përmes një shell-i jo interaktiv wsl.exe është se profilat nuk ngarkohen, prandaj këta parametra të paracaktuar nuk janë të aksesueshëm (dmth, ls në WSL dhe wsl ls do të sillet ndryshe me alias-in e përcaktuar më sipër).

PowerShell ofron $PSDefaultParameterValues, një mekanizëm standard për përcaktimin e parametrave të paracaktuar, por vetëm për komandlet dhe funksionet e zgjeruara. Sigurisht, mund të bëjmë funksione të zgjeruara nga shell-et tona, por kjo sjell ndërlikime shtesë (p.sh., PowerShell përputhet me emrat e pjesshëm të parametrave (p.sh., -a përputhet me -ArgumentList), të cilat do të konflikonin me komandat Linux, që pranojnë emra të pjesshëm si argumente), dhe sintaksa për përcaktimin e vlerave të paracaktuar nuk do të jetë më e përshtatshme (për përcaktimin e argumenteve të paracaktuar kërkohet emri i parametrave në çelës, jo vetëm emri i komandës).

Megjithatë, me një ndryshim të vogël në shell-et tona, mund të implementojmë një model të ngjashëm me $PSDefaultParameterValues, dhe të përfshijmë parametrat e paracaktuar për komandat 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 ' ')
    }
}

Duke kaluar $WslDefaultParameterValues në komandën e komandës, ne dërgojmë parametrat përmes wsl.exe. Më poshtë tregohet se si të shtoni shpejtime në profilin PowerShell për të konfiguruar parametrat e paracaktuar. Tani mund ta bëjmë këtë!

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

Duke qenë se parametrat modelohen pas $PSDefaultParameterValues, mund të lehtësisht t’i çaktivizoni për njëkohësisht, duke vendosur çelësin "Disabled" në vlerën $true. Një përfitim shtesë i një tabele hesh është mundësia për të çaktivizuar $WslDefaultParameterValues ndarë nga $PSDefaultParameterValues.

Autokompaktimi i argumenteve

PowerShell lejon regjistrimin e përfunduesve të argumenteve duke përdorur komandën Register-ArgumentCompleter. Në Bash ka mjete të fuqishme programuese për autokompaktimin. WSL lejon të thërrasim bash nga PowerShell. Nëse mund të regjistrojmë përfundues të argumenteve për funksionet tona të shell-it PowerShell dhe të thërrasim bash për të krijuar përfundime, atëherë do të kemi një autokompaktim të argumenteve me të njëjtin saktësi si në vetë bash!

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

Kodi është pak i ngjeshur pa kuptuar disa funksione të brendshme të bash-it, por në përgjithësi ne bëjmë këtë:

  • Regjistroni përfunduesin e argumenteve për të gjitha mbështjellësit tanë të funksioneve, duke kaluar një listë $commands në parametrin -CommandName për Register-ArgumentCompleter.
  • Përputhim çdo komandë me funksionin e shell-it që përdor bash për autokompaktimin (për përcaktimin e specifikimeve të autokompaktimit në bash përdoret $F, shkurtim nga complete -F).
  • Transformojmë argumentet PowerShell $wordToComplete, $commandAst dhe $cursorPosition në formatin e pritur nga funksionet e autokompaktimit të bash-it sipas specifikimeve autokompaktimi programues bash.
  • Krijojmë linjën e komandës për t'i kaluar në wsl.exe, e cila siguron konfigurimin e duhur të mjedisit, thërret funksionin përkatës të autokompaktimit dhe nxjerr rezultatet me ndarje nga rreshtat.
  • Pastaj thërrasim wsl me linjën e komandës, ndajmë nxjerrjen me ndarës rreshti dhe gjenerojmë për çdo CompletionResults, duke i renditur ato dhe duke i mbrojtur karakteret, të tilla si hapësirat dhe kllapat, të cilat ndryshe do të interpretoheshin gabimisht.

Në fund, shell-ët tanë të komandave Linux do të përdorin të njëjtin autokompaktim si në bash! Shembull:

  • ssh -c -J -m -O -o -Q -w -b

Çdo auto-mbushje ofron vlera të veçanta për argumentin e mëparshëm, duke lexuar të dhënat e konfigurimit, siç janë hostet e njohur, nga WSL!

<TAB> do të kalojë ciklikisht përmes parametrave. <Ctrl + пробел> do të tregojë të gjitha opsionet e disponueshme.

Për më tepër, tani që kemi auto-mbushjen e bash, mund të mbushni automatikisht rrugët Linux drejtpërdrejt në PowerShell!

  • less /etc/
  • ls /usr/share/
  • vim ~/ .bash

Në rastet kur auto-mbushja e bash nuk jep asnjë rezultat, PowerShell kthehet te sistemi për_default me rrugë Windows. Kështu, ju mund të përdorni praktikisht dhe rrugët të dyja sipas dëshirës suaj.

Përfundim

Me PowerShell dhe WSL, mund të integrojmë komandat Linux në Windows si aplikacione natively. Nuk ka nevojë të kërkoni build-e Win32 ose utilitete Linux ose të ndaloni punën tuaj duke kaluar në shell-in Linux. Thjesht instaloni WSL, konfiguro profilin PowerShell dhe lista e komandave që dëshironi të importoni! Auto-mbushja e pasur për parametrat e komandave dhe rrugët e skedarëve Linux dhe Windows është një funksionalitet që sot nuk e ka asnjë nga komandat native të Windows.

Kodi burimor i plotë, i përshkruar më sipër, si dhe rekomandime shtesë për përfshirjen e tij në procesin e punës janë të disponueshme këtu.

Cilat komanda Linux i konsideroni më të dobishme? Çfarë gjërash të zakonshme mungojnë gjatë punës në Windows? Shkruani në komentet ose në GitHub!

Burimi: habr.com

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster