Nggabungake perintah Linux menyang Windows nggunakake PowerShell lan WSL

Pitakonan khas saka pangembang Windows: "Napa isih ora ana <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Apa iku gesek kuat less utawa piranti akrab grep utawa sed, pangembang Windows pengin akses gampang menyang printah iki ing karya saben dina.

Windows Subsystem for Linux (WSL) wis nggawe langkah gedhe ing babagan iki. Iki ngidini sampeyan nelpon perintah Linux saka Windows kanthi proxy wsl.exe (ut. wsl ls). Senajan iki dandan pinunjul, pilihan iki nandhang sangsara marga saka sawetara cacat.

  • Tambahan ing ngendi-endi wsl bosen lan ora wajar.
  • Jalur Windows ing argumen ora mesthi bisa digunakake amarga garis miring mundur diinterpretasikake minangka karakter uwal tinimbang pemisah direktori.
  • Jalur Windows ing argumen ora diterjemahake menyang titik gunung sing cocog ing WSL.
  • Setelan gawan ora dihormati ing profil WSL kanthi alias lan variabel lingkungan.
  • Rampung path Linux ora didhukung.
  • Completion printah ora didhukung.
  • Rampung argumentasi ora didhukung.

Akibaté, perintah Linux dianggep kaya warga kelas loro ing Windows-lan luwih angel digunakake tinimbang prentah asli. Kanggo equalize hak-hak sing, iku perlu kanggo ngatasi masalah kadhaptar.

Pambungkus fungsi PowerShell

Kanthi pembungkus fungsi PowerShell, kita bisa nambah printah rampung lan ngilangi prefiks wsl, nerjemahake path Windows menyang path WSL. Syarat dhasar kanggo cangkang:

  • Kanggo saben printah Linux kudu ana siji pambungkus fungsi kanthi jeneng sing padha.
  • Cangkang kudu ngenali path Windows sing dilewati minangka argumen lan ngowahi dadi jalur WSL.
  • Cangkang kudu nelpon wsl karo printah Linux cocok kanggo sembarang input pipeline lan maringaken sembarang bantahan baris printah liwati menyang fungsi.

Wiwit pola iki bisa diterapake ing sembarang printah, kita bisa abstrak definisi wrappers iki lan mbosenke generate saka dhaptar printah kanggo ngimpor.

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

Dhaftar $command nemtokake printah impor. Kita banjur mbosenke generate pambungkus fungsi kanggo saben wong nggunakake printah Invoke-Expression (kanthi mbusak jeneng alias sing bakal konflik karo fungsi kasebut).

Fungsi kasebut ngulang argumen baris perintah, nemtokake dalan Windows nggunakake printah Split-Path и Test-Pathlan banjur ngowahi path iki kanggo path WSL. Kita mbukak dalan liwat fungsi helper Format-WslArgument, kang bakal kita nemtokake mengko. Iku uwal saka karakter khusus kayata spasi lan kurung sing digunakake bakal misinterpreted.

Pungkasan, kita ngirim wsl input pipeline lan argumen baris perintah.

Kanthi pambungkus iki, sampeyan bisa nelpon perintah Linux favorit kanthi cara sing luwih alami tanpa nambahake prefiks wsl lan tanpa kuwatir babagan carane dalan diowahi:

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

Setel printah dhasar ditampilake ing kene, nanging sampeyan bisa nggawe cangkang kanggo perintah Linux kanthi nambahake menyang dhaptar. Yen sampeyan nambah kode iki menyang profil PowerShell, printah iki bakal kasedhiya kanggo sampeyan ing saben sesi PowerShell, kaya prentah asli!

Setelan Default

Ing Linux, umume nemtokake alias lan/utawa variabel lingkungan ing profil login, nyetel paramèter gawan kanggo printah sing kerep digunakake (contone, alias ls=ls -AFh utawa export LESS=-i). Salah sijine kekurangan proxying liwat cangkang non-interaktif wsl.exe - yen profil ora dimuat, dadi pilihan iki ora kasedhiya minangka standar (i.e. ls ing WSL lan wsl ls bakal tumindak beda karo alias sing ditetepake ing ndhuwur).

PowerShell nyedhiyakake $PSDefaultParameterValues, mekanisme standar kanggo nemtokake paramèter gawan, nanging mung kanggo cmdlet lan fungsi majeng. Mesthi wae, kita bisa nggawe fungsi lanjut saka cangkang kita, nanging iki nyebabake komplikasi sing ora perlu (contone, PowerShell nggandhengake jeneng parameter parsial (contone, -a hubungane karo -ArgumentList), sing bakal konflik karo perintah Linux sing njupuk jeneng parsial minangka argumen), lan sintaks kanggo nemtokake nilai standar ora bakal dadi sing paling cocok (argumen standar mbutuhake jeneng parameter ing tombol, ora mung jeneng printah) .

Nanging, kanthi modifikasi tipis kanggo cangkang kita, kita bisa ngetrapake model sing padha $PSDefaultParameterValues, lan ngaktifake pilihan standar kanggo printah 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 ' ')
    }
}

liwat $WslDefaultParameterValues kanggo baris printah, kita ngirim paramèter liwat wsl.exe. Ing ngisor iki nuduhake carane nambah instruksi menyang profil PowerShell kanggo ngatur setelan gawan. Saiki kita bisa nindakake!

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

Wiwit paramèter dimodelake sawise $PSDefaultParameterValues, Sampeyan bisa iku gampang kanggo mateni wong sementara kanthi nginstal tombol "Disabled" menyang makna $true. Keuntungan tambahan saka tabel hash sing kapisah yaiku kemampuan kanggo mateni $WslDefaultParameterValues kapisah saka $PSDefaultParameterValues.

Argumentasi rampung

PowerShell ngidini sampeyan ndhaptar trailer argumentasi nggunakake printah kasebut Register-ArgumentCompleter. Bash wis kuat alat rampung otomatis sing bisa diprogram. WSL ngidini sampeyan nelpon bash saka PowerShell. Yen kita bisa ndhaptar completion argumen kanggo pambungkus fungsi PowerShell lan nelpon bash kanggo ngasilake completion, kita entuk lengkap argumentasi kanthi presisi sing padha karo bash dhewe!

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

Kode kasebut rada padhet tanpa ngerti sawetara fungsi internal bash, nanging dhasar sing ditindakake yaiku:

  • Ndhaptar lengkap argumentasi kanggo kabeh bungkus fungsi kita kanthi menehi dhaptar $commands ing parameter -CommandName kanggo Register-ArgumentCompleter.
  • We map saben printah kanggo fungsi shell sing bash digunakake kanggo autocompletion (kanggo netepake spesifikasi autocompletion, bash nggunakake $F, singkatan kanggo complete -F <FUNCTION>).
  • Ngonversi Argumen PowerShell $wordToComplete, $commandAst и $cursorPosition menyang format sing dikarepake dening fungsi autocompletion bash miturut spesifikasi programmable otomatis completion bash.
  • We nyipta baris printah kanggo nransfer menyang wsl.exe, kang mesthekake yen lingkungan wis nyiyapake bener, nelpon fungsi otomatis completion cocok, lan output asil ing baris-by-line fashion.
  • Banjur kita nelpon wsl karo baris printah, kita misahake output dening separators baris lan generate kanggo saben CompletionResults, ngurutake lan uwal karakter kayata spasi lan kurung sing bisa disalahake.

Akibaté, cangkang perintah Linux kita bakal nggunakake autocompletion sing padha karo bash! Tuladhane:

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

Saben autocompletion nyedhiyakake nilai khusus kanggo argumen sadurunge, maca data konfigurasi kayata host sing dikenal saka WSL!

<TAB> bakal siklus liwat paramèter. <Ctrl + пробел> bakal nuduhake kabeh pilihan sing kasedhiya.

Kajaba iku, amarga saiki kita duwe autocompletion bash, sampeyan bisa ngrampungake jalur Linux kanthi otomatis ing PowerShell!

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

Ing kasus nalika autocompletion bash ora ngasilake asil, PowerShell bali menyang jalur Windows standar sistem. Mangkono, ing laku, sampeyan bisa nggunakake loro dalan bebarengan ing discretion sampeyan.

kesimpulan

Nggunakake PowerShell lan WSL, kita bisa nggabungake perintah Linux menyang Windows minangka aplikasi asli. Ora perlu nggolek Win32 mbangun utawa keperluan Linux utawa ngganggu alur kerja sampeyan kanthi mbukak cangkang Linux. mung nginstal WSL, ngatur Profil PowerShell и dhaptar printah sing pengin diimpor! Autocompletion sugih kanggo paramèter printah Linux lan Windows lan path file minangka fungsi sing ora kasedhiya ing printah Windows asli saiki.

Kode sumber lengkap sing diterangake ing ndhuwur, uga pedoman tambahan kanggo nggabungake menyang alur kerja sampeyan, kasedhiya kene.

Perintah Linux apa sing paling migunani? Apa perkara umum liyane sing ilang nalika nggarap Windows? Tulis ing komentar utawa ing GitHub!

Source: www.habr.com

Add a comment