Vi integrerar Linux-kommandon i Windows med hjÀlp av PowerShell och WSL

En typisk frĂ„ga frĂ„n utvecklare pĂ„ Windows: "Varför finns det fortfarande inget ?». БуЮь Ń‚ĐŸ ĐŒĐŸŃ‰ĐœĐŸĐ” ĐżŃ€ĐŸĐ»ĐžŃŃ‚Ń‹ĐČĐ°ĐœĐžĐ” less eller vanliga verktyg grep eller sed, utvecklare pĂ„ Windows vill ha enkel tillgĂ„ng till dessa kommandon i sitt dagliga arbete.

Windows Subsystem for Linux (WSL) har gjort enorma framsteg i detta avseende. Det möjliggör att anropa Linux-kommandon frĂ„n Windows, genom att proxya dem via wsl.exe (till exempel, wsl ls). Även om detta Ă€r en betydande förbĂ€ttring lider den av flera nackdelar.

  • Utbredningen av wsl Ă€r tröttande och onaturlig.
  • Windows-sökvĂ€gar i argument fungerar inte alltid, eftersom bakĂ„tsnedstreck tolkas som escape-tecken snarare Ă€n katalogavgrĂ€nsare.
  • Windows-sökvĂ€gar i argument översĂ€tts inte till motsvarande monteringspunkt i WSL.
  • StandardinstĂ€llningar i WSL-profiler med alias och miljövariabler beaktas inte.
  • Slutförande av Linux-sökvĂ€gar stöds inte.
  • Slutförande av kommandon stöds inte.
  • Slutförande av argument stöds inte.

Som ett resultat uppfattas Linux-kommandon under Windows som andra klassens medborgare - och det Àr svÄrare att anvÀnda dem Àn inhemska kommandon. För att jÀmna ut deras rÀttigheter mÄste de angivna problemen lösas.

PowerShell-funktionsskal

Med hjÀlp av PowerShell-funktionsskal kan vi lÀgga till autocompletion av kommandon och eliminera behovet av prefix wsl, genom att översÀtta Windows-sökvÀgar till WSL-sökvÀgar. GrundlÀggande krav förskal Àr:

  • För varje Linux-kommando ska det finnas ett funktionsskal med samma namn.
  • Skalet ska kĂ€nna igen Windows-sökvĂ€gar som skickas som argument och omvandla dem till WSL-sökvĂ€gar.
  • Skalet ska anropa wsl med det motsvarande Linux-kommandot för vilken inmatning som helst av pipelinen och vidarebefordra eventuella kommandoradsargument som skickas till funktionen.

Eftersom denna mall kan tillÀmpas pÄ vilket kommando som helst kan vi abstrahera definieringen av dessa skal och dynamiskt generera dem frÄn en lista med kommandon för import.

# 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 definierar de kommandon som ska importeras. Sedan genererar vi dynamiskt en funktionswrap för var och en av dem med kommandot Invoke-Expression (först tar vi bort eventuella alias som kan krocka med funktionen).

Funktionen itererar över kommandoradsargument, identifierar Windows-sökvÀgar med kommandon Split-Path och Test-Path, och konverterar sedan dessa sökvÀgar till WSL-sökvÀgar. Vi kör sökvÀgarna genom en hjÀlpfunktion Format-WslArgument, som vi definierar senare. Den hÀr funktionen rymmer speciella tecken, som mellanslag och parenteser, som annars skulle tolkas fel.

Till sist skickar vi wsl inmatningen till pipelinjen och eventuella kommandoradsargument.

Med hjÀlp av sÄdana wrapper kan du anropa dina favoritkommandon i Linux pÄ ett mer naturligt sÀtt utan att behöva lÀgga till ett prefix wsl och utan att oroa dig för hur sökvÀgarna omvandlas:

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

HÀr visas ett grundlÀggande kommandouppsÀttning, men du kan skapa en wrapper för vilket Linux-kommando som helst genom att lÀgga till det i listan. Om du lÀgger till denna kod i din profil PowerShell, kommer dessa kommandon att vara tillgÀngliga för dig i varje PowerShell-session, likt inbyggda kommandon!

StandardinstÀllningar

I Linux Àr det vanligt att definiera alias och/eller miljövariabler i profiler (inloggningsprofil), som stÀller in standardinstÀllningar för ofta anvÀnda kommandon (t.ex. alias ls=ls -AFh eller export LESS=-i). En av nackdelarna med proxying genom en icke-interaktiv shell wsl.exe Àr att profilerna inte laddas, sÄ dessa standardinstÀllningar Àr inte tillgÀngliga (det vill sÀga, ls i WSL och wsl ls beter sig olika med aliaset som definierades ovan).

PowerShell tillhandahÄller $PSDefaultParameterValues, en standardmekanism för att definiera standardparametrar, men endast för cmdlets och avancerade funktioner. SjÀlvklart kan vi göra vÄra shell till avancerade funktioner, men det komplicerar saker (glas, PowerShell matchar delvisa parameter-namn (t.ex. -a matchar till -ArgumentList), vilket kommer att krocka med Linux-kommandon som tar delnamn som argument), och syntaxen för att definiera standardvÀrden Àr inte helt lÀmplig (för att definiera standardargument krÀvs parameter-namnet i nyckeln, inte bara kommandonamnet).

Med en liten Àndring av vÄra shells kan vi dock implementera en modell liknande $PSDefaultParameterValues, och införa standardparametrar för Linux-kommandon!

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

Genom att skicka $WslDefaultParameterValues i kommandoraden skickar vi parametrar genom wsl.exe. Nedan visas hur du lÀgger till instruktioner i PowerShell-profilen för att konfigurera standardparametrar. Nu kan vi göra det!

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

Eftersom parametrarna modelleras efter $PSDefaultParameterValues, kan du lÀtt stÀnga av dem tillfÀlligt genom att sÀtta en nyckel "Disabled" till vÀrdet $true. En ytterligare fördel med en separat hash-tabell Àr möjligheten att stÀnga av $WslDefaultParameterValues separat frÄn $PSDefaultParameterValues.

Automatisk avslutning av argument

PowerShell tillÄter registrering av argumentavslutare med kommandot Register-ArgumentCompleter. I Bash finns det kraftfulla programmerbara verktyg för automatisk avslutning. WSL tillÄter att anropa bash frÄn PowerShell. Om vi kan registrera avslutare för vÄra PowerShell-funktioners skal och anropa bash för att skapa avslutningar, fÄr vi fullstÀndig automatisk avslutning av argument med samma precision som i bash sjÀlv!

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

Koden Àr lite kompakt utan att förstÄ vissa interna bash-funktioner, men i huvudsak gör vi följande:

  • Vi registrerar en avslutare för argument för alla vĂ„ra funktionswraps genom att skicka en lista $commands i parametern -CommandName för Register-ArgumentCompleter.
  • Matchar varje kommando med den skalfunktion som bash anvĂ€nder för automatisk avslutning (för att definiera specifikationerna för automatisk avslutning i bash anvĂ€nds $F, en förkortning av complete -F).
  • Omvandlar PowerShell-argumenten $wordToComplete, $commandAst och $cursorPosition till det format som förvĂ€ntas av bash:s automatiska avslutningsfunktioner enligt specifikationerna för programmerbar automatisk avslutning bash.
  • SĂ€tter ihop kommandoraden för att skicka till wsl.exe, som ser till att miljön Ă€r korrekt konfigurerad, anropar den relevanta avslutningsfunktionen och returnerar resultaten med radindelning.
  • Sedan anropar vi wsl med kommandoraden, delar utdata med radavskiljare och genererar för varje CompletionResults, sorterar dem och rymmer tecken som mellanslag och parenteser som annars skulle missförstĂ„s.

SÄ smÄningom kommer vÄra Linux-kommandoskal att anvÀnda exakt samma automatiska avslutning som i bash! Till exempel:

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

Varje autokomplettering ger vÀrden som Àr specifika för den föregÄende parametern, genom att lÀsa konfigurationsdata, som kÀnda vÀrdar, frÄn WSL!

<TAB> kommer att cykliskt iterera över parametrarna. <Ctrl + ĐżŃ€ĐŸĐ±Đ”Đ»> visar alla tillgĂ€ngliga alternativ.

Dessutom, eftersom vi nu har bash-autokomplettering, kan du autocompleta Linux-sökvÀgar direkt i PowerShell!

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

I de fall dÀr bash-autokomplettering inte ger nÄgra resultat, gÄr PowerShell tillbaka till standardssystemet med Windows-sökvÀgar. SÄledes kan du i praktiken anvÀnda bÄde typer av sökvÀgar enligt eget val.

Slutsats

Med PowerShell och WSL kan vi integrera Linux-kommandon i Windows som inbyggda applikationer. Det finns ingen anledning att leta efter Win32-byggnader eller Linux-verktyg eller avbryta arbetsflödet genom att vÀxla till Linux-shell. bara installera WSL, konfigurera PowerShell-profilen och lista de kommandon du vill importera! Rich autocompletion för parametrar av kommandon och sökvÀgar till filer i Linux och Windows Àr en funktionalitet som idag inte ens finns i de inbyggda Windows-kommandona.

Den fullstÀndiga koden som beskrivs ovan samt ytterligare rekommendationer för hur man inkluderar den i arbetsflödet Àr tillgÀngliga hÀr.

Vilka Linux-kommandon tycker du Àr mest anvÀndbara? Vilka andra bekanta funktioner saknas nÀr du arbetar i Windows? Skriv i kommentarerna eller pÄ GitHub!

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster