Integriramo Linux naredbe u Windows putem PowerShell-a i WSL-a

Tipičan je pitanje programera na Windowsu: „Zašto ovdje još nema <UBACITE OVDJE OMILJENU LINUX KOMANDU>?». Будь то мощное пролистывание less ili poznatih alata grep ili sed, programeri na Windowsu žele imati lak pristup tim komandama u svom svakodnevnom radu.

Windows podsystem za Linux (WSL) napravio je ogroman napredak u tom pogledu. Omogućuje pozivanje Linux komandi iz Windowsa, proksirajući ih kroz wsl.exe (na primjer, wsl ls). Iako je to značajno poboljšanje, ta opcija pati od niza nedostataka.

  • Sveprisutno dodavanje wsl umara i nije prirodno.
  • Windows putanje u argumentima ne rade uvijek, jer se obrnutih kosa crta interpretira kao escape znakovi, a ne kao razdjeljnici direktorija.
  • Windows putanje u argumentima se ne prevode u odgovarajuću točku montiranja u WSL-u.
  • Zadani parametri u WSL profilima s aliasima i varijablama okoliša se ne uzimaju u obzir.
  • Nema podrške za završetak Linux putanja.
  • Nema podrške za završetak komandi.
  • Nema podrške za završetak argumenata.

Kao rezultat toga, Linux komande se u Windowsu doživljavaju kao građani druge klase — i teže ih je koristiti od izvornih komandi. Da bi ih izjednčili, potrebno je riješiti navedene probleme.

Funkcijske ljuske PowerShell

Pomoću funkcijskih ljuski PowerShell možemo dodati automatsko dovršavanje komandi i eliminirati potrebu za prefiksima wsl, prevođenjem Windows putanja u WSL putanje. Osnovni zahtjevi za ljuske su:

  • Za svaku Linux komandu mora postojati jedna funkcijska ljuska s istim imenom.
  • Ljuska mora prepoznati Windows putanje proslijeđene kao argumente i pretvoriti ih u WSL putanje.
  • Ljuska mora zvati wsl s odgovarajućom Linux komandom na bilo kojem ulazu cjevovoda i proslijediti sve argumente linije naredbe proslijeđene funkciji.

Budući da se ovaj obrazac može primijeniti na bilo koju komandu, možemo apstrahirati definiciju tih ljuski i dinamički ih generirati iz popisa 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 ' ')
    }
}
"@
}

Popis $command definira komande za uvoz. Zatim dinamički generiramo omotač funkcije za svaku od njih, koristeći komandu Invoke-Expression (prvo uklanjajući sve alias-e koji bi mogli biti u sukobu s funkcijom).

Funkcija prolazi kroz argumente linije naredbe, prepoznaje Windows putanje pomoću komandi Split-Path i Test-Path, a zatim pretvara te putanje u WSL putanje. Prolazimo putanje kroz pomoćnu funkciju Format-WslArgument, koju ćemo odrediti kasnije. Ona escaping-ira posebne znakove, kao što su razmaci i zagrade, koji bi inače bili pogrešno protumačeni.

Na kraju, predajemo wsl ulazne podatke cjevovodu i sve argumente naredbenog retka.

Pomoću takvih omota možemo pozivati omiljene Linux naredbe na prirodniji način, bez dodavanja prefiksa wsl i bez brige o tome kako se putanje pretvaraju:

  • man bash
  • less -i $profile.CurrentUserAllHosts
  • ls -Al C:Windows | less
  • grep -Ein error *.log
  • tail -f *.log

Ovdje je prikazan osnovni skup naredbi, ali možete stvoriti omotač za svaku Linux naredbu, jednostavno dodajući je na popis. Ako ovaj kod dodate u svoj profil PowerShell, te naredbe će vam biti dostupne u svakoj PowerShell sesiji, baš kao i izvorne naredbe!

Zadane postavke

U Linuxu je uobičajeno definirati alias-e i/ili varijable okruženja u profilima (login profile), postavljajući zadane parametre za često korištene naredbe (na primjer, alias ls=ls -AFh ili export LESS=-i). Jedna od mana proksiranja putem neinteraktivne ljuske wsl.exe jest da se profili ne učitavaju, pa ti zadani parametri nisu dostupni (tj. ls u WSL-u i wsl ls ponašat će se drugačije s alias-om definiranim iznad).

PowerShell pruža $PSDefaultParameterValues, standardni mehanizam za određivanje default parametara, ali samo za cmdlete i proširene funkcije. Naravno, možemo od naših ljuski napraviti proširene funkcije, ali to unosi dodatne komplikacije (tako PowerShell povezuje djelomična imena parametara (na primjer, -a povezuje s -ArgumentList), što će biti u sukobu s Linux naredbama koje prihvaćaju djelomična imena kao argumente), a sintaksa za definiranje defaultnih vrijednosti neće biti najprikladnija (za definiranje argumenata po defaultu potreban je naziv parametra u ključu, a ne samo naziv naredbe).

Međutim, s malom izmjenom naših ljuski možemo implementirati model sličan $PSDefaultParameterValues, te uključiti defaultne parametre za Linux naredbe!

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 ' ')
    }
}

Proslijedivši $WslDefaultParameterValues u komandnu liniju šaljemo parametre putem wsl.exe. Ispod je prikazano kako dodati upute u PowerShell profil za podešavanje unaprijed postavljenih parametara. Sada to možemo učiniti!

$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first"

Budući da se parametri modeliraju prema $PSDefaultParameterValues, možete lako ih isključiti na vrijeme, postavljajući ključ "Disabled" na $true. Dodatna prednost odvojene haš-tablice je mogućnost isključenja $WslDefaultParameterValues odvojeno od $PSDefaultParameterValues.

Automatskog dovršavanja argumenata

PowerShell omogućuje registriranje dovršavača argumenata pomoću naredbe Register-ArgumentCompleter. U Bashu postoje moćni programabilni alati za automatsko dovršavanje. WSL omogućuje pozivanje basha iz PowerShella. Ako možemo registrirati dovršavače argumenata za naše funkcije PowerShell i pozvati bash za generiranje dovršetaka, dobit ćemo potpuno automatsko dovršavanje argumenata s istom preciznošću kao u samom 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 je malo gust bez razumijevanja nekih internih funkcija basha, no uglavnom radimo sljedeće:

  • Registriramo dovršavač argumenata za sve naše omotače funkcija, predajući popis $commands u parametar -CommandName za Register-ArgumentCompleter.
  • Uspoređujemo svaku naredbu s funkcijom ljuske koju bash koristi za automatsko dovršavanje (za definiranje specifikacija automatskog dovršavanja u bashu koristi se $F, skraćenica od complete -F).
  • Pretvaramo argumente PowerShella $wordToComplete, $commandAst i $cursorPosition u format koji se očekuje od funkcija automatskog dovršavanja basha prema specifikacijama programabilnog automatskog dovršavanja basha.
  • Sastavljamo naredbenu liniju koja se prenosi u wsl.exe, koja osigurava ispravnu postavku okruženja, poziva odgovarajuću funkciju automatskog dovršavanja i ispisuje rezultate s razdvajanjem po redovima.
  • Zatim pozivamo wsl s naredbenom linijom, dijelimo ishod razdjelnikom redova i generiramo za svaki CompletionResults, sortirajući ih i escaping znakove kao što su razmaci i zagrade, koji bi inače bili pogrešno protumačeni.

Na kraju, naše Linux naredbene ljuske će koristiti potpuno isto automatsko dovršavanje kao u bashu! Na primjer:

  • ssh -c -J -m -O -o -Q -w -b

Svaka automatska dovršavanje isporučuje vrijednosti specifične za prethodni argument, čitajući podatke konfiguracije, poput poznatih hostova, iz WSL-a!

<TAB> bit će ciklično prolazi kroz parametre. <Ctrl + пробел> prikazat će sve dostupne opcije.

Osim toga, budući da sada imamo funkcionalno automatsko dovršavanje bash-a, možete automatski dovršavati Linux putanje izravno u PowerShellu!

  • less /etc/
  • ls /usr/share/
  • vim ~/\.bash

U slučajevima kada automatsko dovršavanje bash-a ne daje nikakve rezultate, PowerShell se vraća na sistem prema zadanim postavkama s Windows putanjama. Tako možete istovremeno koristiti i jedne i druge putanje prema vlastitoj želji.

Zaključak

Uz PowerShell i WSL možemo integrirati Linux naredbe u Windows kao nativne aplikacije. Nema potrebe tražiti Win32 izgradnje ili Linux alate ili prekidati radni tijek prelaskom u Linux shell. Samo instalirajte WSL, podesite PowerShell profil i nabrojite naredbe koje želite uvesti! Obogaćeno automatsko dovršavanje za parametre naredbi i putanje datoteka Linuxa i Windowsa je funkcionalnost koja danas čak ni u nativnim Windows naredbama ne postoji.

Cjeli izvorni kod, opisan gore, kao i dodatni savjeti za njegovo uključivanje u radni tijek su dostupni ovdje.

Koje Linux naredbe smatrate najkorisnijima? Koje još uobičajene stvari nedostaju pri radu u Windowsu? Pišite u komentarima ili na GitHubu!

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster