Linuxi käskude integreerimine Windowsi PowerShelli ja WSL-i abil

Tüüpiline küsimus Windowsi arendajatelt: “Miks ikka ei ole <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Kas see on võimas pühkimine less või tuttavad tööriistad grep või sed, Windowsi arendajad soovivad oma igapäevatöös neile käskudele lihtsat juurdepääsu.

Windowsi alamsüsteem Linuxile (WSL) on selles osas teinud suure sammu edasi. See võimaldab teil kutsuda Windowsi Linuxi käske, puhverserveri kaudu wsl.exe (Nt wsl ls). Kuigi see on märkimisväärne edasiminek, on sellel valikul mitmeid puudusi.

  • Üldlevinud lisand wsl tüütu ja ebaloomulik.
  • Windowsi teed argumentides ei tööta alati, sest kaldkriipsu tõlgendatakse pigem paomärkidena kui kataloogide eraldajatena.
  • Argumentides olevaid Windowsi teid ei tõlgita WSL-is vastavasse ühenduspunkti.
  • Pseudonüümide ja keskkonnamuutujatega WSL-profiilides vaikesätteid ei arvestata.
  • Linuxi tee lõpetamist ei toetata.
  • Käskude täitmist ei toetata.
  • Argumendi lõpetamist ei toetata.

Seetõttu käsitletakse Linuxi käske Windowsi all nagu teise klassi kodanikke ja neid on keerulisem kasutada kui natiivseid käske. Nende õiguste võrdsustamiseks on vaja lahendada loetletud probleemid.

PowerShelli funktsioonide ümbrised

PowerShelli funktsioonide ümbristega saame lisada käskude lõpetamise ja kaotada vajaduse eesliidete järele wsl, tõlkides Windowsi teed WSL-i teedeks. Põhinõuded kestadele:

  • Iga Linuxi käsu jaoks peab olema üks sama nimega funktsioonide ümbris.
  • Kest peab tuvastama argumentidena edastatud Windowsi teed ja teisendama need WSL-teedeks.
  • Kest peaks helistama wsl vastava Linuxi käsuga mis tahes konveieri sisendisse ja edastades kõik funktsioonile edastatud käsureaargumendid.

Kuna seda mustrit saab rakendada mis tahes käsule, saame nende ümbriste määratluse abstraktselt võtta ja need dünaamiliselt genereerida importivate käskude loendist.

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

nimekiri $command määrab impordikäsud. Seejärel genereerime käsu abil dünaamiliselt igaühe jaoks funktsiooniümbrise Invoke-Expression (eemaldades esmalt kõik varjunimed, mis võiksid funktsiooniga vastuolus olla).

Funktsioon kordab käsurea argumente, määrab käskude abil Windowsi teed Split-Path и Test-Pathja teisendab need teed WSL-teedeks. Me juhime teid läbi abifunktsiooni Format-WslArgument, mille määratleme hiljem. See väldib erimärke, nagu tühikud ja sulgud, mida muidu valesti tõlgendataks.

Lõpuks edastame wsl torujuhtme sisend ja mis tahes käsurea argumendid.

Nende ümbristega saate oma lemmik Linuxi käske kutsuda loomulikumal viisil, ilma eesliidet lisamata wsl ja muretsemata selle pärast, kuidas teed teisendatakse:

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

Siin on näidatud põhikäskude komplekt, kuid saate luua mis tahes Linuxi käsu jaoks kesta, lisades selle lihtsalt loendisse. Kui lisate selle koodi oma profiil PowerShell, need käsud on teile saadaval igal PowerShelli seansil, täpselt nagu natiivsed käsud!

Vaikeseaded

Linuxis on tavaline, et sisselogimisprofiilides määratletakse varjunimed ja/või keskkonnamuutujad, millega määratakse sageli kasutatavate käskude jaoks vaikeparameetrid (näiteks alias ls=ls -AFh või export LESS=-i). Üks mitteinteraktiivse kesta kaudu puhverserveri kasutamise puudusi wsl.exe - et profiile ei laadita, seega pole need valikud vaikimisi saadaval (st. ls WSL-is ja wsl ls käitub eespool määratletud varjunimega erinevalt).

PowerShell pakub $PSDefaultParameterValues, standardmehhanism vaikeparameetrite määratlemiseks, kuid ainult cmdlet-ide ja täiustatud funktsioonide jaoks. Muidugi saame oma kestadest teha täiustatud funktsioone, kuid see toob kaasa tarbetuid komplikatsioone (nt PowerShell korreleerib osaliste parameetrite nimesid (näiteks -a korreleerub -ArgumentList), mis läheb vastuollu Linuxi käskudega, mis võtavad argumentidena osalisi nimesid) ja vaikeväärtuste määratlemise süntaks ei ole kõige sobivam (vaikeargumendid nõuavad võtmes parameetri nime, mitte ainult käsu nime) .

Kuid meie kestade väikese muudatusega saame rakendada sarnase mudeli $PSDefaultParameterValuesja lubage Linuxi käskude jaoks vaikesuvandid!

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

Mööduv $WslDefaultParameterValues käsureale saadame parameetrid kaudu wsl.exe. Järgmine näitab, kuidas lisada oma PowerShelli profiilile juhiseid vaikesätete konfigureerimiseks. Nüüd saame sellega hakkama!

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

Kuna parameetrid on modelleeritud pärast $PSDefaultParameterValues, Sa saad neid on lihtne keelata ajutiselt võtme paigaldamisega "Disabled" tähendusse $true. Eraldi räsitabeli täiendav eelis on keelamise võimalus $WslDefaultParameterValues sellest eraldi $PSDefaultParameterValues.

Argumendi lõpetamine

PowerShell võimaldab registreerida argumentide treilereid käsu abil Register-ArgumentCompleter. Bashil on võimas programmeeritavad automaatse täitmise tööriistad. WSL võimaldab teil PowerShelist bashi kutsuda. Kui suudame oma PowerShelli funktsioonimähistes registreerida argumentide lõpetamise ja lõpetamiste genereerimiseks kutsuda bashi, saame täieliku argumendi lõpetamise sama täpsusega kui bash ise!

# 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 pisut tihe, mõistmata mõningaid bashi sisemisi funktsioone, kuid põhimõtteliselt teeme järgmist:

  • Argumendi lõpetaja registreerimine kõigi meie funktsioonide ümbriste jaoks loendi edastamisega $commands parameetris -CommandName eest Register-ArgumentCompleter.
  • Seotame iga käsu shellifunktsiooniga, mida bash kasutab automaatseks täitmiseks (automaatse täitmise spetsifikatsioonide määratlemiseks, bash kasutab $F, lühend sõnadest complete -F <FUNCTION>).
  • PowerShelli argumentide teisendamine $wordToComplete, $commandAst и $cursorPosition vormingusse, mida bashi automaatse täitmise funktsioonid nõuavad vastavalt spetsifikatsioonidele programmeeritav automaatne lõpetamine bash.
  • Koostame ülekandmiseks käsurea wsl.exe, mis tagab, et keskkond on õigesti seadistatud, kutsub välja sobiva automaatse lõpetamise funktsiooni ja väljastab tulemused rida-realt.
  • Siis helistame wsl käsureaga eraldame väljundi reaeraldajatega ja genereerime igaühe jaoks CompletionResults, sorteerides neid ja vältides selliseid märke nagu tühikud ja sulud, mida muidu valesti tõlgendataks.

Selle tulemusena kasutavad meie Linuxi käsukestad täpselt sama automaatset lõpetamist kui bash! Näiteks:

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

Iga automaatne täitmine annab eelmise argumendi jaoks spetsiifilised väärtused, lugedes konfiguratsiooniandmeid, näiteks WSL-i teadaolevaid hoste!

<TAB> tsükliliselt läbi parameetrite. <Ctrl + пробел> kuvab kõik saadaolevad valikud.

Lisaks, kuna meil on nüüd bashi automaatne lõpetamine, saate Linuxi teid automaatselt täita otse PowerShellis!

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

Juhtudel, kui bashi automaatne lõpetamine ei anna tulemusi, naaseb PowerShell süsteemi Windowsi vaiketeedele. Seega saate praktikas kasutada mõlemat teed oma äranägemise järgi korraga.

Järeldus

PowerShelli ja WSL-i abil saame integreerida Linuxi käsud Windowsi algrakendustena. Pole vaja otsida Win32 järge ega Linuxi utiliite ega katkestada oma töövoogu, minnes Linuxi kesta. Lihtsalt installige WSL, seadistada PowerShelli profiil и loetlege käsud, mida soovite importida! Rikkalik automaatne täitmine Linuxi ja Windowsi käsuparameetrite ja failiteede jaoks on funktsioon, mis pole tänapäeval isegi Windowsi algkäskudes saadaval.

Saadaval on ülalkirjeldatud täielik lähtekood ja täiendavad juhised selle lisamiseks oma töövoogu siin.

Millised Linuxi käsud on teie arvates kõige kasulikumad? Millised muud tavalised asjad Windowsis töötades puuduvad? Kirjuta kommentaaridesse või GitHubis!

Allikas: www.habr.com

Lisa kommentaar