Интегрирање на команди на Linux во Windows користејќи PowerShell и WSL

Типично прашање од програмерите на Windows: „Зошто сè уште нема <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Без разлика дали тоа е моќно лизгање less или познати алатки grep или sed, програмерите на Windows сакаат лесен пристап до овие команди во нивната секојдневна работа.

Подсистем Windows за Linux (WSL) направи огромен чекор напред во овој поглед. Тоа ви овозможува да повикувате команди на Linux од Windows преку нивно прокси wsl.exe (на пр. wsl ls). Иако ова е значително подобрување, оваа опција има голем број на недостатоци.

  • Сеприсутно дополнување wsl мачна и неприродна.
  • Патеките на Windows во аргументите не секогаш функционираат бидејќи обратните црти се толкуваат како знаци за бегство наместо како раздвојувачи на директориуми.
  • Патеките на Windows во аргументите не се преведени на соодветната точка на монтирање во WSL.
  • Стандардните поставки не се почитуваат во WSL профилите со псевдоними и променливи на околината.
  • Завршувањето на патеката на Linux не е поддржано.
  • Завршувањето на командата не е поддржано.
  • Пополнувањето на аргументот не е поддржано.

Како резултат на тоа, командите на Линукс се третираат како граѓани од втора класа под Windows - и се потешки за употреба од домашните команди. За да се изедначат нивните права, потребно е да се решат наведените проблеми.

Обвивки за функции PowerShell

Со обвивките на функциите PowerShell, можеме да додадеме комплетирање на командата и да ја елиминираме потребата за префикси wsl, преведување на патеките на Windows во WSL патеки. Основни барања за школки:

  • За секоја команда на Linux мора да има една обвивка на функции со исто име.
  • Школката мора да ги препознае патеките на Windows предадени како аргументи и да ги конвертира во WSL патеки.
  • Школката треба да се јави wsl со соодветната Линукс команда на кој било влез од цевководот и пренесување на аргументите на командната линија предадени на функцијата.

Бидејќи оваа шема може да се примени на која било команда, можеме да ја апстрахираме дефиницијата на овие обвивки и динамички да ги генерираме од списокот на команди за увоз.

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

Листа $command дефинира команди за увоз. Потоа динамички генерираме обвивка на функции за секоја од нив користејќи ја командата Invoke-Expression (со прво отстранување на сите псевдоними што би биле во конфликт со функцијата).

Функцијата се повторува преку аргументите на командната линија, ги одредува патеките на Windows користејќи команди Split-Path и Test-Pathа потоа ги конвертира овие патеки во WSL патеки. Ги водиме патеките преку функцијата помошник Format-WslArgument, што ќе го дефинираме подоцна. Избегнува специјални знаци како празни места и загради кои инаку би биле погрешно протолкувани.

Конечно, пренесуваме wsl внесување на гасоводот и сите аргументи на командната линија.

Со овие обвивки можете да ги повикате омилените команди на Linux на поприроден начин без да додавате префикс wsl и без да се грижите за тоа како се претвораат патеките:

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

Основниот сет на команди е прикажан овде, но можете да креирате школка за која било команда на Linux со едноставно додавање на списокот. Ако го додадете овој код на вашиот профил PowerShell, овие команди ќе ви бидат достапни во секоја PowerShell сесија, исто како и мајчин команди!

Стандардни поставки

Во Linux, вообичаено е да се дефинираат псевдоними и/или променливи на околината во профилите за најавување, поставувајќи ги стандардните параметри за често користените команди (на пример, alias ls=ls -AFh или export LESS=-i). Еден од недостатоците на проксирањето преку неинтерактивна школка wsl.exe - дека профилите не се вчитани, така што овие опции не се стандардно достапни (т.е. ls во WSL и wsl ls ќе се однесуваат поинаку со алијасот дефиниран погоре).

PowerShell обезбедува $PSDefaultParameterValues, стандарден механизам за дефинирање на стандардните параметри, но само за cmdlets и напредните функции. Се разбира, можеме да направиме напредни функции од нашите школки, но ова воведува непотребни компликации (на пример, PowerShell корелира парцијални имиња на параметри (на пример, -a корелира со -ArgumentList), што ќе биде во конфликт со командите на Linux кои земаат делумни имиња како аргументи), а синтаксата за дефинирање на стандардните вредности нема да биде најсоодветна (стандардните аргументи бараат името на параметарот во клучот, а не само името на командата) .

Сепак, со мала модификација на нашите школки, можеме да имплементираме модел сличен на $PSDefaultParameterValues, и овозможете ги стандардните опции за командите на 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 ' ')
    }
}

Полагање $WslDefaultParameterValues до командната линија, испраќаме параметри преку wsl.exe. Следното покажува како да додадете инструкции на вашиот профил на PowerShell за да ги конфигурирате стандардните поставки. Сега можеме да го направиме тоа!

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

Бидејќи параметрите се моделирани по $PSDefaultParameterValues, Ти можеш лесно е да ги оневозможите привремено со инсталирање на клучот "Disabled" во значење $true. Дополнителна придобивка од посебна табела за хаш е можноста за оневозможување $WslDefaultParameterValues одделно од $PSDefaultParameterValues.

Довршување на аргументот

PowerShell ви овозможува да регистрирате приколки за аргументи користејќи ја командата Register-ArgumentCompleter. Баш има моќно програмабилни алатки за автоматско комплетирање. WSL ви овозможува да повикате bash од PowerShell. Ако можеме да регистрираме комплетирање на аргументи за нашите обвивки на функции PowerShell и да го повикаме bash за да ги генерираме комплетирањата, добиваме целосно комплетирање на аргументите со иста прецизност како и самиот bash!

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

Кодот е малку густ без разбирање на некои од внатрешните функции на bash, но во основа она што го правиме е ова:

  • Регистрирање на пополнувач на аргументи за сите наши обвивки на функции со додавање листа $commands во параметар -CommandName за Register-ArgumentCompleter.
  • Секоја команда ја мапираме со функцијата на школка што bash ја користи за автоматско комплетирање (за дефинирање на спецификациите за автоматско комплетирање, баш користи $F, кратенка за complete -F <FUNCTION>).
  • Конвертирање на аргументи на PowerShell $wordToComplete, $commandAst и $cursorPosition во форматот што го очекуваат функциите за автоматско пополнување на bash според спецификациите програмабилно автоматско завршување баш.
  • Ние составуваме командна линија за да ја пренесеме wsl.exe, кој осигурува дека околината е правилно поставена, ја повикува соодветната функција за автоматско завршување и ги прикажува резултатите во линија по линија.
  • Потоа се јавуваме wsl со командната линија, го одделуваме излезот по сепаратори на линии и генерираме за секој CompletionResults, сортирање на нив и бегство од знаци како празни места и загради кои инаку би биле погрешно протолкувани.

Како резултат на тоа, нашите командни школки на Linux ќе го користат истото автоматско комплетирање како bash! На пример:

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

Секое автоматско комплетирање обезбедува вредности специфични за претходниот аргумент, читајќи податоци за конфигурација како што се познати хостови од WSL!

<TAB> ќе кружи низ параметрите. <Ctrl + пробел> ќе ги прикаже сите достапни опции.

Плус, бидејќи сега имаме баш автоматско комплетирање, можете автоматски да ги комплетирате патеките на Linux директно во PowerShell!

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

Во случаи кога баш автоматското комплетирање не дава никакви резултати, PowerShell се враќа на стандардните системски патеки на Windows. Така, во пракса, можете истовремено да ги користите двете патеки по ваша дискреција.

Заклучок

Користејќи PowerShell и WSL, можеме да интегрираме команди на Linux во Windows како мајчин апликации. Нема потреба да барате изданија на Win32 или комунални услуги за Linux или да го прекинувате работниот тек со одење во школка на Линукс. Само инсталирај WSL, конфигурирајте Профил на PowerShell и наведете ги командите што сакате да ги увезете! Богатото автоматско пополнување за параметрите на командите на Linux и Windows и патеките на датотеките е функционалност што не е достапна дури ни во матичните команди на Windows денес.

Целосниот изворен код опишан погоре, како и дополнителни упатства за негово вклучување во вашиот работен тек, е достапен тука.

Кои команди на Linux ви се најкорисни? Кои други вообичаени работи недостасуваат кога работите во Windows? Напишете во коментар или на GitHub!

Извор: www.habr.com

Додадете коментар