Linux-parancsok integrálása a Windowsba PowerShell és WSL használatával

Tipikus kérdés a Windows fejlesztőktől: „Miért még mindig nincs <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Legyen szó erőteljes csúsztatásról less vagy ismerős eszközöket grep vagy sed, a Windows-fejlesztők mindennapi munkájuk során könnyű hozzáférést szeretnének elérni ezekhez a parancsokhoz.

Windows alrendszer Linuxhoz (WSL) óriási előrelépést tett e tekintetben. Lehetővé teszi Linux-parancsok meghívását a Windowsból, proxy használatával wsl.exe (például wsl ls). Bár ez jelentős előrelépés, ennek a lehetőségnek számos hátránya van.

  • Mindenütt jelenlévő kiegészítés wsl fárasztó és természetellenes.
  • A Windows elérési utak az argumentumokban nem mindig működnek, mert a fordított perjeleket a rendszer escape karakterként értelmezi, nem pedig könyvtárelválasztóként.
  • Az argumentumokban szereplő Windows elérési utak nem fordítódnak le a WSL megfelelő csatolási pontjára.
  • Az álneveket és környezeti változókat tartalmazó WSL-profilok nem veszik figyelembe az alapértelmezett beállításokat.
  • A Linux elérési út befejezése nem támogatott.
  • A parancsok befejezése nem támogatott.
  • Az érvelés befejezése nem támogatott.

Ennek eredményeként a Linux-parancsokat másodosztályú polgárokként kezelik Windows alatt – és nehezebben használhatók, mint a natív parancsok. Jogaik kiegyenlítéséhez szükséges a felsorolt ​​problémák megoldása.

PowerShell függvényburkolók

A PowerShell függvényburkolók segítségével parancskiegészítést adhatunk hozzá, és kiküszöbölhetjük az előtagok használatát wsl, a Windows elérési utak WSL elérési útra fordítása. A kagylókkal szemben támasztott alapvető követelmények:

  • Minden Linux-parancshoz egy, azonos nevű függvényburkolónak kell lennie.
  • A shellnek fel kell ismernie az argumentumként átadott Windows elérési utakat, és át kell alakítania WSL útvonalakká.
  • A héjnak hívnia kell wsl a megfelelő Linux paranccsal bármely folyamatbemenetre, és átadja a függvénynek átadott parancssori argumentumokat.

Mivel ez a minta bármely parancsra alkalmazható, elvonatkoztathatjuk ezeknek a burkolóknak a definícióját, és dinamikusan generálhatjuk őket az 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 import parancsokat határoz meg. Ezután a parancs segítségével dinamikusan generálunk egy függvényburkolót mindegyikhez Invoke-Expression (először eltávolít minden olyan álnevet, amely ütközik a funkcióval).

A függvény ismétlődik a parancssori argumentumokon, és parancsok segítségével határozza meg a Windows elérési útját Split-Path и Test-Pathmajd ezeket az elérési utakat WSL útvonalakká alakítja. Az utakat egy segítő függvényen keresztül vezetjük le Format-WslArgument, amelyet később fogunk meghatározni. Megkerüli a speciális karaktereket, például a szóközöket és a zárójeleket, amelyeket egyébként félreértelmeznének.

Végül közöljük wsl pipeline bemenet és bármely parancssori argumentum.

Ezekkel a wrapperekkel természetesebb módon hívhatja meg kedvenc Linux-parancsait előtag hozzáadása nélkül wsl és anélkül, hogy aggódnia kellene az útvonalak átalakítása miatt:

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

A parancsok alapkészlete itt látható, de bármely Linux-parancshoz létrehozhat shell-t, ha egyszerűen hozzáadja a listához. Ha hozzáadja ezt a kódot a profil PowerShell, ezek a parancsok minden PowerShell-munkamenetben elérhetőek lesznek, akárcsak a natív parancsok!

Alapbeállítások

Linuxban gyakori az álnevek és/vagy környezeti változók meghatározása a bejelentkezési profilokban, alapértelmezett paraméterek megadásával a gyakran használt parancsokhoz (pl. alias ls=ls -AFh vagy export LESS=-i). A nem interaktív héjon keresztüli proxyzás egyik hátránya wsl.exe - hogy a profilok nincsenek betöltve, így ezek az opciók alapértelmezés szerint nem érhetők el (pl. ls WSL-ben és wsl ls másként fog viselkedni a fent meghatározott álnévvel).

A PowerShell biztosítja $PSDefaultParameterValues, egy szabványos mechanizmus az alapértelmezett paraméterek meghatározásához, de csak parancsmagokhoz és speciális funkciókhoz. Természetesen készíthetünk speciális függvényeket a héjainkból, de ez szükségtelen bonyodalmakat okoz (például a PowerShell a részleges paraméterneveket korrelálja (pl. -a -vel korrelál -ArgumentList), amely ütközik azokkal a Linux parancsokkal, amelyek részleges neveket vesznek fel argumentumként), és az alapértelmezett értékek meghatározásának szintaxisa nem lesz a legmegfelelőbb (az alapértelmezett argumentumokhoz a paraméter neve szükséges a kulcsban, nem csak a parancsnév) .

A héjaink kis módosításával azonban megvalósíthatunk egy hasonló modellt $PSDefaultParameterValues, és engedélyezze az alapértelmezett beállításokat a Linux parancsokhoz!

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

Elhaladó $WslDefaultParameterValues a parancssorba a paramétereket keresztül küldjük wsl.exe. Az alábbiakban bemutatjuk, hogyan adhat hozzá utasításokat a PowerShell-profilhoz az alapértelmezett beállítások konfigurálásához. Most megtehetjük!

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

Mivel a paraméterek modellezése után $PSDefaultParameterValues, Tudsz könnyű letiltani őket ideiglenesen a kulcs telepítésével "Disabled" jelentésbe $true. A különálló hash tábla további előnye a letiltási lehetőség $WslDefaultParameterValues attól külön-külön $PSDefaultParameterValues.

Érvelés befejezése

A PowerShell lehetővé teszi argumentumelőzetesek regisztrálását a paranccsal Register-ArgumentCompleter. Bash erős programozható automatikus kitöltési eszközök. A WSL lehetővé teszi a bash meghívását a PowerShellből. Ha a PowerShell függvényburkolóinkhoz regisztrálhatunk argumentumkiegészítéseket, és a bash-t meghívjuk a befejezések generálásához, akkor a teljes argumentumkiegészítést ugyanolyan pontossággal kapjuk meg, mint maga a 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]
    }
}

A kód egy kicsit sűrű anélkül, hogy megértené a bash néhány belső funkcióját, de alapvetően a következőt tesszük:

  • Egy argumentumkiegészítő regisztrálása az összes függvényburkolóhoz egy lista átadásával $commands paraméterben -CommandName a Register-ArgumentCompleter.
  • Minden parancsot leképezünk arra a shell függvényre, amelyet a bash az automatikus kiegészítéshez használ (az automatikus kiegészítés specifikációinak meghatározásához, a bash használatához $F, a rövidítése complete -F <FUNCTION>).
  • PowerShell-argumentumok konvertálása $wordToComplete, $commandAst и $cursorPosition a bash automatikus kiegészítési funkciói által elvárt formátumba a specifikációknak megfelelően programozható automatikus kiegészítés bash.
  • Összeállítunk egy parancssort az átvitelhez wsl.exe, amely biztosítja a környezet helyes beállítását, meghívja a megfelelő automatikus kiegészítési függvényt, és soronként adja ki az eredményeket.
  • Aztán hívjuk wsl a parancssorral sorelválasztókkal választjuk el a kimenetet, és mindegyikhez generáljuk CompletionResults, rendezi őket, és kihagyja az olyan karaktereket, mint a szóközök és a zárójelek, amelyeket egyébként félreértelmeznének.

Ennek eredményeként a Linux parancshéjaink pontosan ugyanazt az automatikus kiegészítést fogják használni, mint a bash! Például:

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

Minden automatikus kiegészítés az előző argumentumra jellemző értékeket ad, és beolvassa a konfigurációs adatokat, például az ismert gazdagépeket a WSL-ből!

<TAB> váltogatja a paramétereket. <Ctrl + пробел> megjeleníti az összes elérhető lehetőséget.

Ráadásul, mivel most már van bash automatikus kiegészítésünk, a Linux elérési útjait automatikusan kiegészítheti közvetlenül a PowerShellben!

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

Azokban az esetekben, amikor a bash automatikus kiegészítés nem hoz eredményt, a PowerShell visszaállítja a rendszer alapértelmezett Windows-útvonalait. Így a gyakorlatban saját belátása szerint egyszerre használhatja mindkét utat.

Következtetés

A PowerShell és a WSL használatával a Linux parancsokat natív alkalmazásként integrálhatjuk a Windowsba. Nem kell Win32 buildeket vagy Linux segédprogramokat keresni, és nem kell megszakítania a munkafolyamatot egy Linux rendszerhéj megnyitásával. Éppen telepítse a WSL-t, Beállítás PowerShell profil и listázza ki az importálni kívánt parancsokat! A Linux és Windows parancsparaméterek és fájlútvonalak gazdag automatikus kiegészítése olyan funkció, amely ma még a natív Windows parancsokban sem érhető el.

A fent leírt teljes forráskód, valamint a munkafolyamatba való beépítéséhez további útmutatások elérhetők itt.

Melyik Linux-parancsot találja a leghasznosabbnak? Milyen egyéb gyakori dolgok hiányoznak a Windows rendszerben végzett munka során? Írd meg kommentben ill a GitHubon!

Forrás: will.com

Hozzászólás