Et typisk spørgsmål fra Windows-udviklere: «Hvorfor er der stadig ikke <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less eller kendte værktøjer grep eller sed, udviklere under Windows ønsker at få nem adgang til disse kommandoer i deres daglige arbejde.
har gjort store fremskridt i denne henseende. Det tillader kørsel af Linux-kommandoer fra Windows ved at proxy dem gennem wsl.exe (for eksempel, wsl ls). Selvom det er en betydelig forbedring, lider denne løsning af flere ulemper.
- Den udbredte integration af
wsler besværlig og unaturlig. - Windows-stier i argumenterne fungerer ikke altid, da tilbageslag bliver tolket som escape-tegn i stedet for som mappeseparatorer.
- Windows-stier i argumenterne konverteres ikke til den tilsvarende monteringspunkt i WSL.
- Default-parametre i WSL-profiler med aliaser og miljøvariabler tages ikke i betragtning.
- Der er ikke understøttelse af autoudfyldning af Linux-stier.
- Der er ikke understøttelse af autoudfyldning af kommandoer.
- Der er ikke understøttelse af autoudfyldning af argumenter.
Som et resultat bliver Linux-kommandoer opfattet under Windows som andengenerationsborgere — og de er sværere at bruge end de nativt understøttede kommandoer. For at give dem lige rettigheder skal de nævnte problemer løses.
PowerShell-funktionsskaller
Med PowerShell-funktionsskaller kan vi tilføje autoudfyldning af kommandoer og fjerne behovet for præfikser wsl, ved at oversætte Windows-stier til WSL-stier. De grundlæggende krav til skallerne er:
- Der skal være én funktionsskal for hver Linux-kommando med det samme navn.
- Skallen skal kunne genkende Windows-stier, der sendes som argumenter, og konvertere dem til WSL-stier.
- Skallen skal kalde
wslmed den tilsvarende Linux-kommando ved enhver input i pipelinen og videregive eventuelle kommandolinjeargumenter, der sendes til funktionen.
Da denne skabelon kan anvendes til enhver kommando, kan vi abstrahere definitionen af disse skaller og dynamisk generere dem fra en liste over kommandoer til 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 ' ')
}
}
"@
} Liste $command definerer kommandoer til import. Derefter genererer vi dynamisk en funktionswrapper for hver af dem ved hjælp af kommandoen Invoke-Expression (først fjerner vi eventuelle aliaser, der kan konflikte med funktionen).
Funktionen gennemgår kommandolinjeargumenterne, identificerer Windows-stier med kommandoerne Split-Path og Test-Path, og derefter konverterer disse stier til WSL-stier. Vi kører stierne gennem en hjælpefunktion Format-WslArgument, som vi definerer senere. Den undgår specielle tegn, såsom mellemrum og parenteser, der ellers ville blive fejltolket.
Endelig videregiver vi wsl pipeline-input og eventuelle kommandolinjeargumenter.
Med sådanne indpakninger kan du kalde dine foretrukne Linux-kommandoer på en mere naturlig måde, uden at tilføje et præfiks wsl og uden at bekymre dig om, hvordan stierne konverteres:
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Her vises et grundlæggende sæt kommandoer, men du kan oprette en indpakning for enhver Linux-kommando ved blot at tilføje den til listen. Hvis du tilføjer denne kode til din PowerShell, vil disse kommandoer være tilgængelige for dig i hver PowerShell-session, ligesom de indfødte kommandoer!
Standardindstillinger
I Linux er det almindeligt at definere aliasser og/eller miljøvariabler i profiler (login profile) for at indstille standardindstillinger for ofte anvendte kommandoer (f.eks. alias ls=ls -AFh eller export LESS=-i). En af ulemperne ved at proxye gennem en ikk-interaktiv shell wsl.exe er, at profilerne ikke indlæses, så disse standardindstillinger ikke er tilgængelige (dvs. ls i WSL og wsl ls vil opføre sig forskelligt med den alias, der er defineret ovenfor).
PowerShell giver , en standardmekanisme til at definere standardparametre, men kun for cmdlets og udvidede funktioner. Selvfølgelig kan vi gøre vores shells til udvidede funktioner, men det tilføjer unødvendige komplikationer (så PowerShell matcher delvise parameternavne (f.eks. -a matcher med -ArgumentList), som vil konflikte med Linux-kommandoer, der accepterer delvise navne som argumenter), og syntaksen til at definere standardværdier vil ikke være ideel (til at definere standardargumenter kræves parameterens navn i nøglen, ikke kun kommandoens navn).
Men med en lille ændring i vores shells kan vi implementere en model, der ligner $PSDefaultParameterValues, og inkludere standardparametre for Linux-kommandoer!
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 ' ')
}
} Ved at videregive $WslDefaultParameterValues i kommandoprompten sender vi parametre gennem wsl.exe. Nedenfor er vist, hvordan man tilføjer instruktioner til PowerShell-profilen for at konfigurere standardparametre. Nu kan vi gøre det!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Da parametre modelleres ud fra $PSDefaultParameterValues, kan du midlertidigt ved at indstille nøglen "Disabled" til værdien $true. En yderligere fordel ved en separat hash-tabel er muligheden for at deaktivere $WslDefaultParameterValues separat fra $PSDefaultParameterValues.
Automatisk fuldførelse af argumenter
PowerShell giver mulighed for at registrere argumentafslutninger ved hjælp af kommandoen Register-ArgumentCompleter. I Bash er der kraftfulde . WSL tillader os at kalde bash fra PowerShell. Hvis vi kan registrere argumentafslutninger for vores PowerShell-funktionsskaller og kalde bash for at generere afslutninger, får vi fuld automatisk fuldførelse af argumenter med samme præcision som i bash selv!
# 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 er lidt tæt uden forståelse af nogle interne bash-funktioner, men grundlæggende gør vi følgende:
- Vi registrerer argumentafslutningen for alle vores funktionsindpakninger ved at sende en liste
$commandstil parameteren-CommandNameforRegister-ArgumentCompleter. - Vi matcher hver kommando med den shell-funktion, som bash bruger til automatisk fuldførelse (for at definere specifikationerne for automatisk fuldførelse i bash bruger vi
$F, forkortelse forcomplete -F). - Vi omdanner PowerShell-argumenterne
$wordToComplete,$commandAstog$cursorPositiontil det format, der forventes af bash'ens autocompletion-funktioner i henhold til specifikationerne for bash. - Vi samler kommandolinjen til overdragelse til
wsl.exe, som sikrer korrekt opsætning af miljøet, kalder den relevante autocompletion-funktion og viser resultaterne i linjeskift. - Så kalder vi
wslmed kommandolinjen, opdelte output med linjeskift og genererer for hverCompletionResults, sorterer dem og undgår tegn som mellemrum og parenteser, som ellers ville blive fejltolket.
I sidste ende vil vores Linux-kommandoer bruge præcis den samme autocompletion som i bash! For eksempel:
ssh -c -J -m -O -o -Q -w -b
Hver afslutning autoudfylder specifikke værdier baseret på den forrige parameter ved at læse konfigurationsdata som kendte værter fra WSL!
<TAB> vil cykle gennem parametre. <Ctrl + пробел> vises alle tilgængelige muligheder.
Desuden, da bash-autoudfyldning nu fungerer, kan du autoudfylde Linux-stier direkte i PowerShell!
less /etc/ls /usr/share/vim ~/ .bash
I de tilfælde, hvor bash-autoudfyldning ikke giver nogen resultater, vender PowerShell tilbage til standard systemet med Windows-stier. Således kan du faktisk bruge begge stier samtidig efter eget valg.
Konklusion
Med PowerShell og WSL kan vi integrere Linux-kommandoer i Windows som native applikationer. Der er ikke behov for at lede efter Win32-builds eller Linux-nyttigheder eller afbryde arbejdsgangen ved at skifte til Linux-shell. Bare , konfigurer og ! Rig autoudfyldning for kommandoernes parametre og filstier fra både Linux og Windows er en funktionalitet, som selv ikke findes i de native Windows-kommandoer i dag.
Den fulde kildekode, der er beskrevet ovenfor, samt yderligere anbefalinger til at inkludere den i arbejdsgangen er tilgængelige .
Hvilke Linux-kommandoer anser du for at være de mest nyttige? Hvilke andre velkendte ting savner du, når du arbejder i Windows? Skriv i kommentarerne eller !
Kilde: habr.com
