Linux parancsok integrálása a Windowsba PowerShell és WSL segítségével

A tipikus kérdés Windows fejlesztőktől: "Miért nincs itt még <ITT HELYEZZE EL KEDVENC LINUX PARANCSÁT>?». Будь то мощное пролистывание ezt vagy a megszokott eszközök grep vagy sed, a Windows fejlesztők könnyen szeretnék használni ezeket a parancsokat a mindennapi munkájuk során.

A Windows Subsystem for Linux (WSL) megjelent ebből a szempontból. Lehetővé teszi Linux parancsok hívását Windowsból, a következő módon: wsl.exe (például, wsl ls). Bár ez jelentős javulás, ez a megoldás számos hátránnyal küzd.

  • A gyártási folyamatok gyakori megjelenése wsl fárasztó és természetellenes.
  • A Windows útvonalak az argumentumokban nem mindig működnek, mert a visszafelé képzett perjeket escape-karakterekként értelmezik, nem könyvtárelválasztókként.
  • A Windows útvonalak az argumentumokban nem fordítódnak a megfelelő WSL csatolási pontra.
  • Az alapértelmezett beállításokat nem veszik figyelembe a WSL profilokban az aliasokkal és környezeti változókkal.
  • A Linux útvonalak befejezése nincs támogatva.
  • A parancsok befejezése nincs támogatva.
  • Az argumentumok befejezése nincs támogatva.

Ennek eredményeként a Linux parancsokat a Windows alatt másodrangú állampolgárokként kezelik — nehezebb használni őket, mint a natív parancsokat. A jogegyenlőségért meg kell oldani a felsorolt problémákat.

A PowerShell funkcióshelljei

A PowerShell funkcióshelljeivel hozzáadhatjuk a parancsok automatikus befejezését, ezzel megszüntetve a prefixek szükségességét, wsl, átkonvertálva a Windows útvonalakat WSL útvonalakká. A shellsekkel szembeni alapvető követelmények:

  • Minden Linux parancshoz szükséges egy shell funkció ugyanazzal a névvel.
  • A shellnek fel kell ismernie a Windows útvonalakat, amelyeket argumentumként adtak meg, és WSL útvonalakká kell alakítania őket.
  • A shellnek a megfelelő Linux paranccsal kell hívnia wsl bármilyen csőbejövőhöz, átadva bármely parancssori argumentumot a funkciónak.

Mivel ez a minta bármely parancsra alkalmazható, elvonhatjuk a shellok meghatározását, és dinamikusan generálhatjuk őket a importálandó parancsok listájából.

# 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 meghatározza a beimportálandó parancsokat. Ezután dinamikusan generálunk egy wrapper funkciót mindegyikhez, a következő parancs segítségével: Invoke-Expression (először eltávolítva az összes alias-t, amelyek konfliktusba kerülhetnek a funkcióval).

A funkció végigmegy a parancssori argumentumokon, és a Windows útvonalakat a következő parancsokkal határozza meg: Split-Path és Test-Path, majd átkonvertálja ezeket az utakat WSL-útvá. Az utakat egy segédfunkción keresztül futtatjuk Format-WslArgument, amelyet majd később meghatározunk. Ez escape-eli a speciális karaktereket, mint a szóközök és zárójelek, amelyeket egyébként helytelenül értelmeznének.

Végül átadjuk wsl a cső bemeneteit és az összes parancssori argumentumot.

Ilyen burkolatokkal a kedvenc Linux parancsainkat természetesebb módon hívhatjuk meg, anélkül, hogy előtagot kellene hozzáadnunk wsl és nem kell aggódnunk, hogy miként alakulnak az utak:

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

Itt egy alap parancsgyűjteményt láthatunk, de bármilyen Linux parancs számára készíthetünk egy shell-t, egyszerűen hozzáadva a listához. Ha ezt a kódot hozzáadja a profil PowerShell-hez, ezek a parancsok elérhetővé válnak minden PowerShell munkamenetben, akárcsak a natív parancsok!

Alapértelmezett beállítások

Linux alatt szokás az aliasok és/vagy környezeti változók meghatározása a profilokban (bejelentkezési profil), alapértelmezett beállítások meghatározása a gyakran használt parancsokhoz (például, alias ls=ls -AFh vagy export LESS=-i). Az egyetlen hátránya az interaktív shell-en keresztüli proxyzásnak wsl.exe az, hogy a profilok nem töltődnek be, így ezek az alapértelmezett beállítások nem állnak rendelkezésre (azaz, ls WSL alatt, és wsl ls másképp fognak viselkedni a fent meghatározott alias-szal.

A PowerShell biztosít $PSDefaultParameterValues, egy standard mechanizmust az alapértelmezett paraméterek meghatározására, de csak parancsokhoz és kiterjesztett funkciókhoz. Természetesen az alapértelmezett funkciókból kiterjesztett funkciókat is készíthetünk, de ez felesleges bonyodalmakat okoz (így a PowerShell az részleges paraméterneveket (például, -a összekapcsolás - -ArgumentList), amelyek ütközni fognak a Linux parancsokkal, amelyek részleges neveket várnak paraméterekként), és az alapértelmezett értékek meghatározásának szintaxisa nem a legmegfelelőbb (az alapértelmezett argumentumok meghatározásához a paraméter nevét kell használni a kulcsban, nemcsak a parancs nevét).

Mindazonáltal némi módosítással a shell-jeinkben megvalósíthatjuk a Linux parancsokhoz hasonló modellt, és beépíthetjük az alapértelmezett paramétereket! $PSDefaultParameterValuesfunction 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 ' ') } }

Átadva

$WslDefaultParameterValues $WslDefaultParameterValues a parancssorba paramétereket küldünk el wsl.exe. Lent látható, hogyan lehet utasításokat hozzáadni a PowerShell profilhoz az alapértelmezett paraméterek beállításához. Most már megtehetjük!

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

Mivel a paraméterek modellezve vannak a $PSDefaultParameterValues, könnyedén ki tudjuk őket kapcsolni arra az időre, hogy beállítsuk a kulcsot "Disabled" az értékre $true. Egy külön hash-tábla plusz előnye, hogy lehetővé teszi a $WslDefaultParameterValues különböző letiltását $PSDefaultParameterValues.

Az argumentumok automatikus kiegészítése

A PowerShell lehetővé teszi az argumentumok kiegészítőinek nyilvántartását a Register-ArgumentCompleterparancs segítségével. A Bash-ban erőteljes programozható eszközök állnak rendelkezésre az automatikus kiegészítéshez.A WSL lehetővé teszi a bash hívását a PowerShell-ből. Ha tudjuk regisztrálni az argumentumkiegészítőket a PowerShell funkcióinkhoz, és hívni a bash-t a kiegészítések létrehozásához, akkor ugyanúgy teljes automatikus kiegészítést kapunk az argumentumokhoz, mint magában a bash-ben!

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

A kód kicsit sűrű, ha nem értjük néhány belső bash-funkciót, de alapvetően a következőt tesszük:

  • Regisztráljuk az argumentumkiegészítőt minden funkciócsomagunkhoz, átadva egy listát $commands Ezért döntöttünk úgy, hogy eltávolítjuk ezt a plusz utat a -CommandName a Register-ArgumentCompleter.
  • Minden parancsot összepárosítunk a bash által használt shell funkcióval az automatikus kiegészítéshez (az automatikus kiegészítési specifikációk meghatározásához a bash-ban a $F, azaz a complete -F).
  • Az argumentumokat a PowerShell-ből $wordToComplete, $commandAst és $cursorPosition átalakítjuk a bash automatikus kiegészítő funkciói által várt formátumra a programozott automatikus kiegészítés bash-specifikációinak megfelelően.
  • Összeállítjuk a parancssort, amelyet átadunk a wsl.exe, amely biztosítja a megfelelő környezetbeállítást, meghívja a megfelelő automatikus kiegészítő funkciót, és az eredményeket soronként kiadja.
  • Ezután hívjuk meg wsl a parancssorral, a kimenetet sorok elválasztásával osztva, és minden egyes CompletionResultsgenerálva, amelyek a szóközöket és zárójeleket, mint lehetséges félreérthető karaktereket rendezik és elkülönítik.

Végül a Linux parancssoraink pontosan olyan automatikus kiegészítést fognak használni, mint a bash! Például:

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

A kódautomatikusan kitölti az előző argumentumhoz kapcsolódó, specifikus értékeket, konfigurációs adatokat olvasva, például ismert hosztokat a WSL-ből!

<TAB> ciklikusan végigjárja a paramétereket. <Ctrl + пробел> megmutatja az összes elérhető lehetőséget.

Továbbá, mivel most már működik a bash automatikus kiegészítése, közvetlenül a PowerShell-ben is kiegészítheti a Linux útvonalakat!

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

Olyan esetekben, amikor a bash automatikus kiegészítése nem ad eredményeket, a PowerShell visszatér az alapértelmezett Windows útvonalakhoz. Így gyakorlatilag mindkét útvonalat egyszerre használhatja az igényei szerint.

Zárás

A PowerShell és a WSL segítségével integrálhatjuk a Linux parancsokat a Windows-be, mint natív alkalmazások. Nincs szükség Win32 környezetek vagy Linux eszközök keresésére, vagy a munkafolyamat megszakítására, amikor a Linux shell-be lépünk. Csak állítsa be a WSL-t, konfigurálja a PowerShell profilt és listázza a parancsokat, amelyeket importálni szeretne! A gazdag automatikus kiegészítés a parancsparaméterekhez és a Linux és Windows fájlok elérési útjaihoz olyan funkció, amely ma még a natív Windows parancsokban sincs.

A fenti teljes forráskód, valamint további ajánlások a munkafolyamatba való integrálásáról elérhető itt.

Mely Linux parancsokat tart a leghasznosabbnak? Milyen ismerős hiányzik még a Windows használatakor? Írja meg a megjegyzésekben vagy a GitHub-on!

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster