Typowe pytanie programistów Windows: „Dlaczego tutaj wciąż nie ma <WSTAW TUTAJ ULUBIONĄ KOMENDĘ LINUX>?». Будь то мощное пролистывание less lub znanych narzędzi grep lub sed, programiści Windows chcą uzyskać łatwy dostęp do tych komend w codziennej pracy.
zrobił ogromny krok naprzód w tym zakresie. Umożliwia wywoływanie poleceń Linux z Windows, przesyłając je przez wsl.exe (na przykład, wsl ls). Mimo że to znaczna poprawa, to takie rozwiązanie ma szereg wad.
- Powszechne dodawanie
wsljest męczące i nienaturalne. - Ścieżki Windows w argumentach nie zawsze działają, ponieważ odwrotne ukośniki są interpretowane jako znaki ucieczki, a nie jako separator katalogów.
- Ścieżki Windows w argumentach nie są tłumaczone na odpowiedni punkt montowania w WSL.
- Nie uwzględnia domyślnych parametrów w profilach WSL z aliasami i zmiennymi środowiskowymi.
- Nie obsługuje uzupełniania ścieżek Linux.
- Nie obsługuje uzupełniania komend.
- Nie obsługuje uzupełniania argumentów.
W rezultacie polecenia Linux są postrzegane w Windows jako obywatele drugiej kategorii — trudniejsze w użyciu niż native commands. Aby wyrównać ich prawa, należy rozwiązać wymienione problemy.
Funkcje powłok PowerShell
Dzięki funkcjom powłok PowerShell możemy dodać automatyczne uzupełnianie poleceń i wyeliminować potrzebę prefiksów wsl, tłumacząc ścieżki Windows na ścieżki WSL. Podstawowe wymagania dla powłok to:
- Dla każdego polecenia Linux powinna być jedna powłoka funkcji o tej samej nazwie.
- Powłoka powinna rozpoznawać ścieżki Windows przekazywane jako argumenty i przekształcać je w ścieżki WSL.
- Powłoka powinna wywoływać
wslz odpowiednim poleceniem Linux dla każdego wejścia potoku i przekazując wszelkie argumenty wiersza poleceń przekazywane do funkcji.
Ponieważ ten szablon może być zastosowany do dowolnego polecenia, możemy abstrahować definicję tych powłok i dynamicznie je generować z listy poleceń do importu.
# 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 określa polecenia do importu. Następnie dynamicznie generujemy opakowanie funkcji dla każdego z nich, używając polecenia Invoke-Expression (najpierw usuwając wszelkie aliasy, które mogą kolidować z funkcją).
Funkcja przeszukuje argumenty wiersza poleceń, określa ścieżki Windows za pomocą poleceń Split-Path i Test-Path, a następnie konwertuje te ścieżki na ścieżki WSL. Przechodzimy przez ścieżki za pomocą funkcji pomocniczej Format-WslArgument, którą określimy później. Escapuje ona specjalne znaki, takie jak spacje i nawiasy, które w przeciwnym razie mogłyby być błędnie zinterpretowane.
Na koniec przekazujemy wsl dane wejściowe potoku oraz wszelkie argumenty wiersza poleceń.
Dzięki takim wrapperom można wywoływać ulubione komendy Linuxa w bardziej naturalny sposób, bez dodawania prefiksu wsl i bez martwienia się, jak są konwertowane ścieżki:
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Tutaj przedstawiony jest podstawowy zestaw komend, ale możesz stworzyć wrapper dla dowolnej komendy Linuxa, po prostu dodając ją do listy. Jeśli dodasz ten kod do swojego PowerShell, te komendy będą dostępne w każdej sesji PowerShell, tak jak komendy natywne!
Domyślne parametry
W Linuxie powszechnie określa się aliasy i/lub zmienne środowiskowe w profilach (profil logowania), ustalając domyślne parametry dla często używanych komend (na przykład, alias ls=ls -AFh lub export LESS=-i). Jedną z wad proxy przez nieinteraktywną powłokę wsl.exe jest to, że profile się nie ładują, więc te domyślne parametry są niedostępne (tzn. ls w WSL i wsl ls będą zachowywać się różnie z aliasem określonym powyżej).
PowerShell udostępnia , standardowy mechanizm definiowania domyślnych parametrów, ale tylko dla cmdletów i zaawansowanych funkcji. Oczywiście, możemy z naszych powłok zrobić zaawansowane funkcje, ale wprowadza to dodatkowe komplikacje (tak, PowerShell odnosi się do częściowych nazw parametrów (na przykład, -a odnosi się do -ArgumentList), które będą kolidować z komendami Linuxa akceptującymi częściowe nazwy jako argumenty), a składnia do definiowania domyślnych wartości nie będzie najbardziej odpowiednia (do określenia argumentów domyślnych wymagane jest podanie nazwy parametru w kluczu, a nie tylko nazwy komendy).
Jednak dzięki niewielkiej zmianie naszych powłok możemy wdrożyć model podobny do $PSDefaultParameterValues, i włączyć domyślne parametry dla komend Linuxa!
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 ' ')
}
} Przekazując $WslDefaultParameterValues w terminalu przesyłamy parametry przez wsl.exe. Poniżej pokazano, jak dodać instrukcje do profilu PowerShell w celu skonfigurowania wartości domyślnych. Teraz możemy to zrobić!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Ponieważ parametry są modelowane po $PSDefaultParameterValues, możesz na chwilę, ustawiając klucz "Disabled" na wartość $true. Dodatkową zaletą osobnej tablicy mieszanej jest możliwość wyłączenia $WslDefaultParameterValues oddzielnie od $PSDefaultParameterValues.
Automatyczne uzupełnianie argumentów
PowerShell pozwala rejestrować uzupełniacze argumentów za pomocą polecenia Register-ArgumentCompleter. W Bash są potężne . WSL pozwala wywoływać bash z PowerShell. Jeśli możemy zarejestrować uzupełniacze argumentów dla naszych funkcji w PowerShell i wywołać bash w celu utworzenia uzupełnień, uzyskamy pełne automatyczne uzupełnienie argumentów z taką samą precyzją, jak w samym bashu!
# 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]
}
}Kod jest nieco zbity bez zrozumienia niektórych wewnętrznych funkcji bash, ale w zasadzie robimy to:
- Rejestrujemy uzupełniacz argumentów dla wszystkich naszych funkcji opakowujących, przekazując listę
$commandsdo parametru-CommandNamedoRegister-ArgumentCompleter. - Mapujemy każdą komendę z funkcją powłoki, którą bash używa do automatycznego uzupełnienia (do określenia specyfikacji automatycznego uzupełniania w bash używa się
$F, skrót odcomplete -F). - Przekształcamy argumenty PowerShell
$wordToComplete,$commandAsti$cursorPositionw format oczekiwany przez funkcje automatycznego uzupełniania bash zgodnie ze specyfikacjami bash. - Składamy polecenie do przekazania do
wsl.exe, które zapewnia poprawną konfigurację środowiska, wywołuje odpowiednią funkcję automatycznego uzupełnienia i zwraca wyniki z podziałem na linie. - Następnie wywołujemy
wslz poleceniem, dzielimy wynik na podstawie końców linii i generujemy dla każdegoCompletionResults, sortując je i eskapując znaki, takie jak spacje i nawiasy, które w przeciwnym razie byłyby błędnie interpretowane.
W rezultacie nasze powłoki komend Linux będą używać dokładnie takiego samego uzupełnienia automatycznego, jak w bashu! Na przykład:
ssh -c -J -m -O -o -Q -w -b
Każde autouzpełnienie dostarcza wartości specyficzne dla wcześniejszego argumentu, odczytując dane konfiguracyjne, takie jak znane hosty, z WSL!
<TAB> będzie cyklicznie iterować przez parametry. <Ctrl + пробел> wyświetli wszystkie dostępne opcje.
Ponadto, ponieważ teraz mamy działające autouzpełnienie bash, możesz autouzpełniać ścieżki Linuxa bezpośrednio w PowerShell!
less /etc/ls /usr/share/vim ~/.ash
W przypadkach, gdy autouzpełnienie bash nie dostarcza żadnych wyników, PowerShell powraca do systemu domyślnego z ścieżkami Windows. Tak więc, w praktyce możesz używać jednocześnie zarówno tych, jak i innych ścieżek według własnego uznania.
Podsumowanie
Dzięki PowerShell i WSL możemy integrować polecenia Linuxa w Windows jako natywne aplikacje. Nie ma potrzeby szukania kompilacji Win32 lub narzędzi Linuxa ani przerywania pracy, przechodząc do powłoki Linux. Po prostu , skonfiguruj i ! Bogate autouzpełnienie dla parametrów poleceń i ścieżek plików Linux i Windows to funkcjonalność, której dziś nie ma nawet w natywnych poleceniach Windows.
Pełny kod źródłowy, opisany powyżej, a także dodatkowe zalecenia dotyczące jego włączenia do procesu roboczego są dostępne .
Jakie polecenia Linuxa uważasz za najbardziej przydatne? Jakich jeszcze znanych funkcji brakuje podczas pracy w Windows? Napisz w komentarzach lub !
Źródło: habr.com
