Mengintegrasikan arahan Linux ke dalam Windows menggunakan PowerShell dan WSL

Soalan biasa daripada pembangun Windows: “Mengapa masih tiada <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Sama ada leretan yang kuat less atau alat biasa grep atau sed, pembangun Windows mahukan akses mudah kepada arahan ini dalam kerja harian mereka.

Subsistem Windows untuk Linux (WSL) telah membuat satu langkah besar ke hadapan dalam hal ini. Ia membolehkan anda memanggil arahan Linux daripada Windows dengan memproksinya wsl.exe (cth. wsl ls). Walaupun ini adalah peningkatan yang ketara, pilihan ini mengalami beberapa kelemahan.

  • Tambahan di mana-mana wsl membosankan dan tidak wajar.
  • Laluan Windows dalam argumen tidak selalu berfungsi kerana garis miring ke belakang ditafsirkan sebagai aksara melarikan diri dan bukannya pemisah direktori.
  • Laluan Windows dalam argumen tidak diterjemahkan ke titik pelekap yang sepadan dalam WSL.
  • Tetapan lalai tidak dihormati dalam profil WSL dengan alias dan pembolehubah persekitaran.
  • Penyiapan laluan Linux tidak disokong.
  • Penyiapan arahan tidak disokong.
  • Penyelesaian hujah tidak disokong.

Akibatnya, arahan Linux dilayan seperti warga kelas kedua di bawah Windows—dan lebih sukar digunakan daripada arahan asli. Untuk menyamakan hak mereka, adalah perlu untuk menyelesaikan masalah yang disenaraikan.

Pembalut fungsi PowerShell

Dengan pembungkus fungsi PowerShell, kami boleh menambah pelengkapan arahan dan menghapuskan keperluan untuk awalan wsl, menterjemah laluan Windows ke laluan WSL. Keperluan asas untuk cengkerang:

  • Untuk setiap arahan Linux mesti ada satu pembungkus fungsi dengan nama yang sama.
  • Cangkang mesti mengenali laluan Windows yang diluluskan sebagai argumen dan menukarnya kepada laluan WSL.
  • Shell harus memanggil wsl dengan arahan Linux yang sesuai kepada mana-mana input saluran paip dan menghantar sebarang argumen baris arahan yang dihantar ke fungsi.

Memandangkan corak ini boleh digunakan pada mana-mana arahan, kita boleh mengabstrak definisi pembungkus ini dan menjananya secara dinamik daripada senarai arahan untuk diimport.

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

Senarai $command mentakrifkan arahan import. Kami kemudiannya menjana pembungkus fungsi secara dinamik untuk setiap daripada mereka menggunakan arahan Invoke-Expression (dengan terlebih dahulu mengalih keluar sebarang alias yang akan bercanggah dengan fungsi).

Fungsi ini berulang atas argumen baris arahan, menentukan laluan Windows menggunakan arahan Split-Path и Test-Pathdan kemudian menukar laluan ini kepada laluan WSL. Kami menjalankan laluan melalui fungsi pembantu Format-WslArgument, yang akan kami tentukan kemudian. Ia melepaskan watak istimewa seperti ruang dan kurungan yang sebaliknya akan disalah tafsir.

Akhir kata, kami sampaikan wsl input saluran paip dan sebarang hujah baris arahan.

Dengan pembungkus ini anda boleh memanggil arahan Linux kegemaran anda dengan cara yang lebih semula jadi tanpa menambah awalan wsl dan tanpa bimbang tentang bagaimana laluan ditukar:

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

Set perintah asas ditunjukkan di sini, tetapi anda boleh mencipta shell untuk mana-mana arahan Linux dengan hanya menambahkannya pada senarai. Jika anda menambah kod ini pada anda profil PowerShell, arahan ini akan tersedia untuk anda dalam setiap sesi PowerShell, sama seperti arahan asli!

Tetapan Lalai

Di Linux, adalah perkara biasa untuk mentakrifkan alias dan/atau pembolehubah persekitaran dalam profil log masuk, menetapkan parameter lalai untuk arahan yang kerap digunakan (contohnya, alias ls=ls -AFh atau export LESS=-i). Salah satu kelemahan proksi melalui shell bukan interaktif wsl.exe - bahawa profil tidak dimuatkan, jadi pilihan ini tidak tersedia secara lalai (iaitu. ls dalam WSL dan wsl ls akan berkelakuan berbeza dengan alias yang ditakrifkan di atas).

PowerShell menyediakan $PSDefaultParameterValues, mekanisme standard untuk menentukan parameter lalai, tetapi hanya untuk cmdlet dan fungsi lanjutan. Sudah tentu, kita boleh membuat fungsi lanjutan daripada cangkerang kita, tetapi ini memperkenalkan komplikasi yang tidak perlu (contohnya, PowerShell mengaitkan nama parameter separa (contohnya, -a berkorelasi dengan -ArgumentList), yang akan bercanggah dengan arahan Linux yang mengambil nama separa sebagai argumen), dan sintaks untuk mentakrifkan nilai lalai tidak akan menjadi yang paling sesuai (argumen lalai memerlukan nama parameter dalam kunci, bukan hanya nama arahan) .

Walau bagaimanapun, dengan sedikit pengubahsuaian pada cangkerang kami, kami boleh melaksanakan model yang serupa dengan $PSDefaultParameterValues, dan dayakan pilihan lalai untuk arahan 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 ' ')
    }
}

lulus $WslDefaultParameterValues ke baris arahan, kami menghantar parameter melalui wsl.exe. Berikut menunjukkan cara menambah arahan pada profil PowerShell anda untuk mengkonfigurasi tetapan lalai. Sekarang kita boleh melakukannya!

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

Oleh kerana parameter dimodelkan selepas $PSDefaultParameterValues, Awak boleh mudah untuk melumpuhkan mereka buat sementara waktu dengan memasang kunci "Disabled" menjadi makna $true. Manfaat tambahan jadual cincang yang berasingan ialah keupayaan untuk melumpuhkan $WslDefaultParameterValues secara berasingan daripada $PSDefaultParameterValues.

Penyelesaian hujah

PowerShell membolehkan anda mendaftarkan treler hujah menggunakan arahan Register-ArgumentCompleter. Bash mempunyai kuasa alat penyiapan automatik boleh atur cara. WSL membolehkan anda memanggil bash daripada PowerShell. Jika kami boleh mendaftarkan penyelesaian hujah untuk pembungkus fungsi PowerShell kami dan memanggil bash untuk menjana penyelesaian, kami mendapat penyelesaian hujah penuh dengan ketepatan yang sama seperti bash itu sendiri!

# 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 ini agak padat tanpa memahami beberapa fungsi dalaman bash, tetapi pada asasnya apa yang kami lakukan ialah ini:

  • Mendaftarkan pelengkap hujah untuk semua pembungkus fungsi kami dengan menghantar senarai $commands dalam parameter -CommandName untuk Register-ArgumentCompleter.
  • Kami memetakan setiap arahan ke fungsi shell yang digunakan oleh bash untuk autolengkap (untuk menentukan spesifikasi autolengkap, penggunaan bash $F, singkatan untuk complete -F <FUNCTION>).
  • Menukar Argumen PowerShell $wordToComplete, $commandAst и $cursorPosition ke dalam format yang diharapkan oleh fungsi autolengkap bash mengikut spesifikasi penyiapan automatik boleh atur cara bash.
  • Kami mengarang baris arahan untuk dipindahkan ke wsl.exe, yang memastikan persekitaran disediakan dengan betul, memanggil fungsi pelengkapan automatik yang sesuai dan mengeluarkan hasil dalam fesyen baris demi baris.
  • Lepas tu kita call wsl dengan baris arahan, kami memisahkan output dengan pemisah baris dan menjana untuk setiap satu CompletionResults, menyusunnya dan melarikan aksara seperti ruang dan kurungan yang sebaliknya akan disalahtafsirkan.

Akibatnya, cengkerang perintah Linux kami akan menggunakan autolengkap yang sama seperti bash! Sebagai contoh:

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

Setiap autolengkap membekalkan nilai khusus kepada hujah sebelumnya, membaca data konfigurasi seperti hos yang diketahui daripada WSL!

<TAB> akan kitaran melalui parameter. <Ctrl + пробел> akan menunjukkan semua pilihan yang tersedia.

Selain itu, memandangkan kami kini mempunyai autolengkap bash, anda boleh autolengkapkan laluan Linux terus dalam PowerShell!

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

Dalam kes di mana autolengkap bash tidak menghasilkan sebarang hasil, PowerShell berbalik kepada laluan Windows lalai sistem. Oleh itu, dalam amalan, anda boleh menggunakan kedua-dua laluan secara serentak mengikut budi bicara anda.

Kesimpulan

Menggunakan PowerShell dan WSL, kami boleh menyepadukan arahan Linux ke dalam Windows sebagai aplikasi asli. Tidak perlu mencari binaan Win32 atau utiliti Linux atau mengganggu aliran kerja anda dengan pergi ke cangkerang Linux. Cuma pasang WSL, konfigurasikan Profil PowerShell и senaraikan arahan yang ingin anda import! Autolengkap yang kaya untuk parameter arahan dan laluan fail Linux dan Windows ialah fungsi yang tidak tersedia dalam arahan Windows asli hari ini.

Kod sumber penuh yang diterangkan di atas, serta garis panduan tambahan untuk memasukkannya ke dalam aliran kerja anda, tersedia di sini.

Perintah Linux manakah yang anda rasa paling berguna? Apakah perkara biasa lain yang hilang semasa bekerja di Windows? Tulis dalam komen atau pada GitHub!

Sumber: www.habr.com

Tambah komen