PowerShell ve WSL kullanarak Linux komutlarını Windows'a entegre etme

Windows geliştiricilerinin tipik bir sorusu: “Neden hala yok? <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Güçlü bir kaydırma olup olmadığı less veya tanıdık araçlar grep veya sedWindows geliştiricileri günlük işlerinde bu komutlara kolay erişim istiyor.

Linux için Windows Alt Sistemi (WSL) bu konuda çok büyük bir adım attı. Windows'tan Linux komutlarını proxy yoluyla çağırmanıza olanak tanır. wsl.exe (Örneğin, wsl ls). Bu önemli bir gelişme olmasına rağmen, bu seçeneğin bir takım dezavantajları vardır.

  • Her yerde bulunan ekleme wsl sıkıcı ve doğal değil.
  • Bağımsız değişkenlerdeki Windows yolları her zaman çalışmaz çünkü ters eğik çizgiler dizin ayırıcıları yerine kaçış karakterleri olarak yorumlanır.
  • Bağımsız değişkenlerdeki Windows yolları, WSL'deki karşılık gelen bağlama noktasına çevrilmez.
  • Takma adlara ve ortam değişkenlerine sahip WSL profillerinde varsayılan ayarlara uyulmaz.
  • Linux yol tamamlama desteklenmiyor.
  • Komut tamamlama desteklenmiyor.
  • Bağımsız değişken tamamlama desteklenmiyor.

Sonuç olarak, Linux komutları Windows altında ikinci sınıf vatandaş muamelesi görür ve bunların kullanımı yerel komutlara göre daha zordur. Haklarının eşitlenmesi için yukarıda sıralanan sorunların çözülmesi gerekmektedir.

PowerShell işlev sarmalayıcıları

PowerShell işlev sarmalayıcıları ile komut tamamlama ekleyebilir ve önek ihtiyacını ortadan kaldırabiliriz wsl, Windows yollarını WSL yollarına çeviriyor. Kabuklar için temel gereksinimler:

  • Her Linux komutu için aynı ada sahip bir işlev sarmalayıcı bulunmalıdır.
  • Kabuğun argüman olarak iletilen Windows yollarını tanıması ve bunları WSL yollarına dönüştürmesi gerekir.
  • Kabuk çağırmalı wsl herhangi bir boru hattı girişine uygun Linux komutuyla ve işleve iletilen komut satırı bağımsız değişkenlerini ileterek.

Bu model herhangi bir komuta uygulanabildiğinden, bu sarmalayıcıların tanımını soyutlayabilir ve bunları içe aktarılacak komutlar listesinden dinamik olarak oluşturabiliriz.

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

Liste $command içe aktarma komutlarını tanımlar. Daha sonra komutu kullanarak her biri için dinamik olarak bir işlev sarmalayıcı oluştururuz. Invoke-Expression (öncelikle işlevle çelişecek tüm takma adları kaldırarak).

İşlev, komut satırı argümanları üzerinde yinelenir, komutları kullanarak Windows yollarını belirler Split-Path и Test-Pathve sonra bu yolları WSL yollarına dönüştürür. Yolları bir yardımcı fonksiyon aracılığıyla çalıştırıyoruz Format-WslArgumentbunu daha sonra tanımlayacağız. Aksi takdirde yanlış yorumlanabilecek boşluk ve parantez gibi özel karakterlerden kaçınılır.

Son olarak aktarıyoruz wsl boru hattı girişi ve herhangi bir komut satırı argümanı.

Bu sarmalayıcılarla favori Linux komutlarınızı önek eklemeden daha doğal bir şekilde çağırabilirsiniz. wsl ve yolların nasıl dönüştürüldüğü konusunda endişelenmeden:

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

Temel komut seti burada gösterilmektedir, ancak herhangi bir Linux komutu için onu yalnızca listeye ekleyerek bir kabuk oluşturabilirsiniz. Bu kodu sitenize eklerseniz profil PowerShell, bu komutlar tıpkı yerel komutlar gibi her PowerShell oturumunda kullanımınıza sunulacak!

Varsayılan ayarları

Linux'ta, oturum açma profillerinde takma adlar ve/veya ortam değişkenleri tanımlamak, sık kullanılan komutlar için varsayılan parametreleri ayarlamak yaygındır (örneğin, alias ls=ls -AFh veya export LESS=-i). Etkileşimli olmayan bir kabuk aracılığıyla proxy oluşturmanın dezavantajlarından biri wsl.exe - profiller yüklenmediğinden bu seçenekler varsayılan olarak mevcut değildir (ör. ls WSL'de ve wsl ls yukarıda tanımlanan takma adla farklı davranacaktır).

PowerShell şunları sağlar $PSDefaultParameterValues, varsayılan parametreleri tanımlamak için standart bir mekanizmadır, ancak yalnızca cmdlet'ler ve gelişmiş işlevler içindir. Elbette kabuklarımızdan gelişmiş işlevler oluşturabiliriz, ancak bu gereksiz karmaşıklıklara neden olur (örneğin, PowerShell kısmi parametre adlarını ilişkilendirir (örneğin, -a ile ilişkilidir -ArgumentList), bağımsız değişken olarak kısmi adlar alan Linux komutlarıyla çakışacaktır) ve varsayılanları tanımlamak için kullanılan sözdizimi en uygun olmayacaktır (varsayılan bağımsız değişkenleri tanımlamak yalnızca komut adını değil, anahtardaki parametre adını da gerektirir).

Ancak kabuklarımızda ufak bir değişiklik yaparak buna benzer bir model uygulayabiliriz. $PSDefaultParameterValuesve Linux komutları için varsayılan seçenekleri etkinleştirin!

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 ' ')
    }
}

Geçen $WslDefaultParameterValues komut satırına parametreleri gönderiyoruz wsl.exe. Aşağıda, varsayılan ayarları yapılandırmak için PowerShell profilinize talimatların nasıl ekleneceği gösterilmektedir. Artık bunu yapabiliriz!

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

Parametreler modellendikten sonra $PSDefaultParameterValuesyapabilirsin onları devre dışı bırakmak kolaydır anahtarı takarak geçici olarak "Disabled" anlam içine $true. Ayrı bir karma tablosunun ek bir yararı, devre dışı bırakma yeteneğidir. $WslDefaultParameterValues ayrı $PSDefaultParameterValues.

Bağımsız değişken tamamlama

PowerShell, komutu kullanarak bağımsız değişken fragmanlarını kaydetmenize olanak tanır Register-ArgumentCompleter. Bash'in güçlü bir özelliği var programlanabilir otomatik tamamlama araçları. WSL, PowerShell'den bash'ı çağırmanıza olanak tanır. PowerShell işlev sarmalayıcılarımız için argüman tamamlamalarını kaydedebilirsek ve tamamlamaları oluşturmak için bash'ı çağırabilirsek, tam argüman tamamlamayı bash'ın kendisiyle aynı hassasiyetle elde ederiz!

# 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, bash'ın bazı dahili işlevlerini anlamadan biraz yoğun olabilir, ancak temel olarak yaptığımız şey şudur:

  • Bir liste ileterek tüm işlev sarmalayıcılarımız için bir argüman tamamlayıcıyı kaydetme $commands parametrede -CommandName için Register-ArgumentCompleter.
  • Her komutu, bash'ın otomatik tamamlama için kullandığı kabuk işleviyle eşleştiririz (otomatik tamamlama özelliklerini tanımlamak için bash, $Fkısaltması complete -F <FUNCTION>).
  • PowerShell Bağımsız Değişkenlerini Dönüştürme $wordToComplete, $commandAst и $cursorPosition spesifikasyonlara göre bash'ın otomatik tamamlama fonksiyonlarının beklediği formata programlanabilir otomatik tamamlama bash.
  • Aktarılacak bir komut satırı oluşturuyoruz wsl.exeOrtamın doğru şekilde kurulmasını sağlayan, uygun otomatik tamamlama işlevini çağırır ve sonuçları satır satır çıkarır.
  • Sonra ararız wsl komut satırıyla çıktıyı satır ayırıcılarla ayırıp her biri için üretiyoruz CompletionResults, bunları sıralıyor ve aksi takdirde yanlış yorumlanabilecek boşluk ve parantez gibi karakterlerden kaçınıyor.

Sonuç olarak, Linux komut kabuklarımız bash ile tamamen aynı otomatik tamamlamayı kullanacak! Örneğin:

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

Her otomatik tamamlama, WSL'den bilinen ana bilgisayarlar gibi yapılandırma verilerini okuyarak önceki bağımsız değişkene özgü değerleri sağlar!

<TAB> parametreler arasında geçiş yapacaktır. <Ctrl + пробел> mevcut tüm seçenekleri gösterecektir.

Ayrıca, artık bash otomatik tamamlama özelliğimiz olduğundan, Linux yollarını doğrudan PowerShell'de otomatik olarak tamamlayabilirsiniz!

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

Bash otomatik tamamlamanın herhangi bir sonuç vermediği durumlarda PowerShell, sistemin varsayılan Windows yollarına geri döner. Böylece pratikte her iki yolu da kendi takdirinize bağlı olarak aynı anda kullanabilirsiniz.

Sonuç

PowerShell ve WSL kullanarak Linux komutlarını Windows'a yerel uygulamalar olarak entegre edebiliriz. Win32 yapılarını veya Linux yardımcı programlarını aramanıza veya bir Linux kabuğuna giderek iş akışınızı kesintiye uğratmanıza gerek yoktur. Sadece WSL'yi yükle, yapılandır PowerShell profili и içe aktarmak istediğiniz komutları listeleyin! Linux ve Windows komut parametreleri ve dosya yolları için zengin otomatik tamamlama, bugün yerel Windows komutlarında bile bulunmayan bir işlevselliktir.

Yukarıda açıklanan kaynak kodunun tamamının yanı sıra bunu iş akışınıza dahil etmeye yönelik ek yönergeler de mevcuttur burada.

Hangi Linux komutlarını en yararlı buluyorsunuz? Windows'ta çalışırken başka hangi ortak şeyler eksik? Yorumlara yazın veya GitHub'da!

Kaynak: habr.com

Yorum ekle