Integreerime Linuxi käsklusi Windowsis PowerShelli ja WSL-i abil

Tüüpiline küsimus Windowsi arendajatelt: "Miks siin ikkagi pole <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less või tuttavaid tööriistu grep või sed, Windowsi arendajad soovivad saada kerget juurdepääsu nendele käsklustele igapäevases töös.

Windowsi alamsüsteem Linuxi jaoks (WSL) on selles osas teinud tohutu edusamme. See võimaldab käivitada Linuxi käske Windowsist, edastades need wsl.exe (nt. wsl ls). Kuigi see on märkimisväärne parandus, on sellisel variandil mitmeid puudusi.

  • Üldine lisamine wsl on kurnav ja ebamugav.
  • Windowsi teed argumentides ei pruugi alati töötada, kuna tagasikäigud tõlgendatakse kui escape-märke, mitte kataloogide eraldajaid.
  • Windowsi teed argumentides ei muuda vastavasse WSL-i mount-punkti.
  • WSL profiilides vaikimisi parameetreid ei arvestata koos aliaste ja keskkonnamuutujatega.
  • Linuxi teede autokomplekteerimist ei toetata.
  • Käskude autokomplekteerimist ei toetata.
  • Argumentide autokomplekteerimist ei toetata.

Tulemuseks on see, et Linuxi käske tajutakse Windowsis teisejärgulisena — ja neid on keerulisem kasutada kui natiivseid käske. Nende õiguste tasandamiseks tuleb lahendada loetletud probleemid.

PowerShelli funktsioonide kestad

PowerShelli funktsioonide kestade kaudu saame lisada käskude autokomplekteerimise ja kõrvaldada vajaduse eelfikseerimiseks wsl, tõlgendades Windowsi teed WSL-i teedeks. Funktsioonide kestade peamised nõuded:

  • Iga Linuxi käsu jaoks peab olema üks kest, mille nimi on sama.
  • Kest peab tuvastama Windowsi teed, mis edastatakse argumentidena, ja konverteerima need WSL-i teedeks.
  • Kest peab kutsuma wsl õige Linuxi käsu iga toru sisendi jaoks ning edastama kõik argumentide read, mis antakse funktsioonile.

Kuna see mall võib rakendada igale käsule, saame need kestad abstraktiseerida ja genereerida dünaamiliselt koos käsude loendiga impordiks.

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

Loend $command määratleb imporditavad käsud. Seejärel genereerime dünaamiliselt funktsiooni pakendi igale neist, kasutades käsku Invoke-Expression (eespool eemaldades kõik aliasid, mis segavad funktsiooni).

Funktsioon läbib argumentide read, tuvastab Windowsi teed käskude abil Split-Path ja Test-Path, ja seejärel muundab need WSL-i teed. Käime teed läbi abifunktsiooni Format-WslArgument, mille määratleme hiljem. See põgeneb spetsiaalsete märkide, nagu tühikud ja sulud, eest, mis muidu tõlgendataks valesti.

Lõpuks edastame wsl toru sisendi ja kõik käsurea argumendid.

Selliste mähiste abil on võimalik oma lemmik Linuxi käske välja kutsuda loomulikumal viisil, lisamata prefiksi wsl ja muretsemata, kuidas teed muundatakse:

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

Siin on esitatud põhikomandide komplekt, kuid saate luua mähise iga Linuxi käsu jaoks, lisades selle lihtsalt nimekirja. Kui lisate selle koodi oma profiili PowerShelli, on need käsud teile kättesaadavad igas PowerShelli seansis, nagu ka natiivsed käsud!

Vaikeseaded

Linuxis on tavaline määrata aliased ja/või keskkonnamuutujad profiilides (sisselogimisprofiil), seades vaikeseaded sageli kasutatavatele käskudele (näiteks, alias ls=ls -AFh või export LESS=-i). Üks puudusi, kui kasutada mitteinteraktiivset mähist, wsl.exe on see, et profiile ei laadita, mistõttu need vaikeseaded ei ole kättesaadavad (nt. ls WSL-is ja wsl ls käituvad erinevalt eelnevalt määratud aliasest).

PowerShell pakub $PSDefaultParameterValues, standardmehhanismi vaikeseadete määramiseks, kuid ainult käsu ja laiendatud funktsioonide jaoks. Muidugi, meie mähiseid saab muuta laiendatud funktsioonideks, kuid see toob kaasa lisavõimeid (nii et PowerShell seondub osaliste parameetrite nimede (nt. -a seondub -ArgumentList), mis võivad konflikti minna Linuxi käskudega, mis võtavad osalised nimed argumentidena), ning vaikeseadete määramise süntaks pole kõige sobivam (argumentide määramiseks vajatakse parameetri nime võtmena, mitte ainult käsunime).

Siiski, väikese muudatusega meie mähistes, suudame rakendada mudeli, mis on sarnane $PSDefaultParameterValues, ja lisada vaikeseaded Linuxi käskudele!

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

Edastades $WslDefaultParameterValues komandisse, saadame parameetreid läbi wsl.exe. Allpool on näidatud, kuidas lisada juhiseid PowerShelli profiili, et seadistada lähteseaded. Nüüd saame seda teha!

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

Kuna parameetrid modelleeritakse pärast $PSDefaultParameterValues, saad kergesti need keelata ajaliselt, seades lüliti "Disabled" väärtuseks $true. Täiendav eelis eraldi hash-tabelist on võimalus keelata $WslDefaultParameterValues erakordselt $PSDefaultParameterValues.

Argumentide automaatne täiendus

PowerShell võimaldab registreerida argumentide täiustajaid käsuga Register-ArgumentCompleter. Bashil on võimsad programmeeritavad automaatsete täienduste vahendid.WSL võimaldab kutsuda bash'i PowerShellist. Kui saame registreerida argumentide täiustajad meie PowerShelli funktsioonide kestades ja kutsuda bash'i, et luua täiendusi, saame täieliku argumentide automaatse täienduse sama täpsusega nagu bash'is endas!

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

Kood on veidi tihe ilma mõistmata mõningaid sisemisi bash'i funktsioone, kuid põhimõtteliselt teeme järgmist:

  • Registreerime argumentide täiustaja kõikide meie funktsiooni wrapping'ute jaoks, edastades nimekirja $commands parameetris -CommandName jaoks Register-ArgumentCompleter.
  • Seome iga käsu bash'i kasutatud kestafunktsiooniga automaatsete täienduste jaoks (automaatsete täienduste määratlemiseks bash'is kasutatakse $F, lühend complete -F).
  • Muudame PowerShelli argumendid $wordToComplete, $commandAst ja $cursorPosition formaadiks, mida bash'i automaatsete täienduste funktsioonid ootavad vastavalt programmeeritavale automaatse täienduse bash.
  • Koostame käsurea, et edastada see wsl.exe, mis tagab õige keskkonna seadistamise, kutsub esile vastava automaatse täienduse funktsiooni ja väljundit ridade kaupa.
  • Seejärel kutsume välja wsl käskude abil, eraldame väljundi rea eraldajatega ja genereerime iga CompletionResults, sorteerides neid ja escaping sümboleid, nagu tühikud ja sulud, mis muidu tõlgendataks valesti.

Lõpuks kasutavad meie Linuxi käsuread täpselt sama automaatset täiendust nagu bash! Näiteks:

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

Iga automaatkomplekteerimine toob esile eelmise argumendi jaoks spetsiifilisi väärtusi, lugedes konfigureerimisandmeid, nagu tuntud hostid, WSL-ist!

<TAB> järel käiakse parameetreid tsükliliselt läbi. <Ctrl + пробел> näitab kõiki saadaolevaid valikuid.

Lisaks, kuna meil on nüüd bash-i automaatkomplekteerimine tööle pandud, saate automaatkomplekteerida Linuxi teid otse PowerShellis!

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

Juhul, kui bash'i automaatkomplekteerimine ei anna mingit tulemust, naaseb PowerShell vaikimisi Windowsi teede juurde. Nii saate tegelikult samal ajal kasutada nii neid kui ka teisi teid oma äranägemise järgi.

Kokkuvõte

PowerShelli ja WSL abil saame integreerida Linuxi käsud Windowsisse natiivsete rakendustena. Pole vaja otsida Win32 ehitusi või Linuxi utiliite ega katkestada töövoogu, minnes Linuxi shelli. Lihtsalt paigaldage WSL, seadistage PowerShelli profiil ja loetlege käsud, mida soovite importida! Rikas automaatkomplekteerimine Linuxi ja Windowsi failide käskude ja teede parameetrite jaoks on funktsionaalsus, mida täna pole isegi Windowsi natiivsetes käskudes.

Ülaltoodud täiendav kood ja soovitused selle integreerimiseks teie töövoogu on saadaval siin.

Milliseid Linuxi käske peate kõige kasulikumaks? Millest tuttavast on Windowsis töötamisel veel puudu? Kirjutage kommentaaridesse või GitHubis!

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster