Et typisk spørgsmål fra udviklere Windows: "Hvorfor er der ikke noget her endnu?" <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Uanset om det er et kraftigt swipe less eller velkendte værktøjer grep eller sed, udviklere under Windows ønsker nem adgang til disse kommandoer i deres daglige arbejde.
har taget et stort skridt fremad i denne henseende. Det giver dig mulighed for at kalde kommandoer Linux af Windows, der giver dem fuldmagt via wsl.exe (Fx wsl ls). Selvom dette er en væsentlig forbedring, lider denne mulighed af en række ulemper.
- Allestedsnærværende tilføjelse
wslkedeligt og unaturligt. - måder Windows i argumenter virker ikke altid, fordi omvendte skråstreger fortolkes som escape-tegn, ikke mappeseparatorer.
- måder Windows i argumenter oversættes ikke til det tilsvarende monteringspunkt i WSL.
- Standardindstillingerne respekteres ikke i WSL-profiler med aliaser og miljøvariabler.
- Stifuldførelse understøttes ikke Linux.
- Kommandofuldførelse er ikke understøttet.
- Argumentfuldførelse er ikke understøttet.
Som et resultat af holdet Linux opfattes under Windows De er som andenrangsborgere – og sværere at udnytte end deres hjemmehold. For at sikre deres rettigheder skal disse problemer løses.
PowerShell-funktionsindpakninger
Med PowerShell-funktionsindpakninger kan vi tilføje kommandofuldførelse og eliminere behovet for præfikser wsl, udsendelsesstier Windows På WSL-stien. Grundlæggende krav til skaller:
- For hvert hold Linux Der skal være én funktionswrapper med samme navn.
- Skallen skal genkende stier Windows, sendt som argumenter, og konverterer dem til WSL-stier.
- Skallen burde kalde
wslmed den passende kommando Linux til ethvert pipeline-input og videregivelse af eventuelle kommandolinjeargumenter, der sendes til funktionen.
Da dette mønster kan anvendes på enhver kommando, kan vi abstrahere definitionen af disse wrappers og dynamisk generere dem fra en liste over kommandoer, der skal importeres.
# 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 importkommandoer. Vi genererer derefter dynamisk en funktionsindpakning for hver af dem ved hjælp af kommandoen Invoke-Expression (ved først at fjerne eventuelle aliaser, der ville være i konflikt med funktionen).
Funktionen itererer over kommandolinjeargumenterne og bestemmer stierne Windows ved hjælp af kommandoer Split-Path и Test-Pathog konverterer derefter disse stier til WSL-stier. Vi kører stierne gennem en hjælperfunktion Format-WslArgument, som vi vil definere senere. Det undslipper specialtegn såsom mellemrum og parenteser, der ellers ville blive misfortolket.
Til sidst formidler vi wsl pipeline-input og eventuelle kommandolinjeargumenter.
Ved hjælp af sådanne wrappers kan du kalde dine yndlingskommandoer Linux 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 wrapper til enhver kommando. Linux, blot ved at tilføje den til listen. Hvis du tilføjer denne kode til din PowerShell, disse kommandoer vil være tilgængelige for dig i hver PowerShell-session, ligesom native kommandoer!
Standardindstillinger
В Linux Det er almindeligt at definere aliasser og/eller miljøvariabler i profiler (loginprofil) og indstille standardparametre for ofte brugte kommandoer (f.eks. alias ls=ls -AFh eller export LESS=-i). En af ulemperne ved proxying gennem en ikke-interaktiv shell wsl.exe - at profilerne ikke er indlæst, så disse muligheder er ikke tilgængelige som standard (dvs. ls i WSL og wsl ls vil opføre sig anderledes med det ovenfor definerede alias).
PowerShell giver , en standardmekanisme til at definere standardparametre, men kun for cmdlets og avancerede funktioner. Selvfølgelig kan vi lave avancerede funktioner ud af vores skaller, men dette introducerer unødvendige komplikationer (f.eks. korrelerer PowerShell delvise parameternavne (f.eks. -a korrelerer med -ArgumentList), hvilket vil være i konflikt med kommandoer Linux, tage delvise navne som argumenter), og syntaksen til definition af standardværdier vil ikke være den mest passende (definition af standardargumenter kræver navnet på parameteren i switchen, ikke kun kommandonavnet).
Men med en lille ændring af vores skaller, kan vi implementere en model svarende til $PSDefaultParameterValuesog aktiver standardindstillinger for kommandoer Linux!
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 ' ')
}
} Bestået $WslDefaultParameterValues til kommandolinjen sender vi parametre via wsl.exe. Det følgende viser, hvordan du tilføjer instruktioner til din PowerShell-profil for at konfigurere standardindstillinger. Nu kan vi gøre det!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Da parametrene er modelleret efter $PSDefaultParameterValuesdu kan midlertidigt ved at installere nøglen "Disabled" i betydning $true. En yderligere fordel ved en separat hash-tabel er muligheden for at deaktivere $WslDefaultParameterValues adskilt fra $PSDefaultParameterValues.
Argumentafslutning
PowerShell giver dig mulighed for at registrere argumenttrailere ved hjælp af kommandoen Register-ArgumentCompleter. Bash har kraftfuld . WSL giver dig mulighed for at ringe til bash fra PowerShell. Hvis vi kan registrere argumentafslutninger for vores PowerShell-funktionsindpakninger og kalde bash for at generere afslutningerne, får vi fuld argumentafslutning med samme præcision som 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 en smule tæt uden at forstå nogle af bashs interne funktioner, men dybest set, hvad vi gør er dette:
- Registrering af en argumentkompletterer for alle vores funktionsindpakninger ved at sende en liste
$commandsi parameter-CommandNameforRegister-ArgumentCompleter. - Vi knytter hver kommando til shell-funktionen, som bash bruger til autofuldførelse (for at definere autofuldførelsesspecifikationer, bruger bash
$F, forkortelse forcomplete -F <FUNCTION>). - Konvertering af PowerShell-argumenter
$wordToComplete,$commandAstи$cursorPositioni det format, der forventes af bashs autofuldførelsesfunktioner i henhold til specifikationerne bash. - Vi komponerer en kommandolinje til at overføre til
wsl.exe, som sikrer, at miljøet er konfigureret korrekt, kalder den passende autofuldførelsesfunktion og udsender resultaterne linje for linje. - Så ringer vi
wslmed kommandolinjen adskiller vi output med linjeseparatorer og genererer for hverCompletionResults, sortering af dem og undslippende tegn såsom mellemrum og parenteser, der ellers ville blive misfortolket.
Som følge heraf er vores kommandoskaller Linux vil bruge præcis den samme autofuldførelse som i bash! For eksempel:
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>
Hver autofuldførelse leverer værdier, der er specifikke for det forrige argument, læser konfigurationsdata såsom kendte værter fra WSL!
<TAB> vil cykle gennem parametrene. <Ctrl + пробел> vil vise alle tilgængelige muligheder.
Da vi nu har bash autocompletion til at virke, kan du også autofuldføre stier Linux direkte i PowerShell!
less /etc/<TAB>ls /usr/share/<TAB>vim ~/.bash<TAB>
I tilfælde hvor bash-autofuldførelse ikke giver nogen resultater, vender PowerShell tilbage til systemets standardstier. WindowsSåledes kan du i praksis bruge begge stier samtidigt efter eget skøn.
Konklusion
Ved hjælp af PowerShell og WSL kan vi integrere kommandoer Linux в Windows som native apps. Ingen grund til at søge efter Win32-builds eller -værktøjer Linux eller afbryd arbejdsgangen ved at gå til Linux-skal. Bare , konfigurere и Omfattende autofuldførelse for kommandoparametre og filstier Linux и Windows — dette er en funktionalitet, der ikke engang er tilgængelig i native kommandoer i dag Windows.
Den fulde kildekode, der er beskrevet ovenfor, samt yderligere retningslinjer for inkorporering af den i din arbejdsgang, er tilgængelig .
Hvilke hold? Linux Hvad finder du mest nyttigt? Hvilke andre velkendte ting mangler, når man arbejder i WindowsSkriv i kommentarerne eller !
Kilde: www.habr.com
