Tipično pitanje programera na Windows-u: "Zašto ovde još uvek nema <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less ili poznati alati grep ili sed, programeri na Windows-u žele lak pristup ovim komandama u svakodnevnom radu.
napravio je veliki korak napred u tom smislu. Omogućava pozivanje Linux komandi iz Windows-a, proksirajući ih kroz wsl.exe (na primer, wsl ls). Iako je ovo značajno poboljšanje, ovaj pristup pati od niza nedostataka.
- Sveprisutna dodavanja
wslumara i deluje neprirodno. - Windows putanje u argumentima ne rade uvek, jer se nazadni kosa crta tumači kao escape simbol, a ne kao separator direktorijuma.
- Windows putanje u argumentima se ne prevode u odgovarajuću tačku montiranja u WSL-u.
- Podrazumevani parametri u WSL profilima sa aliasima i varijablama okruženja se ne uzimaju u obzir.
- Završavanje Linux putanja nije podržano.
- Završavanje komandi nije podržano.
- Završavanje argumenata nije podržano.
Kao rezultat, Linux komande se pod Windows-om smatraju građanima drugog reda — i teže ih je koristiti nego domaće komande. Da bismo ih izjednačili, potrebno je rešiti nabrojane probleme.
PowerShell funkcijske omotnice
Pomoću PowerShell funkcijskih omotnica možemo dodati automatsko završavanje komandi i eliminisati potrebu za prefiksima wsl, prevodeći Windows putanje u WSL putanje. Glavni zahtevi za omotnice su:
- Za svaku Linux komandu treba da postoji jedna funkcijska omotnica sa istim imenom.
- Omotnica mora prepoznati Windows putanje prosleđene kao argumente i pretvoriti ih u WSL putanje.
- Omotnica mora pozvati
wslodgovarajuću Linux komandu na bilo koji ulaz u cevovod i preneti sve argumente komandne linije prosleđene funkciji.
Budući da se ovaj obrazac može primeniti na svaku komandu, možemo apstrahovati definiciju ovih omotnica i dinamički ih generisati iz liste komandi za uvoz.
# 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 definiše komande za uvoz. Zatim dinamički generišemo omotnicu funkcije za svaku od njih, koristeći komandu Invoke-Expression (prvo uklanjajući sve alias-e koji bi mogli doći u sukob sa funkcijom).
Funkcija prolazi kroz argumente komandne linije, identifikuje Windows putanje pomoću komandi Split-Path in Test-Path, in nato pretvori te poti v poti WSL. Poti predelamo skozi pomožno funkcijo Format-WslArgument, ki jo bomo definirali pozneje. Ubeži posebne znake, kot so presledki in oklepaji, ki bi bili sicer napačno interpretirani.
Na koncu predamo wsl vhod kontejnerju in vse argumente ukazne vrstice.
S takšnimi ovoji lahko kličemo svoje najljubše ukaze Linux na bolj naraven način, brez predpona wsl in brez skrbi, kako se poti pretvarjajo:
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Tukaj je osnovni niz ukazov, vendar lahko ustvarite ovojnico za kateri koli ukaz Linux, tako da ga preprosto dodate na seznam. Če dodate to kodo v svoj PowerShell, bodo ti ukazi na voljo v vsakem seji PowerShell, tako kot nativni ukazi!
Privzete nastavitve
V Linuxu je običajno določiti alias in/ali spremenljivke okolja v profilih (login profile), da se nastavijo privzete nastavitve za pogosto uporabljene ukaze (na primer, alias ls=ls -AFh ili export LESS=-i). Ena od pomanjkljivosti posredovanja preko neinteraktivne lupine wsl.exe je, da se profili ne naložijo, zato ti privzeti parametri niso na voljo (tj. ls v WSL in wsl ls se bodo obnašali različno z zgoraj določenim aliasom.
PowerShell ponuja , standardni mehanizem za določanje privzetih parametrov, vendar le za cmdlete in razširjene funkcije. Seveda lahko naše lupine pretvorimo v razširjene funkcije, vendar to prinaša dodatne zaplete (torej PowerShell obravnava delna imena parametrov (na primer, -a se povezuje z -ArgumentList), ki bodo v konflikti z ukazi Linux, ki sprejemajo delna imena kot argumente), in sintaksa za določitev privzetih vrednosti ne bo najbolj primerna (za določitev argumentov po privzetku je potrebno ime parametra v ključu in ne le ime ukaza).
Vendar pa z majhno spremembo naših lupin lahko uvedemo model podoben $PSDefaultParameterValues, in vključimo privzete parametre za ukaze 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 ' ')
}
} S predajo $WslDefaultParameterValues v ukazatelja, pošiljamo parametre preko wsl.exe. Ispod je prikazano kako dodati uputstva u PowerShell profil za podešavanje podrazumevanih parametara. Sada to možemo da uradimo!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Budući da su parametri modelirani prema $PSDefaultParameterValues, možete privremeno, postavljanjem ključa "Disabled" na vrednost $true. Dodatna prednost odvojene hesh tabele je mogućnost da isključite $WslDefaultParameterValues odvojeno od $PSDefaultParameterValues.
Automatsko završavanje argumenata
PowerShell omogućava registrovanje završioca argumenata koristeći komandu Register-ArgumentCompleter. U Bash-u postoje moćni . WSL omogućava pozivanje bash-a iz PowerShell-a. Ako možemo da registrujemo završioca argumenata za naše funkcije u PowerShell-u i pozovemo bash za generisanje završetaka, dobićemo potpuno automatsko završavanje argumenata sa istom preciznošću kao u samom bash-u!
# 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 je malo gust bez razumevanja nekih internih funkcija bash-a, ali uglavnom radimo sledeće:
- Registrovamo završioca argumenata za sve naše omotane funkcije, prosleđujući spisak
$commandsv parameter-CommandNamezaRegister-ArgumentCompleter. - Mapiramo svaku komandu na funkciju omotanja koju bash koristi za automatsko završavanje (za određivanje specifikacija automatskog završavanja u bash-u koristi se
$F, skraćenica odcomplete -F). - Transformišemo argumente PowerShell-a
$wordToComplete,$commandAstin$cursorPositionu format koji očekuju funkcije automatskog završavanja bash-a prema specifikacijama bash-a. - Sastavljamo komandnu liniju za prosledjivanje u
wsl.exe, koja obezbeđuje pravilnu konfiguraciju okruženja, poziva odgovarajuću funkciju automatskog završavanja i vraća rezultate razdvojene po redovima. - Zatim pozivamo
wslsa komandnom linijom, razdvajamo izlaz separatorima linija i generišemo za svakuCompletionResults, sortirajući ih i escapirajući karaktere kao što su razmaci i zagrade, koji bi inače bili pogrešno interpretirani.
Na kraju, naše Linux komandne linije će koristiti potpuno isto automatsko završavanje kao u bash-u! Na primer:
ssh -c -J -m -O -o -Q -w -b
Vsaka avtoizpolnitev ponuja vrednosti, specifične za prejšnji argument, tako da prebere nastavitve, kot so znani gostitelji, iz WSL!
<TAB> bo ciklično prebiral parametre. <Ctrl + пробел> bo prikazal vse razpoložljive možnosti.
Poleg tega, ker zdaj deluje avtoizpolnitev bash, lahko avtoizpolnjujete poti Linux neposredno v PowerShell!
less /etc/ls /usr/share/vim ~/bash
V primerih, ko avtoizpolnitev bash ne vrne rezultatov, se PowerShell vrne na privzeti sistem s potmi Windows. Tako lahko v praksi hkrati uporabljate tako ene kot druge poti po svoji izbiri.
Zaključek
S pomočjo PowerShell in WSL lahko integriramo ukaze Linux v Windows kot domače aplikacije. Ni treba iskati različnih različic Win32 ali Linux orodij ali prekinjati delovnega procesa, ko preklapljate na Linux shell. Preprosto , nastavite in ! Bogato avtoizpolnjevanje za parametre ukazov in poti datotek Linux in Windows je funkcionalnost, ki je danes celo manjkajoča v domačih ukazih Windows.
Celotna izvirna koda, opisana zgoraj, ter dodatni nasveti za vključitev v delovni proces so na voljo .
Katere ukaze Linux menite, da so najbolj uporabni? Kaj še pogrešate pri delu v Windows? Pišite v komentarje ali !
Izvor: habr.com
