Integrazione di squadre Linux в Windows aduprendu PowerShell è WSL

Una dumanda tipica da i sviluppatori Windows: "Perchè ùn ci hè ancu nunda quì? <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Ch'ella sia un swipe putente less o arnesi familiari grep o sed, sviluppatori sottu Windows volenu un accessu faciule à sti cumandamenti in u so travagliu cutidianu.

Subsistema Windows di Linux (WSL) hà fattu un grande passu avanti in questu sensu. Vi permette di chjamà cumandamenti Linux из Windows, trasmettenduli per mezu di wsl.exe (p.e. wsl ls). Ancu s'ellu hè una mellura significativa, sta opzione soffre di una quantità di svantaghji.

  • Addizione omnipresente wsl tediosa è antinaturale.
  • I modi Windows in argumenti ùn funzionanu micca sempre perchè e barre inverse sò interpretate cum'è caratteri di escape, micca separatori di cartulare.
  • I modi Windows L'argumenti in ùn sò micca tradutti in u puntu di muntatura currispundente in WSL.
  • I paràmetri predeterminati ùn sò micca rispettati in i profili WSL cù alias è variabili di l'ambiente.
  • U cumpletamentu di u percorsu ùn hè micca supportatu Linux.
  • Cumpiimentu di cumanda ùn hè micca supportatu.
  • U cumpletu di l'argumentu ùn hè micca supportatu.

In cunsequenza di a squadra Linux sò percepiti sottu Windows Sò cum'è citadini di seconda classe - è più difficiuli d'utilizà chè e so squadre di casa. Per uguaglià i so diritti, sti prublemi devenu esse affrontati.

Wrapper di funzione PowerShell

Cù i wrappers di funzione PowerShell, pudemu aghjustà u cumpletu di cumandamenti è eliminà a necessità di prefissi wsl, percorsi di trasmissione Windows Nant'à u percorsu WSL. Requisiti basi per e cunchiglie:

  • Per ogni squadra Linux Ci deve esse un wrapper di funzione cù u listessu nome.
  • A cunchiglia deve ricunnosce i percorsi Windows, passati cum'è argumenti, è cunvertisceli in percorsi WSL.
  • A cunchiglia deve chjamà wsl cù u cumandamentu adattatu Linux à qualsiasi input di pipeline è passendu qualsiasi argumentu di linea di cumanda passatu à a funzione.

Siccomu stu mudellu pò esse appiicatu à qualsiasi cumandamentu, pudemu astrattu a definizione di sti wrappers è generà dinamicamente da una lista di cumandamenti per impurtà.

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

a lista $command definisce i cumandamenti d'importazione. Allora generà dinamicamente un wrapper di funzione per ognunu di elli usendu u cumandamentu Invoke-Expression (per prima sguassate qualsiasi alias chì anu cunflittu cù a funzione).

A funzione itera nantu à l'argumenti di a linea di cummanda è determina i percorsi Windows aduprendu cumandamenti Split-Path и Test-Pathè poi cunvertisce sti camini in camini WSL. Eseguimu i camini attraversu una funzione d'aiutu Format-WslArgument, chì avemu da definisce dopu. Scappa caratteri spiciali cum'è spazii è parentesi chì altrimenti seranu malinterpretati.

Infine, trasmettemu wsl input di pipeline è qualsiasi argumenti di linea di cummanda.

Cù l'aiutu di tali involucri pudete chjamà i vostri cumandamenti preferiti Linux in un modu più naturale, senza aghjunghje un prefissu wsl è senza preoccupari di cumu si cunvertisce i camini:

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

Un inseme basicu di cumandamenti hè mostratu quì, ma pudete creà un wrapper per qualsiasi cumandamentu. Linux, solu aghjunghjendulu à a lista. Sè aghjunghjite stu codice à u vostru prufilu PowerShell, sti cumandamenti seranu dispunibili per voi in ogni sessione di PowerShell, cum'è i cumandamenti nativi!

Impostazioni predefinite

В Linux Hè cumunu di definisce alias è/o variabili d'ambiente in i profili (profilu di login), definendu parametri predefiniti per i cumandamenti usati frequentemente (per esempiu, alias ls=ls -AFh o export LESS=-i). Unu di i svantaghji di proxy attraversu una cunchiglia non interattiva wsl.exe - chì i profili ùn sò micca caricati, cusì queste opzioni ùn sò micca dispunibuli per difettu (i.e. ls in WSL è wsl ls si cumportarà di manera diversa cù l'alias definitu sopra).

PowerShell furnisce $PSDefaultParameterValues, un mecanismu standard per definisce i paràmetri predeterminati, ma solu per i cmdlets è e funzioni avanzate. Di sicuru, pudemu fà funzioni avanzate fora di i nostri cunchiglia, ma questu introduce cumplicazioni innecessarii (per esempiu, PowerShell correlate i nomi di parametri parziali (per esempiu, -a correlate cù -ArgumentList), chì entrerà in cunflittu cù i cumandamenti Linux, pigliendu nomi parziali cum'è argumenti), è a sintassi per definisce i valori predefiniti ùn serà micca a più adatta (definisce argumenti predefiniti richiede u nome di u parametru in u switch, micca solu u nome di u cumandamentu).

Tuttavia, cù una ligera mudificazione à i nostri cunchiglia, pudemu implementà un mudellu simili à $PSDefaultParameterValues, è attivate l'opzioni predefinite per i cumandamenti 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 ' ')
    }
}

Passendu $WslDefaultParameterValues à a linea di cummanda, mandemu paràmetri via wsl.exe. U seguitu mostra cumu aghjunghje struzzioni à u vostru prufilu PowerShell per cunfigurà i paràmetri predeterminati. Avà pudemu fà!

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

Siccomu i paràmetri sò modellati dopu $PSDefaultParameterValues, Poi hè faciule di disattivà elli temporaneamente installendu a chjave "Disabled" in significatu $true. Un benefiziu supplementu di una tavola hash separata hè a capacità di disattivà $WslDefaultParameterValues separatamente da $PSDefaultParameterValues.

Cumplementu di l'argumentu

PowerShell permette di registrà trailers d'argumenti cù u cumandamentu Register-ArgumentCompleter. Bash hà putente Strumenti di cumpletamentu automaticu programabili. WSL vi permette di chjamà bash da PowerShell. Se pudemu registrà u cumpletu di l'argumentu per i nostri wrappers di funzioni PowerShell è chjamà bash per generà e cumpletture, avemu un cumpletu di l'argumentu cumpletu cù a stessa precisione cum'è bash stessu!

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

U codice hè un pocu densu senza capisce alcune di e funzioni interne di bash, ma in fondu ciò chì facemu hè questu:

  • Registrazione di un argumentu cumpletu per tutti i nostri wrappers di funzione passendu una lista $commands in paràmetru -CommandName di Register-ArgumentCompleter.
  • Mapemu ogni cumandamentu à a funzione di shell chì bash usa per l'autocompletion (per definisce e specificazioni di autocompletion, bash usa $F, abbreviazione per complete -F <FUNCTION>).
  • Cunvertisce Argumenti PowerShell $wordToComplete, $commandAst и $cursorPosition in u formatu previstu da e funzioni di autocompletion di bash secondu e specificazioni cumpletamentu automaticu programmabile bacca.
  • Cumponemu una linea di cummanda per trasfiriri wsl.exe, chì assicura chì l'ambienti hè stallatu currettamente, chjama a funzione di cumpleta automatica adattata, è pruduce i risultati in linea per linea.
  • Allora chjamemu wsl cù a linea di cummanda, separemu l'output per separatori di linea è generà per ognunu CompletionResults, sortenduli è scappendu caratteri cum'è spazii è parentesi chì altrimenti seranu malinterpretati.

In cunsequenza, i nostri gusci di cummandu Linux aduprerà esattamente u listessu cumpletamentu automaticu cum'è in bash! Per esempiu:

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

Ogni autocumplementu furnisce valori specifichi à l'argumentu precedente, leghjendu dati di cunfigurazione cum'è ospiti cunnisciuti da WSL!

<TAB> ciclu i paràmetri. <Ctrl + пробел> mostrarà tutte l'opzioni dispunibili.

Inoltre, postu chì avà avemu u cumpletamentu automaticu bash chì funziona, pudete cumpletà automaticamente i percorsi Linux direttamente in PowerShell!

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

In i casi induve l'autocompletazione bash ùn produce micca risultati, PowerShell torna à i percorsi predefiniti di u sistema. WindowsCusì, in pratica, pudete aduprà i dui percorsi simultaneamente à a vostra discrezione.

cunchiusioni

Cù PowerShell è WSL pudemu integrà cumandamenti Linux в Windows cum'è l'applicazioni native. Ùn ci hè bisognu di circà build o utilità Win32 Linux o interrompe u flussu di travagliu andendu à Linux-shell. Ghjustu stallà WSL, cunfigurà Profilu PowerShell и lista i cumandamenti chì vulete impurtà! Autocompletamentu riccu per i parametri di cumanda è i percorsi di file Linux и Windows — sta funziunalità ùn hè micca dispunibile mancu in i cumandamenti nativi oghje Windows.

U codice fonte sanu descrittu sopra, è ancu linee supplementari per incorpore in u vostru flussu di travagliu, hè dispunibule ccà.

Chì squadre ? Linux Chì truvate u più utile ? Chì altre cose cunnisciute mancanu quandu si travaglia in WindowsScrivite in i cumenti o nantu à GitHub!

Source: www.habr.com

Cumprate un hosting affidabile per i siti cù prutezzione DDoS, servitori VPS VDS 🔥 Cumprate un hosting di siti web affidabile cù prutezzione DDoS, servitori VPS VDS | ProHoster