Linux komandu integrÄ“Å”ana sistēmā Windows, izmantojot PowerShell un WSL

Tipisks Windows izstrādātāju jautājums: ā€œKāpēc joprojām nav <Š’Š”Š¢ŠŠ’Š¬Š¢Š• Š¢Š£Š¢ Š›Š®Š‘Š˜ŠœŠ£Š® ŠšŠžŠœŠŠŠ”Š£ LINUX>?. NeatkarÄ«gi no tā, vai tas ir spēcÄ«gs vilkums less vai pazÄ«stami rÄ«ki grep vai sed, Windows izstrādātāji vēlas vieglu piekļuvi Ŕīm komandām ikdienas darbā.

Windows apakÅ”sistēma operētājsistēmai Linux (WSL) Å”ajā ziņā ir spēris milzÄ«gu soli uz priekÅ”u. Tas ļauj izsaukt Linux komandas no Windows, izmantojot starpniekserveri wsl.exe (Piemēram, wsl ls). Lai gan tas ir bÅ«tisks uzlabojums, Å”ai iespējai ir vairāki trÅ«kumi.

  • VisuresoÅ”s papildinājums wsl nogurdinoÅ”i un nedabiski.
  • Windows ceļi argumentos ne vienmēr darbojas, jo slÄ«psvÄ«tras tiek interpretētas kā atsoļa rakstzÄ«mes, nevis direktoriju atdalÄ«tāji.
  • Windows ceļi argumentos netiek tulkoti uz atbilstoÅ”o pievienoÅ”anas punktu WSL.
  • WSL profilos ar aizstājvārdiem un vides mainÄ«gajiem netiek ievēroti noklusējuma iestatÄ«jumi.
  • Linux ceļa pabeigÅ”ana netiek atbalstÄ«ta.
  • Komandu pabeigÅ”ana netiek atbalstÄ«ta.
  • Argumenta pabeigÅ”ana netiek atbalstÄ«ta.

Rezultātā operētājsistēmā Windows Linux komandas tiek uzskatītas par otrās klases pilsoņiem, un tās ir grūtāk izmantot nekā vietējās komandas. Lai izlīdzinātu viņu tiesības, ir jāatrisina uzskaitītās problēmas.

PowerShell funkciju aptinēji

Izmantojot PowerShell funkciju aptinumus, mēs varam pievienot komandu pabeigÅ”anu un novērst nepiecieÅ”amÄ«bu pēc prefiksiem wsl, pārvērÅ”ot Windows ceļus WSL ceļos. PamatprasÄ«bas čaulām:

  • Katrai Linux komandai ir jābÅ«t vienam funkciju iesaiņojumam ar tādu paÅ”u nosaukumu.
  • Apvalkam ir jāatpazÄ«st Windows ceļi, kas nodoti kā argumenti, un jāpārvērÅ” tie par WSL ceļiem.
  • Apvalkam vajadzētu piezvanÄ«t wsl ar atbilstoÅ”o Linux komandu jebkurai konveijera ievadei un nododot visus funkcijai nodotos komandrindas argumentus.

Tā kā Å”o modeli var lietot jebkurai komandai, mēs varam abstrahēt Å”o iesaiņojumu definÄ«ciju un dinamiski Ä£enerēt tos no importējamo komandu saraksta.

# 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 ' ')
    }
}
"@
}

Saraksts $command definē importÄ“Å”anas komandas. Pēc tam mēs dinamiski Ä£enerējam funkciju iesaiņojumu katram no tiem, izmantojot komandu Invoke-Expression (vispirms noņemot visus aizstājvārdus, kas varētu bÅ«t pretrunā ar funkciju).

Funkcija atkārtojas virs komandrindas argumentiem, nosaka Windows ceļus, izmantojot komandas Split-Path Šø Test-Pathun pēc tam pārvērÅ” Å”os ceļus par WSL ceļiem. Mēs veicam ceļus, izmantojot palÄ«gfunkciju Format-WslArgument, ko mēs definēsim vēlāk. Tajā netiek izmantotas Ä«paÅ”as rakstzÄ«mes, piemēram, atstarpes un iekavas, kas citādi tiktu nepareizi interpretētas.

Visbeidzot, mēs nododam wsl konveijera ievadi un visus komandrindas argumentus.

Izmantojot Ŕos aptinumus, jūs varat izsaukt savas iecienītākās Linux komandas dabiskākā veidā, nepievienojot prefiksu wsl un neuztraucoties par to, kā ceļi tiek pārveidoti:

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

Å eit ir parādÄ«ta pamata komandu kopa, taču jebkurai Linux komandai varat izveidot čaulu, vienkārÅ”i pievienojot to sarakstam. Ja pievienosit Å”o kodu savam profils PowerShell, Ŕīs komandas jums bÅ«s pieejamas katrā PowerShell sesijā, tāpat kā vietējās komandas!

Noklusējuma iestatījumi

Operētājsistēmā Linux pieteikÅ”anās profilos parasti tiek definēti aizstājvārdi un/vai vides mainÄ«gie, iestatot noklusējuma parametrus bieži izmantotajām komandām (piemēram, alias ls=ls -AFh vai export LESS=-i). Viens no trÅ«kumiem starpniekserverÄ«, izmantojot neinteraktÄ«vu apvalku wsl.exe - ka profili nav ielādēti, tāpēc Ŕīs opcijas pēc noklusējuma nav pieejamas (t.i. ls WSL un wsl ls darbosies atŔķirÄ«gi ar iepriekÅ” definēto aizstājvārdu).

PowerShell nodroÅ”ina $PSDefaultParameterValues, standarta mehānisms noklusējuma parametru definÄ“Å”anai, bet tikai cmdlet un papildu funkcijām. Protams, mēs varam izveidot uzlabotas funkcijas no mÅ«su čaulām, taču tas rada nevajadzÄ«gus sarežģījumus (piemēram, PowerShell korelē daļējus parametru nosaukumus (piemēram, -a korelē ar -ArgumentList), kas bÅ«s pretrunā ar Linux komandām, kas izmanto daļējus nosaukumus kā argumentus), un noklusējuma vērtÄ«bu definÄ“Å”anas sintakse nebÅ«s vispiemērotākā (noklusējuma argumentiem ir nepiecieÅ”ams parametra nosaukums atslēgā, nevis tikai komandas nosaukums) .

Tomēr, nedaudz pārveidojot mūsu apvalkus, mēs varam ieviest modeli, kas ir līdzīgs $PSDefaultParameterValuesun iespējojiet noklusējuma opcijas Linux komandām!

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

Ejot garām $WslDefaultParameterValues uz komandrindu, mēs nosūtām parametrus, izmantojot wsl.exe. Tālāk ir parādīts, kā PowerShell profilam pievienot norādījumus, lai konfigurētu noklusējuma iestatījumus. Tagad mēs to varam!

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

Tā kā parametri ir modelēti pēc $PSDefaultParameterValuesjÅ«s varat tos ir viegli atspējot Ä«slaicÄ«gi, uzstādot atslēgu "Disabled" nozÄ«mē $true. AtseviŔķas hash tabulas papildu priekÅ”rocÄ«ba ir iespēja atspējot $WslDefaultParameterValues atseviŔķi no $PSDefaultParameterValues.

Argumenta pabeigŔana

PowerShell ļauj reÄ£istrēt argumentu piekabes, izmantojot komandu Register-ArgumentCompleter. BaÅ”am ir spēcÄ«gs programmējami automātiskās pabeigÅ”anas rÄ«ki. WSL ļauj izsaukt bash no PowerShell. Ja mēs varam reÄ£istrēt argumentu pabeigÅ”anas mÅ«su PowerShell funkciju iesaiņojumos un izsaukt bash, lai Ä£enerētu pabeigÅ”anas, mēs iegÅ«stam pilnÄ«gu argumentu pabeigÅ”anu ar tādu paÅ”u precizitāti kā pats bash!

# 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]
    }
}

Kods ir nedaudz blÄ«vs, neizprotot dažas bash iekŔējās funkcijas, bet pamatā tas, ko mēs darām, ir Ŕāds:

  • Argumentu pabeigtāja reÄ£istrÄ“Å”ana visiem mÅ«su funkciju iesaiņojumiem, nododot sarakstu $commands parametrā -CommandName par Register-ArgumentCompleter.
  • Katra komanda tiek kartēta uz čaulas funkciju, ko bash izmanto automātiskajai pabeigÅ”anai (lai noteiktu automātiskās pabeigÅ”anas specifikācijas, bash izmanto $F, saÄ«sinājums vārdam complete -F <FUNCTION>).
  • PowerShell argumentu konvertÄ“Å”ana $wordToComplete, $commandAst Šø $cursorPosition formātā, ko paredz bash automātiskās pabeigÅ”anas funkcijas saskaņā ar specifikācijām programmējama automātiskā pabeigÅ”ana bash.
  • Mēs izveidojam komandrindu, uz kuru pārsÅ«tÄ«t wsl.exe, kas nodroÅ”ina, ka vide ir iestatÄ«ta pareizi, izsauc atbilstoÅ”o automātiskās pabeigÅ”anas funkciju un izvada rezultātus pa rindiņai.
  • Tad zvanām wsl ar komandrindu mēs atdalām izvadi ar rindu atdalÄ«tājiem un Ä£enerējam katram CompletionResults, kārtojot tos un izlaižot rakstzÄ«mes, piemēram, atstarpes un iekavas, kas citādi tiktu nepareizi interpretētas.

Rezultātā mÅ«su Linux komandu čaulas izmantos tieÅ”i tādu paÅ”u automātisko pabeigÅ”anu kā bash! Piemēram:

  • ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>

Katra automātiskā pabeigÅ”ana nodroÅ”ina vērtÄ«bas, kas raksturÄ«gas iepriekŔējam argumentam, nolasot konfigurācijas datus, piemēram, zināmos saimniekdatorus no WSL!

<TAB> ciklos pa parametriem. <Ctrl + ŠæрŠ¾Š±ŠµŠ»> parādÄ«s visas pieejamās opcijas.

Turklāt, tā kā mums tagad ir bash automātiskā pabeigŔana, varat automātiski pabeigt Linux ceļus tieŔi programmā PowerShell!

  • less /etc/<TAB>
  • ls /usr/share/<TAB>
  • vim ~/.bash<TAB>

GadÄ«jumos, kad bash automātiskā pabeigÅ”ana nesniedz nekādus rezultātus, PowerShell atgriežas pie sistēmas noklusējuma Windows ceļiem. Tādējādi praksē jÅ«s varat vienlaikus izmantot abus ceļus pēc saviem ieskatiem.

Secinājums

Izmantojot PowerShell un WSL, mēs varam integrēt Linux komandas sistēmā Windows kā vietējās lietojumprogrammas. Nav nepiecieÅ”ams meklēt Win32 bÅ«vējumus vai Linux utilÄ«tas vai pārtraukt darbplÅ«smu, atverot Linux čaulu. VienkārÅ”i instalēt WSL, konfigurēt PowerShell profils Šø uzskaitiet komandas, kuras vēlaties importēt! BagātÄ«ga automātiskā pabeigÅ”ana Linux un Windows komandu parametriem un failu ceļiem ir funkcionalitāte, kas mÅ«sdienās nav pieejama pat vietējās Windows komandās.

Ir pieejams pilns iepriekŔ aprakstītais pirmkods, kā arī papildu vadlīnijas tā iekļauŔanai darbplūsmā Ŕeit.

Kuras Linux komandas jums Ŕķiet visnoderÄ«gākās? Kādas citas bieži sastopamas lietas trÅ«kst, strādājot sistēmā Windows? Raksti komentāros vai vietnē GitHub!

Avots: www.habr.com

Pievieno komentāru