< LINUX>
?". Apakah itu less
gulir kuat atau alat grep
atau sed
biasa, pengembang Windows ingin akses mudah ke perintah ini dalam pekerjaan sehari-hari.Windows Subsystem untuk Linux (WSL) telah mengambil langkah besar dalam hal ini. Ini memungkinkan Anda untuk memanggil perintah Linux dari Windows, memeriksa mereka melalui
wsl.exe
(misalnya, wsl ls
). Meskipun ini merupakan peningkatan yang signifikan, opsi ini menderita sejumlah kerugian.- Penambahan
wsl
di mana-mana melelahkan dan tidak wajar. - Jalur Windows dalam argumen tidak selalu berfungsi, karena garis miring terbalik ditafsirkan sebagai karakter pelarian, bukan pemisah direktori.
- Jalur Windows dalam argumen tidak diterjemahkan ke titik pemasangan yang sesuai di WSL.
- Pengaturan default di profil WSL dengan alias dan variabel lingkungan tidak diperhitungkan.
- Penyelesaian jalur Linux tidak didukung.
- Penyelesaian perintah tidak didukung.
- Penyelesaian argumen tidak didukung.
Akibatnya, perintah Linux dianggap di bawah Windows sebagai warga negara kelas dua - dan mereka lebih sulit digunakan daripada tim asli. Untuk menyamakan hak mereka, Anda harus menyelesaikan masalah ini.
Kerang PowerShell
Menggunakan pembungkus fungsi PowerShell, kita dapat menambahkan penyelesaian perintah dan menghilangkan kebutuhan untuk awalan
wsl
dengan menerjemahkan jalur Windows ke jalur WSL. Persyaratan dasar untuk kerang:- Setiap perintah Linux harus memiliki satu shell fungsi dengan nama yang sama.
- Shell harus mengenali jalur Windows yang dilewati sebagai argumen dan mengubahnya menjadi jalur WSL.
- Shell harus memanggil
wsl
dengan perintah Linux yang sesuai untuk input pipa apa pun dan meneruskan argumen baris perintah yang diteruskan ke fungsi tersebut.
Karena templat ini dapat diterapkan pada perintah apa pun, kami dapat mengabstraksi definisi shell ini dan secara dinamis menghasilkannya dari daftar perintah yang akan diimpor.
# 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 (eg 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 ' ') } } "@ }
Daftar
$command
mendefinisikan perintah untuk mengimpor. Kemudian kami secara dinamis menghasilkan pembungkus fungsi untuk masing-masing dari mereka menggunakan perintah Invoke-Expression
(pertama menghapus alias yang akan bertentangan dengan fungsi).Fungsi iterates atas argumen baris perintah, menentukan jalur Windows menggunakan perintah
Split-Path
dan Test-Path
, dan kemudian mengubah jalur ini ke jalur WSL. Kita menjalankan path melalui fungsi helper Format-WslArgument
, yang akan kita definisikan nanti. Itu lolos dari karakter khusus, seperti spasi dan tanda kurung, yang kalau tidak akan disalahartikan.Akhirnya, kami meneruskan input
wsl
dan argumen baris perintah apa pun ke wsl
.Menggunakan pembungkus ini, Anda dapat memanggil perintah Linux favorit Anda dengan cara yang lebih alami tanpa menambahkan awalan
wsl
dan tanpa khawatir tentang bagaimana jalur dikonversi:man bash
less -i $profile.CurrentUserAllHosts
ls -Al C:\Windows\ | less
grep -Ein error *.log
tail -f *.log
Set perintah dasar ditampilkan di sini, tetapi Anda dapat membuat shell untuk perintah Linux apa saja dengan hanya menambahkannya ke daftar. Jika Anda menambahkan kode ini ke profil PowerShell Anda, perintah ini akan tersedia untuk Anda di setiap sesi PowerShell, seperti halnya perintah asli!
Opsi default
Di Linux, biasanya untuk mendefinisikan alias dan / atau variabel lingkungan di profil (profil login), mengatur parameter default untuk perintah yang sering digunakan (misalnya,
alias ls=ls -AFh
atau export LESS=-i
). Salah satu kelemahan wsl.exe
shell wsl.exe
non-interaktif adalah bahwa profil tidak dimuat, oleh karena itu opsi ini tidak tersedia secara default (mis., ls
di WSL dan wsl ls
akan berperilaku berbeda dengan alias yang ditentukan di atas).PowerShell menyediakan $ PSDefaultParameterValues , mekanisme standar untuk mendefinisikan parameter default, tetapi hanya untuk cmdlet dan fungsi lanjutan. Tentu saja, Anda dapat membuat fungsi-fungsi lanjutan dari shell kami, tetapi ini menimbulkan komplikasi yang tidak perlu (misalnya, PowerShell cocok dengan nama parameter parsial (misalnya,
-ArgumentList
dengan -ArgumentList
), yang akan bertentangan dengan perintah Linux yang menerima nama parsial sebagai argumen), dan sintaks untuk menentukan nilai-nilai default tidak akan menjadi yang paling cocok (untuk mendefinisikan argumen default, diperlukan nama parameter pada kunci, dan bukan hanya nama perintah).Namun, dengan sedikit modifikasi pada shell kami, kami dapat mengimplementasikan model yang mirip dengan
$PSDefaultParameterValues
dan mengaktifkan opsi default untuk perintah 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 ' ') } }
Dengan
$WslDefaultParameterValues
ke baris perintah, kami mengirim parameter melalui wsl.exe
. Berikut ini menunjukkan cara menambahkan instruksi ke profil PowerShell untuk mengonfigurasi pengaturan default. Sekarang kita bisa melakukannya! $WslDefaultParameterValues["grep"] = "-E" $WslDefaultParameterValues["less"] = "-i" $WslDefaultParameterValues["ls"] = "-AFh --group-directories-first"
Karena parameter dimodelkan setelah
$PSDefaultParameterValues
, Anda dapat dengan mudah mematikannya sementara dengan mengatur kunci "Disabled"
menjadi $true
. Keuntungan tambahan dari tabel hash yang terpisah adalah kemampuan untuk menonaktifkan $WslDefaultParameterValues
secara terpisah dari $PSDefaultParameterValues
.Penyelesaian Argumen
PowerShell memungkinkan mendaftarkan terminator argumen menggunakan perintah
Register-ArgumentCompleter
. Bash memiliki alat penyelesaian yang kuat dan dapat diprogram . WSL memungkinkan Anda untuk memanggil bash dari PowerShell. Jika kita dapat mendaftarkan terminator argumen untuk pembungkus fungsi PowerShell kita dan memanggil bash untuk membuat terminasi, maka kita mendapatkan penyelesaian penuh argumen dengan presisi yang sama seperti di 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] } }
Kode ini sedikit ketat tanpa memahami beberapa bash internal, tetapi pada dasarnya kami melakukan hal berikut:
- Kami mendaftarkan finalizer argumen untuk semua pembungkus fungsi kami dengan meneruskan daftar
$commands
ke parameter-CommandName
untukRegister-ArgumentCompleter
. - Kami memetakan setiap perintah ke fungsi shell yang digunakan bash untuk pelengkapan otomatis (bash menggunakan
$F
untuk menentukan spesifikasi pelengkapan otomatis, singkatan daricomplete -F <FUNCTION>
). - Konversikan argumen PowerShell
$wordToComplete
,$commandAst
dan$cursorPosition
ke format yang diharapkan oleh fungsi penyelesaian bash sesuai dengan spesifikasi penyelesaian bash yang dapat diprogram . - Kami menyusun baris perintah untuk mentransfer ke
wsl.exe
, yang memastikan pengaturan lingkungan yang benar, memanggil fungsi pelengkapan otomatis yang sesuai dan menampilkan hasilnya dengan jeda baris. - Kemudian kita memanggil
wsl
dengan baris perintah, memisahkan output dengan pemisah baris dan menghasilkanCompletionResults
untuk masing-masing, mengurutkan mereka dan melarikan diri karakter seperti spasi dan tanda kurung yang seharusnya akan disalahartikan.
Akibatnya, shell perintah Linux kami akan menggunakan pelengkapan otomatis yang persis sama seperti di bash! Sebagai contoh:
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>
Setiap pelengkapan otomatis memberikan nilai khusus untuk argumen sebelumnya, membaca data konfigurasi, seperti host yang dikenal, dari WSL!
<TAB>
akan menggilir parameter. <Ctrl + >
akan menampilkan semua opsi yang tersedia.Juga, karena bash autocomplete sekarang berfungsi dengan kami, Anda dapat autocomplete path Linux langsung di PowerShell!
less /etc/<TAB>
ls /usr/share/<TAB>
vim ~/.bash<TAB>
Dalam kasus di mana bash pelengkapan otomatis gagal, PowerShell kembali ke sistem default dengan jalur Windows. Dengan demikian, dalam praktiknya, Anda dapat secara bersamaan menggunakan itu dan cara-cara lain sesuai kebijaksanaan Anda.
Kesimpulan
Dengan PowerShell dan WSL, kita dapat mengintegrasikan perintah Linux ke Windows sebagai aplikasi asli. Tidak perlu mencari Win32 builds atau utilitas Linux atau mengganggu alur kerja dengan beralih ke shell Linux. Cukup instal WSL , konfigurasikan profil PowerShell Anda dan daftarkan perintah yang ingin Anda impor ! Pelengkapan otomatis kaya untuk parameter perintah dan jalur untuk file Linux dan Windows adalah fungsi yang bahkan tidak ditemukan dalam perintah Windows asli hari ini.
Kode sumber lengkap yang dijelaskan di atas, serta rekomendasi tambahan untuk memasukkannya dalam alur kerja tersedia di sini .
Perintah Linux apa yang menurut Anda paling berguna? Apa hal-hal akrab lainnya yang hilang saat bekerja di Windows? Tulis di komentar atau di GitHub !