Linux-komentojen integrointi Windowsiin PowerShellin ja WSL:n avulla

Tyypillinen kysymys Windows-kehittäjiltä: "Miksi ei vieläkään ole <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Olipa kyseessä voimakas pyyhkäisy less tai tuttuja työkaluja grep tai sed, Windows-kehittäjät haluavat helpon pääsyn näihin komentoihin päivittäisessä työssään.

Windows-alijärjestelmä Linuxille (WSL) on ottanut valtavan edistysaskeleen tässä suhteessa. Sen avulla voit kutsua Linux-komentoja Windowsista välityspalvelimella wsl.exe (Esim wsl ls). Vaikka tämä on merkittävä parannus, tämä vaihtoehto kärsii useista haitoista.

  • Jokapaikan lisäys wsl tylsää ja luonnotonta.
  • Windows-polut argumenteissa eivät aina toimi, koska kenoviivat tulkitaan pakollisiksi merkeiksi eikä hakemistoerottimiksi.
  • Argumenttien Windows-polkuja ei käännetä vastaavaan WSL-liitoskohtaan.
  • Oletusasetuksia ei noudateta WSL-profiileissa, joissa on aliaksia ja ympäristömuuttujia.
  • Linux-polun viimeistelyä ei tueta.
  • Komennon suorittamista ei tueta.
  • Argumentin täydentämistä ei tueta.

Tämän seurauksena Linux-komentoja käsitellään kuin toisen luokan kansalaisia ​​Windowsissa – ja niitä on vaikeampi käyttää kuin alkuperäisiä komentoja. Heidän oikeuksiensa tasaamiseksi on tarpeen ratkaista luetellut ongelmat.

PowerShell-toimintokääreet

PowerShell-funktion kääreillä voimme lisätä komentojen täydennyksen ja poistaa etuliitteet wsl, kääntää Windows-polut WSL-poluiksi. Perusvaatimukset kuorille:

  • Jokaisella Linux-komennolla on oltava yksi samanniminen funktion kääre.
  • Shellin on tunnistettava argumentteina välitetyt Windows-polut ja muunnettava ne WSL-poluiksi.
  • Kuoren pitäisi kutsua wsl sopivalla Linux-komennolla mihin tahansa liukuhihnan syötteeseen ja välittämällä kaikki funktiolle välitetyt komentoriviargumentit.

Koska tätä mallia voidaan soveltaa mihin tahansa komentoon, voimme abstraktisti näiden kääreiden määritelmän ja luoda ne dynaamisesti tuottavien komentojen luettelosta.

# 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 määrittää tuontikomennot. Luomme sitten dynaamisesti funktion kääreen kullekin komennolla Invoke-Expression (poistamalla ensin kaikki aliakset, jotka ovat ristiriidassa toiminnon kanssa).

Funktio toistuu komentoriviargumenttien yli, määrittää Windows-polut komentojen avulla Split-Path и Test-Pathja muuntaa sitten nämä polut WSL-poluiksi. Suoritamme polut aputoiminnon kautta Format-WslArgument, jonka määrittelemme myöhemmin. Se välttää erikoismerkit, kuten välilyönnit ja sulut, jotka muuten tulkittaisiin väärin.

Lopuksi välitämme wsl putkisyöte ja kaikki komentorivin argumentit.

Näillä kääreillä voit kutsua suosikki Linux-komentojasi luonnollisemmalla tavalla ilman etuliitettä wsl ja huolehtimatta siitä, kuinka polut muunnetaan:

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

Peruskomentosarja näytetään tässä, mutta voit luoda komentotulkin mille tahansa Linux-komennolle yksinkertaisesti lisäämällä sen luetteloon. Jos lisäät tämän koodin omaan profiili PowerShell, nämä komennot ovat käytettävissäsi jokaisessa PowerShell-istunnossa, aivan kuten alkuperäiset komennot!

Oletusasetukset

Linuxissa on yleistä määrittää aliaksia ja/tai ympäristömuuttujia kirjautumisprofiileihin, jolloin usein käytetään oletusparametreja (esim. alias ls=ls -AFh tai export LESS=-i). Yksi ei-interaktiivisen kuoren välityspalvelimen haitoista wsl.exe - että profiileja ei ole ladattu, joten nämä vaihtoehdot eivät ole oletusarvoisesti käytettävissä (esim. ls WSL:ssä ja wsl ls käyttäytyy eri tavalla yllä määritellyn aliaksen kanssa).

PowerShell tarjoaa $PSDefaultParameterValues, vakiomekanismi oletusparametrien määrittämiseen, mutta vain cmdleteille ja lisätoiminnoille. Voimme tietysti tehdä kuoristamme edistyneitä toimintoja, mutta tämä aiheuttaa tarpeettomia komplikaatioita (esimerkiksi PowerShell korreloi osittaisten parametrien nimiä (esim. -a korreloi kanssa -ArgumentList), joka on ristiriidassa Linux-komentojen kanssa, jotka ottavat osittaisia ​​nimiä argumenteiksi), ja oletusarvojen määrittelyyn käytettävä syntaksi ei ole sopivin (oletusargumentit edellyttävät parametrin nimeä avaimessa, ei vain komennon nimeä) .

Pienellä muutoksilla kuoriimme voimme kuitenkin toteuttaa samanlaisen mallin kuin $PSDefaultParameterValuesja ota oletusasetukset käyttöön Linux-komentoille!

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

Ohitus $WslDefaultParameterValues komentoriville, lähetämme parametrit kautta wsl.exe. Seuraavassa näytetään, kuinka voit lisätä PowerShell-profiiliisi ohjeita oletusasetusten määrittämiseksi. Nyt voimme tehdä sen!

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

Koska parametrit on mallinnettu $PSDefaultParameterValues, Sinä pystyt ne on helppo poistaa käytöstä tilapäisesti asentamalla avaimen "Disabled" merkitykseen $true. Lisäetuna erillisestä hash-taulukosta on kyky poistaa käytöstä $WslDefaultParameterValues erikseen $PSDefaultParameterValues.

Argumentin loppuun saattaminen

PowerShell antaa sinun rekisteröidä argumenttitrailereita komennolla Register-ArgumentCompleter. Bashilla on voimaa ohjelmoitavat automaattisen täydennystyökalut. WSL:n avulla voit kutsua bashia PowerShellista. Jos voimme rekisteröidä argumentin täydennykset PowerShell-funktion kääreillemme ja kutsua bashia luomaan täydennykset, saamme täyden argumentin täydennyksen samalla tarkkuudella kuin itse 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]
    }
}

Koodi on hieman tiheä ymmärtämättä joitain bashin sisäisiä toimintoja, mutta periaatteessa teemme näin:

  • Argumenttien täydentäjän rekisteröiminen kaikille funktion kääreille välittämällä luettelo $commands parametrissa -CommandName varten Register-ArgumentCompleter.
  • Me yhdistämme jokaisen komennon komentotulkkifunktioon, jota bash käyttää automaattiseen täydennykseen (määrittääksesi automaattisen täydennyksen määritykset, bash käyttää $F, lyhenne sanoista complete -F <FUNCTION>).
  • PowerShell-argumenttien muuntaminen $wordToComplete, $commandAst и $cursorPosition muotoon, jota bashin automaattinen täydennys toimintojen edellyttämä edellyttää ohjelmoitava automaattinen täydennys lyödä.
  • Luomme komentorivin, johon siirretään wsl.exe, joka varmistaa, että ympäristö on määritetty oikein, kutsuu asianmukaisen automaattisen täydennystoiminnon ja tulostaa tulokset rivi riviltä.
  • Sitten soitetaan wsl komentorivillä erotamme tuotoksen rivierottimella ja luomme kullekin CompletionResults, lajittelemalla ne ja jättämällä pois merkkejä, kuten välilyöntejä ja sulkeita, jotka muuten tulkittaisiin väärin.

Tämän seurauksena Linux-komentotulkimemme käyttävät täsmälleen samaa automaattista täydennystä kuin bash! Esimerkiksi:

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

Jokainen automaattinen täydennys antaa arvoja, jotka ovat ominaisia ​​edelliselle argumentille, lukemalla konfiguraatiotietoja, kuten tunnetut isännät WSL:stä!

<TAB> selaa parametreja. <Ctrl + пробел> näyttää kaikki käytettävissä olevat vaihtoehdot.

Lisäksi, koska meillä on nyt bash-automaattinen täydennys, voit täydentää Linux-polut automaattisesti suoraan PowerShellissä!

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

Tapauksissa, joissa bash-automaattinen täydennys ei tuota tuloksia, PowerShell palaa järjestelmän oletusarvoisiin Windows-polkuihin. Käytännössä voit siis käyttää molempia polkuja samanaikaisesti oman harkintasi mukaan.

Johtopäätös

PowerShellin ja WSL:n avulla voimme integroida Linux-komentoja Windowsiin alkuperäisinä sovelluksina. Sinun ei tarvitse etsiä Win32-koontiversioita tai Linux-apuohjelmia tai keskeyttää työnkulkua siirtymällä Linux-kuoreen. Vain asenna WSL, määritä PowerShell-profiili и luettele komennot, jotka haluat tuoda! Runsas automaattinen täydennys Linux- ja Windows-komentoparametreille ja tiedostopoluille on toiminto, joka ei ole saatavilla edes alkuperäisissä Windowsin komentoissa.

Yllä kuvattu täydellinen lähdekoodi sekä lisäohjeet sen sisällyttämiseksi työnkulkuun ovat saatavilla täällä.

Mitkä Linux-komennot ovat mielestäsi hyödyllisimpiä? Mitä muita yleisiä asioita puuttuu, kun työskentelet Windowsissa? Kirjoita kommentteihin tai GitHubissa!

Lähde: will.com

Lisää kommentti