?". Apakah itu less gulir kuat atau alat grep atau sed biasa, pengembang...">

Kami mengintegrasikan perintah Linux di Windows menggunakan PowerShell dan WSL

Pertanyaan umum pengembang Windows: "Mengapa masih belum ada di sini < 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 untuk Register-ArgumentCompleter .
  • Kami memetakan setiap perintah ke fungsi shell yang digunakan bash untuk pelengkapan otomatis (bash menggunakan $F untuk menentukan spesifikasi pelengkapan otomatis, singkatan dari complete -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 menghasilkan CompletionResults 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 !

Source: https://habr.com/ru/post/id469257/


All Articles