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!
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!
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:
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!