Uma pergunta típica de desenvolvedores Windows"Por que ainda não tem nada aqui?" <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Quer seja um golpe poderoso less ou ferramentas familiares grep ou sed, desenvolvedores sob Windows desejam ter acesso fácil a esses comandos em seu trabalho diário.
Deu um grande passo em frente nesse sentido. Permite que você execute comandos. Linux de Windows, encaminhando-os através de wsl.exe (Por exemplo, wsl ls). Embora esta seja uma melhoria significativa, esta opção apresenta várias desvantagens.
- Adição onipresente
wsltedioso e antinatural. - Caminhos Windows Em argumentos, nem sempre funciona porque as barras invertidas são interpretadas como caracteres de escape, e não como separadores de diretório.
- Caminhos Windows Os argumentos não são traduzidos para o ponto de montagem correspondente no WSL.
- As configurações padrão não são respeitadas em perfis WSL com aliases e variáveis de ambiente.
- O recurso de autocompletar caminhos não é suportado. Linux.
- A conclusão do comando não é suportada.
- A conclusão do argumento não é suportada.
Como resultado da equipe Linux são percebidos sob Windows Eles são como cidadãos de segunda classe — e mais difíceis de usar do que seus times de origem. Para igualar seus direitos, essas questões precisam ser abordadas.
Wrappers de função do PowerShell
Com wrappers de função do PowerShell, podemos adicionar a conclusão de comandos e eliminar a necessidade de prefixos wsl, caminhos de transmissão Windows No caminho do WSL. Requisitos básicos para shells:
- Para cada equipe Linux Deve haver pelo menos uma função wrapper com o mesmo nome.
- O shell deve reconhecer os caminhos. Windows, passados como argumentos, e convertê-los em caminhos WSL.
- O shell deve chamar
wslcom o comando apropriado Linux a qualquer entrada de pipeline e passando quaisquer argumentos de linha de comando passados para a função.
Como esse padrão pode ser aplicado a qualquer comando, podemos abstrair a definição desses wrappers e gerá-los dinamicamente a partir de uma lista de comandos para importar.
# 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 ' ')
}
}
"@
} Lista $command define comandos de importação. Em seguida, geramos dinamicamente um wrapper de função para cada um deles usando o comando Invoke-Expression (primeiro removendo quaisquer aliases que possam entrar em conflito com a função).
A função itera sobre os argumentos da linha de comando e determina os caminhos. Windows usando comandos Split-Path и Test-Pathe então converte esses caminhos em caminhos WSL. Executamos os caminhos por meio de uma função auxiliar Format-WslArgument, que definiremos mais adiante. Ele escapa de caracteres especiais, como espaços e parênteses, que de outra forma seriam mal interpretados.
Por fim, transmitimos wsl entrada do pipeline e quaisquer argumentos de linha de comando.
Com a ajuda desses wrappers, você pode chamar seus comandos favoritos. Linux de uma forma mais natural, sem adicionar um prefixo wsl e sem se preocupar em como os caminhos são convertidos:
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Aqui é apresentado um conjunto básico de comandos, mas você pode criar um wrapper para qualquer comando. Linux, basta adicioná-lo à lista. Se você adicionar este código ao seu PowerShell, esses comandos estarão disponíveis para você em todas as sessões do PowerShell, assim como os comandos nativos!
Parâmetros padrão
В Linux É comum definir aliases e/ou variáveis de ambiente em perfis (perfil de login), configurando parâmetros padrão para comandos usados com frequência (por exemplo, alias ls=ls -AFh ou export LESS=-i). Uma das desvantagens do proxy através de um shell não interativo wsl.exe - que os perfis não são carregados, portanto estas opções não estão disponíveis por padrão (ou seja, ls na WSL e wsl ls se comportará de maneira diferente com o alias definido acima).
PowerShell fornece , um mecanismo padrão para definir parâmetros padrão, mas apenas para cmdlets e funções avançadas. É claro que podemos criar funções avançadas a partir de nossos shells, mas isso introduz complicações desnecessárias (por exemplo, o PowerShell correlaciona nomes de parâmetros parciais (por exemplo, -a correlaciona com -ArgumentList), o que entrará em conflito com os comandos Linux, aceitando nomes parciais como argumentos), e a sintaxe para definir valores padrão não será a mais adequada (definir argumentos padrão requer o nome do parâmetro na opção, não apenas o nome do comando).
Porém, com uma ligeira modificação em nossos shells, podemos implementar um modelo semelhante ao $PSDefaultParameterValuese habilite as opções padrão para os comandos. 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 ' ')
}
} Passagem $WslDefaultParameterValues para a linha de comando, enviamos parâmetros via wsl.exe. Veja a seguir como adicionar instruções ao seu perfil do PowerShell para definir as configurações padrão. Agora podemos fazer isso!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Como os parâmetros são modelados a partir de $PSDefaultParameterValues, Você pode temporariamente instalando a chave "Disabled" em significado $true. Um benefício adicional de uma tabela hash separada é a capacidade de desativar $WslDefaultParameterValues separadamente de $PSDefaultParameterValues.
Conclusão do argumento
PowerShell permite registrar trailers de argumentos usando o comando Register-ArgumentCompleter. Bash tem poderoso . WSL permite que você chame o bash do PowerShell. Se pudermos registrar conclusões de argumentos para nossos wrappers de função do PowerShell e chamar o bash para gerar as conclusões, obteremos a conclusão completa dos argumentos com a mesma precisão que o próprio 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]
}
}O código é um pouco denso sem entender algumas funções internas do bash, mas basicamente o que fazemos é isto:
- Registrando um completador de argumento para todos os nossos wrappers de função passando uma lista
$commandsno parâmetro-CommandNameparaRegister-ArgumentCompleter. - Mapeamos cada comando para a função shell que o bash usa para preenchimento automático (para definir especificações de preenchimento automático, o bash usa
$F, abreviatura decomplete -F <FUNCTION>). - Convertendo argumentos do PowerShell
$wordToComplete,$commandAstи$cursorPositionno formato esperado pelas funções de preenchimento automático do bash de acordo com as especificações festança. - Compomos uma linha de comando para transferir para
wsl.exe, que garante que o ambiente esteja configurado corretamente, chama a função de preenchimento automático apropriada e gera os resultados linha por linha. - Então ligamos
wslcom a linha de comando, separamos a saída por separadores de linha e geramos para cadaCompletionResults, classificando-os e escapando de caracteres como espaços e parênteses que, de outra forma, seriam mal interpretados.
Como resultado, nossos shells de comando Linux Usará exatamente o mesmo recurso de autocompletar do bash! Por exemplo:
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>
Cada preenchimento automático fornece valores específicos do argumento anterior, lendo dados de configuração como hosts conhecidos do WSL!
<TAB> percorrerá os parâmetros. <Ctrl + пробел> mostrará todas as opções disponíveis.
Além disso, como agora temos o recurso de autocompletar do bash funcionando, você pode usar o recurso de autocompletar caminhos. Linux diretamente no PowerShell!
less /etc/<TAB>ls /usr/share/<TAB>vim ~/.bash<TAB>
Nos casos em que a autocompletação do bash não produz resultados, o PowerShell recorre aos caminhos padrão do sistema. WindowsAssim, na prática, você pode usar ambos os caminhos simultaneamente, a seu critério.
Conclusão
Usando o PowerShell e o WSL, podemos integrar comandos. Linux в Windows como aplicativos nativos. Não é preciso procurar por versões ou utilitários Win32. Linux ou interromper o fluxo de trabalho acessando Linux-concha. Apenas , configurar и ! Preenchimento automático avançado para parâmetros de comando e caminhos de arquivo Linux и Windows — essa é uma funcionalidade que não está disponível nem mesmo nos comandos nativos atualmente. Windows.
O código-fonte completo descrito acima, bem como diretrizes adicionais para incorporá-lo ao seu fluxo de trabalho, estão disponíveis .
Quais equipes? Linux O que você considera mais útil? Que outras coisas familiares estão faltando quando você trabalha em Windows? Escreva nos comentários ou !
Fonte: habr.com
