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-ի ճանապարհի ավարտը չի աջակցվում:
  • Հրամանի ավարտը չի աջակցվում:
  • Փաստարկի լրացումը չի աջակցվում:

Արդյունքում, Linux-ի հրամանները Windows-ի տակ վերաբերվում են որպես երկրորդ կարգի քաղաքացիների և ավելի դժվար է օգտագործել, քան բնիկ հրամանները: Նրանց իրավունքները հավասարեցնելու համար անհրաժեշտ է լուծել թվարկված խնդիրները։

PowerShell ֆունկցիայի փաթաթիչներ

PowerShell ֆունկցիայի փաթաթիչներով մենք կարող ենք ավելացնել հրամանի ավարտը և վերացնել նախածանցների անհրաժեշտությունը wsl, թարգմանելով Windows-ի ուղիները WSL ուղիների: Ռումբերի հիմնական պահանջները.

  • Linux-ի յուրաքանչյուր հրամանի համար պետք է լինի մեկ գործառույթի փաթաթան՝ նույն անունով:
  • Կեղևը պետք է ճանաչի Windows-ի անցած ուղիները որպես փաստարկներ և փոխակերպի դրանք WSL ուղիների:
  • Կեղևը պետք է կանչի wsl Linux-ի համապատասխան հրամանով ցանկացած խողովակաշարի մուտքագրման համար և փոխանցելով ցանկացած հրամանի տող արգումենտ, որը փոխանցվել է ֆունկցիային:

Քանի որ այս օրինաչափությունը կարող է կիրառվել ցանկացած հրամանի վրա, մենք կարող ենք վերացականացնել այս փաթաթանների սահմանումը և դինամիկ ձևավորել դրանք ներմուծման հրամանների ցանկից:

# 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, ստանդարտ մեխանիզմ՝ լռելյայն պարամետրերի սահմանման համար, բայց միայն cmdlet-ների և առաջադեմ գործառույթների համար։ Իհարկե, մենք կարող ենք կատարել առաջադեմ գործառույթներ մեր պատյաններից, բայց դա ավելորդ բարդություններ է առաջացնում (օրինակ, 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.
  • Մենք յուրաքանչյուր հրամանը քարտեզագրում ենք shell ֆունկցիայի վրա, որն օգտագործում է bash-ը ավտոմատ լրացման համար (ավտոլրացման առանձնահատկությունները սահմանելու համար, 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 + пробел> ցույց կտա բոլոր առկա տարբերակները:

Բացի այդ, քանի որ մենք այժմ ունենք bash ավտոմատ լրացում, դուք կարող եք ավտոմատ կերպով լրացնել Linux ուղիները անմիջապես PowerShell-ում:

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

Այն դեպքերում, երբ bash-ի ինքնալրացումը որևէ արդյունք չի տալիս, PowerShell-ը վերադառնում է Windows-ի համակարգի լռելյայն ուղիներին: Այսպիսով, գործնականում դուք կարող եք միաժամանակ օգտագործել երկու ուղիները ձեր հայեցողությամբ:

Ամփոփում

Օգտագործելով PowerShell-ը և WSL-ը, մենք կարող ենք Linux-ի հրամանները ինտեգրել Windows-ին՝ որպես բնօրինակ հավելվածներ: Կարիք չկա որոնել Win32 builds կամ Linux կոմունալ ծառայություններ կամ ընդհատել ձեր աշխատանքային հոսքը՝ անցնելով Linux shell: Պարզապես տեղադրել WSL, կազմաձևել PowerShell պրոֆիլը и նշեք այն հրամանները, որոնք ցանկանում եք ներմուծել! Linux-ի և Windows-ի հրամանների պարամետրերի և ֆայլերի ուղիների հարուստ ավտոմատ լրացումը ֆունկցիոնալություն է, որն այսօր նույնիսկ հասանելի չէ Windows-ի բնիկ հրամաններում:

Վերևում նկարագրված ամբողջական աղբյուրի կոդը, ինչպես նաև այն ձեր աշխատանքային գործընթացում ներառելու լրացուցիչ ուղեցույցներ հասանելի են այստեղ.

Linux-ի ո՞ր հրամաններն եք առավել օգտակար: Ի՞նչ այլ ընդհանուր բաներ են բացակայում Windows-ում աշխատելիս: Գրեք մեկնաբանություններում կամ GitHub-ում!

Source: www.habr.com

Добавить комментарий