Using linux commands in windows

Интегрируем команды Linux в Windows с помощью PowerShell и WSL

Типичный вопрос разработчиков под Windows: «Почему здесь до сих пор нет <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less или привычные инструменты grep или sed , разработчики под Windows хотят получить лёгкий доступ к этим командам в повседневной работе.

  • Повсеместное добавление wsl утомительно и неестественно.
  • Пути Windows в аргументах не всегда срабатывают, потому что обратные слэши интерпретируются как escape-символы, а не разделители каталогов.
  • Пути Windows в аргументах не переводятся в соответствующую точку монтирования в WSL.
  • Не учитываются параметры по умолчанию в профилях WSL с алиасами и переменными окружения.
  • Не поддерживается завершение путей Linux.
  • Не поддерживается завершение команд.
  • Не поддерживается завершение аргументов.

Оболочки функций PowerShell

C помощью оболочек функций 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 (сначала удалив любые алиасы, которые будут конфликтовать с функцией).

Читайте также:  Bodhi linux по русски

Функция перебирает аргументы командной строки, определяет пути 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 принято определять алиасы и/или переменные окружения в профилях (login profile), задавая параметры по умолчанию для часто используемых команд (например, alias ls=ls -AFh или export LESS=-i ). Один из недостатков проксирования через неинтерактивную оболочку wsl.exe — то, что профили не загружаются, поэтому эти параметры по умолчанию недоступны (т. е. ls в WSL и wsl ls будут вести себя по-разному с алиасом, определённым выше).

PowerShell предоставляет $PSDefaultParameterValues, стандартный механизм для определения параметров по умолчанию, но только для командлетов и расширенных функций. Конечно, можно из наших оболочек сделать расширенные функции, но это вносит лишние осложнения (так, PowerShell соотносит частичные имена параметров (например, -a соотносится с -ArgumentList ), которые будут конфликтовать с командами Linux, принимающими частичные имена в качестве аргументов), а синтаксис для определения значений по умолчанию будет не самым подходящим (для определения аргументов по умолчанию требуется имя параметра в ключе, а не только имя команды).

Однако с небольшим изменением наших оболочек мы можем внедрить модель, аналогичную $PSDefaultParameterValues , и включить параметры по умолчанию для команд Linux!

Передавая $WslDefaultParameterValues в командную строку, мы отправляем параметры через wsl.exe . Ниже показано, как добавить инструкции в профиль PowerShell для настройки параметров по умолчанию. Теперь мы можем это сделать!

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

Поскольку параметры моделируются после $PSDefaultParameterValues , вы можете легко их отключить на время, установив ключ «Disabled» в значение $true . Дополнительное преимущество отдельной хэш-таблицы в возможности отключить $WslDefaultParameterValues отдельно от $PSDefaultParameterValues .

Читайте также:  What is tftp server in linux

Автозавершение аргументов

PowerShell позволяет регистрировать завершители аргументов с помощью команды Register-ArgumentCompleter . В Bash есть мощные программируемые средства для автозавершения. 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) <  < "_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'/ where 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 $commandCompletion $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 `"`$`"" $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 для автозавершения (для определения спецификаций автозавершения в bash используется $F , сокращение от complete -F ).
  • Преобразуем аргументы PowerShell $wordToComplete , $commandAst и $cursorPosition в формат, ожидаемый функциями автозавершения bash в соответствии со спецификациями программируемого автозавершения bash.
  • Составляем командную строку для передачи в wsl.exe , который обеспечивает правильную настройку среды, вызывает соответствующую функцию автозавершения и выводит результаты с разбиением по строкам.
  • Затем вызываем wsl с командной строкой, разделяем выдачу разделителями строк и генерируем для каждой CompletionResults , сортируя их и экранируя символы, такие как пробелы и скобки, которые в противном случае были бы неверно истолкованы.
Читайте также:  Linux разрешение экрана при загрузке

будет циклически перебирать параметры. покажет все доступные опции.

Кроме того, поскольку теперь у нас работает автозавершение bash, вы можете автозавершать пути Linux непосредственно в PowerShell!

Заключение

С помощью PowerShell и WSL мы можем интегрировать команды Linux в Windows как нативные приложения. Нет необходимости искать билды Win32 или утилиты Linux или прерывать рабочий процесс, переходя в Linux-шелл. Просто установите WSL, настройте профиль PowerShell и перечислите команды, которые хотите импортировать! Богатое автозавершение для параметров команд и путей к файлам Linux и Windows — это функциональность, которой сегодня нет даже в нативных командах Windows.

Полный исходный код, описанный выше, а также дополнительные рекомендации по его включению в рабочий процесс доступны здесь.

Какие команды Linux вы считаете наиболее полезными? Каких ещё привычных вещей не хватает при работе в Windows? Пишите в комментариях или на GitHub!

Источник

Оцените статью
Adblock
detector